From 72a7814eb7f70946bd894cd0f3b09053bba65c88 Mon Sep 17 00:00:00 2001 From: appqy <8822812@gmail.com> Date: Fri, 23 Sep 2022 20:04:24 +0800 Subject: [PATCH] init --- .gitignore | 5 + public/1.html | 24 - public/sign.js | 82 - public/solidityCode.js | 76 - public/web3.min.js | 1 - runtime/.gitkeep | 1 - thinkphp/.gitignore | 4 + thinkphp/.htaccess | 1 + thinkphp/.travis.yml | 47 + thinkphp/CONTRIBUTING.md | 119 + thinkphp/LICENSE.txt | 32 + thinkphp/README.md | 69 + thinkphp/base.php | 65 + thinkphp/codecov.yml | 12 + thinkphp/composer.json | 35 + thinkphp/console.php | 20 + thinkphp/convention.php | 298 + thinkphp/helper.php | 589 + thinkphp/lang/zh-cn.php | 136 + thinkphp/library/think/App.php | 677 + thinkphp/library/think/Build.php | 235 + thinkphp/library/think/Cache.php | 247 + thinkphp/library/think/Collection.php | 467 + thinkphp/library/think/Config.php | 214 + thinkphp/library/think/Console.php | 863 + thinkphp/library/think/Controller.php | 229 + thinkphp/library/think/Cookie.php | 268 + thinkphp/library/think/Db.php | 180 + thinkphp/library/think/Debug.php | 252 + thinkphp/library/think/Env.php | 39 + thinkphp/library/think/Error.php | 136 + thinkphp/library/think/Exception.php | 55 + thinkphp/library/think/File.php | 478 + thinkphp/library/think/Hook.php | 148 + thinkphp/library/think/Lang.php | 265 + thinkphp/library/think/Loader.php | 677 + thinkphp/library/think/Log.php | 237 + thinkphp/library/think/Model.php | 2350 + thinkphp/library/think/Paginator.php | 409 + thinkphp/library/think/Process.php | 1205 + thinkphp/library/think/Request.php | 1690 + thinkphp/library/think/Response.php | 332 + thinkphp/library/think/Route.php | 1645 + thinkphp/library/think/Session.php | 366 + thinkphp/library/think/Template.php | 1139 + thinkphp/library/think/Url.php | 333 + thinkphp/library/think/Validate.php | 1371 + thinkphp/library/think/View.php | 239 + thinkphp/library/think/cache/Driver.php | 231 + thinkphp/library/think/cache/driver/File.php | 268 + thinkphp/library/think/cache/driver/Lite.php | 187 + .../library/think/cache/driver/Memcache.php | 177 + .../library/think/cache/driver/Memcached.php | 187 + thinkphp/library/think/cache/driver/Redis.php | 188 + .../library/think/cache/driver/Sqlite.php | 199 + .../library/think/cache/driver/Wincache.php | 152 + .../library/think/cache/driver/Xcache.php | 155 + thinkphp/library/think/config/driver/Ini.php | 24 + thinkphp/library/think/config/driver/Json.php | 24 + thinkphp/library/think/config/driver/Xml.php | 31 + thinkphp/library/think/console/Command.php | 470 + thinkphp/library/think/console/Input.php | 464 + thinkphp/library/think/console/LICENSE | 19 + thinkphp/library/think/console/Output.php | 222 + thinkphp/library/think/console/bin/README.md | 1 + .../library/think/console/bin/hiddeninput.exe | Bin 0 -> 9216 bytes .../library/think/console/command/Build.php | 56 + .../library/think/console/command/Clear.php | 63 + .../library/think/console/command/Help.php | 69 + .../library/think/console/command/Lists.php | 74 + .../library/think/console/command/Make.php | 110 + .../think/console/command/make/Controller.php | 50 + .../think/console/command/make/Model.php | 36 + .../command/make/stubs/controller.plain.stub | 10 + .../command/make/stubs/controller.stub | 85 + .../console/command/make/stubs/model.stub | 10 + .../console/command/optimize/Autoload.php | 294 + .../think/console/command/optimize/Config.php | 93 + .../think/console/command/optimize/Route.php | 75 + .../think/console/command/optimize/Schema.php | 118 + .../library/think/console/input/Argument.php | 115 + .../think/console/input/Definition.php | 375 + .../library/think/console/input/Option.php | 190 + thinkphp/library/think/console/output/Ask.php | 340 + .../think/console/output/Descriptor.php | 319 + .../think/console/output/Formatter.php | 198 + .../library/think/console/output/Question.php | 211 + .../console/output/descriptor/Console.php | 149 + .../think/console/output/driver/Buffer.php | 52 + .../think/console/output/driver/Console.php | 373 + .../think/console/output/driver/Nothing.php | 33 + .../think/console/output/formatter/Stack.php | 116 + .../think/console/output/formatter/Style.php | 189 + .../think/console/output/question/Choice.php | 163 + .../console/output/question/Confirmation.php | 57 + thinkphp/library/think/controller/Rest.php | 99 + thinkphp/library/think/controller/Yar.php | 51 + thinkphp/library/think/db/Builder.php | 905 + thinkphp/library/think/db/Connection.php | 1059 + thinkphp/library/think/db/Expression.php | 48 + thinkphp/library/think/db/Query.php | 3045 ++ thinkphp/library/think/db/builder/Mysql.php | 137 + thinkphp/library/think/db/builder/Pgsql.php | 89 + thinkphp/library/think/db/builder/Sqlite.php | 82 + thinkphp/library/think/db/builder/Sqlsrv.php | 137 + thinkphp/library/think/db/connector/Mysql.php | 126 + thinkphp/library/think/db/connector/Pgsql.php | 103 + .../library/think/db/connector/Sqlite.php | 104 + .../library/think/db/connector/Sqlsrv.php | 125 + thinkphp/library/think/db/connector/pgsql.sql | 117 + .../think/db/exception/BindParamException.php | 35 + .../db/exception/DataNotFoundException.php | 43 + .../db/exception/ModelNotFoundException.php | 43 + thinkphp/library/think/debug/Console.php | 160 + thinkphp/library/think/debug/Html.php | 111 + .../exception/ClassNotFoundException.php | 32 + .../library/think/exception/DbException.php | 43 + .../think/exception/ErrorException.php | 57 + thinkphp/library/think/exception/Handle.php | 282 + .../library/think/exception/HttpException.php | 36 + .../think/exception/HttpResponseException.php | 33 + .../library/think/exception/PDOException.php | 39 + .../exception/RouteNotFoundException.php | 22 + .../exception/TemplateNotFoundException.php | 33 + .../think/exception/ThrowableError.php | 47 + .../think/exception/ValidateException.php | 33 + thinkphp/library/think/log/driver/File.php | 272 + thinkphp/library/think/log/driver/Socket.php | 250 + thinkphp/library/think/log/driver/Test.php | 30 + thinkphp/library/think/model/Collection.php | 79 + thinkphp/library/think/model/Merge.php | 322 + thinkphp/library/think/model/Pivot.php | 42 + thinkphp/library/think/model/Relation.php | 155 + .../think/model/relation/BelongsTo.php | 243 + .../think/model/relation/BelongsToMany.php | 644 + .../library/think/model/relation/HasMany.php | 318 + .../think/model/relation/HasManyThrough.php | 157 + .../library/think/model/relation/HasOne.php | 215 + .../think/model/relation/MorphMany.php | 314 + .../library/think/model/relation/MorphOne.php | 263 + .../library/think/model/relation/MorphTo.php | 299 + .../library/think/model/relation/OneToOne.php | 337 + .../think/paginator/driver/Bootstrap.php | 205 + thinkphp/library/think/process/Builder.php | 233 + thinkphp/library/think/process/Utils.php | 75 + .../think/process/exception/Failed.php | 42 + .../think/process/exception/Timeout.php | 61 + .../library/think/process/pipes/Pipes.php | 93 + thinkphp/library/think/process/pipes/Unix.php | 196 + .../library/think/process/pipes/Windows.php | 228 + thinkphp/library/think/response/Json.php | 51 + thinkphp/library/think/response/Jsonp.php | 58 + thinkphp/library/think/response/Redirect.php | 105 + thinkphp/library/think/response/View.php | 89 + thinkphp/library/think/response/Xml.php | 102 + .../library/think/session/driver/Memcache.php | 118 + .../think/session/driver/Memcached.php | 126 + .../library/think/session/driver/Redis.php | 128 + thinkphp/library/think/template/TagLib.php | 334 + .../library/think/template/driver/File.php | 74 + thinkphp/library/think/template/taglib/Cx.php | 673 + thinkphp/library/think/view/driver/Php.php | 160 + thinkphp/library/think/view/driver/Think.php | 167 + thinkphp/library/traits/controller/Jump.php | 167 + thinkphp/library/traits/model/SoftDelete.php | 200 + thinkphp/library/traits/think/Instance.php | 54 + thinkphp/logo.png | Bin 0 -> 6995 bytes thinkphp/phpunit.xml | 35 + thinkphp/start.php | 19 + thinkphp/tests/.gitignore | 4 + thinkphp/tests/README.md | 132 + thinkphp/tests/application/config.php | 33 + thinkphp/tests/application/database.php | 44 + .../application/index/controller/Index.php | 10 + thinkphp/tests/application/route.php | 22 + thinkphp/tests/application/views/display.html | 1 + .../tests/application/views/display.phtml | 1 + thinkphp/tests/application/views/extend.html | 2 + thinkphp/tests/application/views/extend2.html | 17 + thinkphp/tests/application/views/include.html | 2 + .../tests/application/views/include2.html | 1 + thinkphp/tests/application/views/layout.html | 2 + thinkphp/tests/application/views/layout2.html | 2 + thinkphp/tests/conf/memcached.ini | 1 + thinkphp/tests/conf/redis.ini | 1 + thinkphp/tests/conf/timezone.ini | 1 + thinkphp/tests/mock.php | 20 + thinkphp/tests/script/install.sh | 13 + thinkphp/tests/thinkphp/baseTest.php | 39 + .../tests/thinkphp/library/think/appTest.php | 90 + .../thinkphp/library/think/behavior/One.php | 15 + .../thinkphp/library/think/behavior/Three.php | 9 + .../thinkphp/library/think/behavior/Two.php | 9 + .../thinkphp/library/think/buildTest.php | 73 + .../think/cache/driver/cacheTestCase.php | 207 + .../library/think/cache/driver/fileTest.php | 46 + .../library/think/cache/driver/liteTest.php | 69 + .../think/cache/driver/memcacheTest.php | 49 + .../think/cache/driver/memcachedTest.php | 72 + .../library/think/cache/driver/redisTest.php | 66 + .../thinkphp/library/think/cacheTest.php | 315 + .../library/think/config/ConfigInitTrait.php | 52 + .../think/config/driver/fixtures/config.ini | 1 + .../think/config/driver/fixtures/config.json | 1 + .../think/config/driver/fixtures/config.xml | 6 + .../library/think/config/driver/iniTest.php | 36 + .../library/think/config/driver/jsonTest.php | 36 + .../library/think/config/driver/xmlTest.php | 36 + .../thinkphp/library/think/configTest.php | 149 + .../library/think/controller/.gitignore | 2 + .../thinkphp/library/think/controllerTest.php | 194 + .../thinkphp/library/think/cookieTest.php | 150 + .../library/think/db/driver/.gitignore | 2 + .../tests/thinkphp/library/think/dbTest.php | 352 + .../thinkphp/library/think/debugTest.php | 220 + .../thinkphp/library/think/exceptionTest.php | 52 + .../tests/thinkphp/library/think/hookTest.php | 67 + .../thinkphp/library/think/lang/lang.php | 4 + .../tests/thinkphp/library/think/langTest.php | 76 + .../library/think/loader/test/Hello.php | 7 + .../thinkphp/library/think/loaderTest.php | 71 + .../library/think/log/driver/fileTest.php | 34 + .../tests/thinkphp/library/think/logTest.php | 39 + .../thinkphp/library/think/model/.gitignore | 2 + .../thinkphp/library/think/paginateTest.php | 40 + .../thinkphp/library/think/requestTest.php | 203 + .../thinkphp/library/think/responseTest.php | 95 + .../thinkphp/library/think/routeTest.php | 287 + .../thinkphp/library/think/session/.gitignore | 2 + .../thinkphp/library/think/sessionTest.php | 319 + .../library/think/template/driver/.gitignore | 2 + .../library/think/template/taglib/cxTest.php | 575 + .../thinkphp/library/think/templateTest.php | 415 + .../tests/thinkphp/library/think/urlTest.php | 129 + .../thinkphp/library/think/validateTest.php | 200 + .../library/think/view/driver/.gitignore | 2 + .../think/view/theme/index/template.html | 14 + .../tests/thinkphp/library/think/viewTest.php | 76 + .../library/traits/controller/jumpTest.php | 339 + .../library/traits/model/softDeleteTest.php | 179 + .../library/traits/think/instanceTest.php | 60 + thinkphp/tpl/default_index.tpl | 10 + thinkphp/tpl/dispatch_jump.tpl | 49 + thinkphp/tpl/page_trace.tpl | 71 + thinkphp/tpl/think_exception.tpl | 537 + vendor/autoload.php | 7 + vendor/composer/ClassLoader.php | 415 + vendor/composer/LICENSE | 21 + vendor/composer/autoload_classmap.php | 16 + vendor/composer/autoload_files.php | 83 + vendor/composer/autoload_namespaces.php | 10 + vendor/composer/autoload_psr4.php | 47 + vendor/composer/autoload_real.php | 70 + vendor/composer/autoload_static.php | 341 + vendor/composer/installed.json | 2744 ++ .../easywechat-composer/.gitignore | 5 + .../easywechat-composer/.php_cs | 29 + .../easywechat-composer/.travis.yml | 12 + .../easywechat-composer/LICENSE | 21 + .../easywechat-composer/README.md | 55 + .../easywechat-composer/composer.json | 35 + .../easywechat-composer/extensions.php | 2 + .../easywechat-composer/phpunit.xml | 20 + .../src/Commands/ExtensionsCommand.php | 63 + .../src/Commands/Provider.php | 31 + .../src/Contracts/Encrypter.php | 35 + .../src/Delegation/DelegationOptions.php | 80 + .../src/Delegation/DelegationTo.php | 83 + .../src/Delegation/Hydrate.php | 83 + .../easywechat-composer/src/EasyWeChat.php | 79 + .../src/Encryption/DefaultEncrypter.php | 89 + .../src/Exceptions/DecryptException.php | 21 + .../src/Exceptions/DelegationException.php | 42 + .../src/Exceptions/EncryptException.php | 21 + .../easywechat-composer/src/Extension.php | 143 + .../src/Http/DelegationResponse.php | 25 + .../easywechat-composer/src/Http/Response.php | 104 + .../Http/Controllers/DelegatesController.php | 49 + .../src/Laravel/ServiceProvider.php | 116 + .../src/Laravel/config.php | 29 + .../src/Laravel/routes.php | 16 + .../src/ManifestManager.php | 127 + .../easywechat-composer/src/Plugin.php | 107 + .../src/Traits/MakesHttpRequests.php | 110 + .../src/Traits/WithAggregator.php | 60 + .../tests/ManifestManagerTest.php | 37 + vendor/guzzlehttp/guzzle/.php_cs | 23 + vendor/guzzlehttp/guzzle/CHANGELOG.md | 1342 + vendor/guzzlehttp/guzzle/Dockerfile | 18 + vendor/guzzlehttp/guzzle/LICENSE | 27 + vendor/guzzlehttp/guzzle/README.md | 93 + vendor/guzzlehttp/guzzle/UPGRADING.md | 1203 + vendor/guzzlehttp/guzzle/composer.json | 89 + vendor/guzzlehttp/guzzle/src/Client.php | 501 + .../guzzlehttp/guzzle/src/ClientInterface.php | 87 + .../guzzle/src/Cookie/CookieJar.php | 321 + .../guzzle/src/Cookie/CookieJarInterface.php | 84 + .../guzzle/src/Cookie/FileCookieJar.php | 91 + .../guzzle/src/Cookie/SessionCookieJar.php | 72 + .../guzzle/src/Cookie/SetCookie.php | 410 + .../src/Exception/BadResponseException.php | 27 + .../guzzle/src/Exception/ClientException.php | 9 + .../guzzle/src/Exception/ConnectException.php | 37 + .../guzzle/src/Exception/GuzzleException.php | 23 + .../Exception/InvalidArgumentException.php | 7 + .../guzzle/src/Exception/RequestException.php | 192 + .../guzzle/src/Exception/SeekException.php | 27 + .../guzzle/src/Exception/ServerException.php | 9 + .../Exception/TooManyRedirectsException.php | 6 + .../src/Exception/TransferException.php | 6 + .../guzzle/src/Handler/CurlFactory.php | 585 + .../src/Handler/CurlFactoryInterface.php | 27 + .../guzzle/src/Handler/CurlHandler.php | 45 + .../guzzle/src/Handler/CurlMultiHandler.php | 219 + .../guzzle/src/Handler/EasyHandle.php | 92 + .../guzzle/src/Handler/MockHandler.php | 195 + .../guzzlehttp/guzzle/src/Handler/Proxy.php | 55 + .../guzzle/src/Handler/StreamHandler.php | 545 + vendor/guzzlehttp/guzzle/src/HandlerStack.php | 277 + .../guzzle/src/MessageFormatter.php | 185 + vendor/guzzlehttp/guzzle/src/Middleware.php | 254 + vendor/guzzlehttp/guzzle/src/Pool.php | 134 + .../guzzle/src/PrepareBodyMiddleware.php | 111 + .../guzzle/src/RedirectMiddleware.php | 255 + .../guzzlehttp/guzzle/src/RequestOptions.php | 263 + .../guzzlehttp/guzzle/src/RetryMiddleware.php | 128 + .../guzzlehttp/guzzle/src/TransferStats.php | 126 + vendor/guzzlehttp/guzzle/src/UriTemplate.php | 237 + vendor/guzzlehttp/guzzle/src/Utils.php | 92 + vendor/guzzlehttp/guzzle/src/functions.php | 334 + .../guzzle/src/functions_include.php | 6 + vendor/guzzlehttp/promises/CHANGELOG.md | 103 + vendor/guzzlehttp/promises/LICENSE | 24 + vendor/guzzlehttp/promises/Makefile | 13 + vendor/guzzlehttp/promises/README.md | 547 + vendor/guzzlehttp/promises/composer.json | 58 + .../promises/src/AggregateException.php | 17 + .../promises/src/CancellationException.php | 10 + vendor/guzzlehttp/promises/src/Coroutine.php | 169 + vendor/guzzlehttp/promises/src/Create.php | 84 + vendor/guzzlehttp/promises/src/Each.php | 90 + .../guzzlehttp/promises/src/EachPromise.php | 255 + .../promises/src/FulfilledPromise.php | 84 + vendor/guzzlehttp/promises/src/Is.php | 46 + vendor/guzzlehttp/promises/src/Promise.php | 278 + .../promises/src/PromiseInterface.php | 97 + .../promises/src/PromisorInterface.php | 16 + .../promises/src/RejectedPromise.php | 91 + .../promises/src/RejectionException.php | 48 + vendor/guzzlehttp/promises/src/TaskQueue.php | 67 + .../promises/src/TaskQueueInterface.php | 24 + vendor/guzzlehttp/promises/src/Utils.php | 276 + vendor/guzzlehttp/promises/src/functions.php | 363 + .../promises/src/functions_include.php | 6 + vendor/guzzlehttp/psr7/.github/FUNDING.yml | 2 + vendor/guzzlehttp/psr7/.github/stale.yml | 14 + .../guzzlehttp/psr7/.github/workflows/ci.yml | 34 + .../psr7/.github/workflows/integration.yml | 37 + .../psr7/.github/workflows/static.yml | 29 + vendor/guzzlehttp/psr7/.php_cs.dist | 56 + vendor/guzzlehttp/psr7/CHANGELOG.md | 312 + vendor/guzzlehttp/psr7/LICENSE | 26 + vendor/guzzlehttp/psr7/README.md | 824 + vendor/guzzlehttp/psr7/composer.json | 76 + vendor/guzzlehttp/psr7/src/AppendStream.php | 246 + vendor/guzzlehttp/psr7/src/BufferStream.php | 142 + vendor/guzzlehttp/psr7/src/CachingStream.php | 147 + vendor/guzzlehttp/psr7/src/DroppingStream.php | 45 + vendor/guzzlehttp/psr7/src/FnStream.php | 163 + vendor/guzzlehttp/psr7/src/Header.php | 71 + vendor/guzzlehttp/psr7/src/InflateStream.php | 56 + vendor/guzzlehttp/psr7/src/LazyOpenStream.php | 42 + vendor/guzzlehttp/psr7/src/LimitStream.php | 157 + vendor/guzzlehttp/psr7/src/Message.php | 252 + vendor/guzzlehttp/psr7/src/MessageTrait.php | 270 + vendor/guzzlehttp/psr7/src/MimeType.php | 140 + .../guzzlehttp/psr7/src/MultipartStream.php | 158 + vendor/guzzlehttp/psr7/src/NoSeekStream.php | 25 + vendor/guzzlehttp/psr7/src/PumpStream.php | 170 + vendor/guzzlehttp/psr7/src/Query.php | 113 + vendor/guzzlehttp/psr7/src/Request.php | 152 + vendor/guzzlehttp/psr7/src/Response.php | 155 + vendor/guzzlehttp/psr7/src/Rfc7230.php | 19 + vendor/guzzlehttp/psr7/src/ServerRequest.php | 379 + vendor/guzzlehttp/psr7/src/Stream.php | 270 + .../psr7/src/StreamDecoratorTrait.php | 152 + vendor/guzzlehttp/psr7/src/StreamWrapper.php | 165 + vendor/guzzlehttp/psr7/src/UploadedFile.php | 328 + vendor/guzzlehttp/psr7/src/Uri.php | 810 + vendor/guzzlehttp/psr7/src/UriNormalizer.php | 219 + vendor/guzzlehttp/psr7/src/UriResolver.php | 222 + vendor/guzzlehttp/psr7/src/Utils.php | 428 + vendor/guzzlehttp/psr7/src/functions.php | 422 + .../guzzlehttp/psr7/src/functions_include.php | 6 + .../karsonzhang/fastadmin-addons/.gitignore | 2 + .../karsonzhang/fastadmin-addons/LICENSE.txt | 32 + vendor/karsonzhang/fastadmin-addons/README.md | 8 + .../fastadmin-addons/composer.json | 38 + .../fastadmin-addons/src/Addons.php | 286 + .../src/addons/AddonException.php | 21 + .../src/addons/Controller.php | 210 + .../fastadmin-addons/src/addons/Route.php | 84 + .../fastadmin-addons/src/addons/Service.php | 1150 + .../fastadmin-addons/src/common.php | 520 + .../fastadmin-addons/src/config.php | 5 + vendor/markbaker/complex/README.md | 156 + .../markbaker/complex/classes/Autoloader.php | 53 + .../markbaker/complex/classes/Bootstrap.php | 38 + .../markbaker/complex/classes/src/Complex.php | 390 + .../complex/classes/src/Exception.php | 13 + .../complex/classes/src/functions/abs.php | 29 + .../complex/classes/src/functions/acos.php | 38 + .../complex/classes/src/functions/acosh.php | 34 + .../complex/classes/src/functions/acot.php | 25 + .../complex/classes/src/functions/acoth.php | 25 + .../complex/classes/src/functions/acsc.php | 29 + .../complex/classes/src/functions/acsch.php | 29 + .../classes/src/functions/argument.php | 28 + .../complex/classes/src/functions/asec.php | 29 + .../complex/classes/src/functions/asech.php | 29 + .../complex/classes/src/functions/asin.php | 37 + .../complex/classes/src/functions/asinh.php | 33 + .../complex/classes/src/functions/atan.php | 45 + .../complex/classes/src/functions/atanh.php | 38 + .../classes/src/functions/conjugate.php | 28 + .../complex/classes/src/functions/cos.php | 34 + .../complex/classes/src/functions/cosh.php | 32 + .../complex/classes/src/functions/cot.php | 29 + .../complex/classes/src/functions/coth.php | 24 + .../complex/classes/src/functions/csc.php | 29 + .../complex/classes/src/functions/csch.php | 29 + .../complex/classes/src/functions/exp.php | 34 + .../complex/classes/src/functions/inverse.php | 29 + .../complex/classes/src/functions/ln.php | 33 + .../complex/classes/src/functions/log10.php | 32 + .../complex/classes/src/functions/log2.php | 32 + .../classes/src/functions/negative.php | 31 + .../complex/classes/src/functions/pow.php | 40 + .../complex/classes/src/functions/rho.php | 28 + .../complex/classes/src/functions/sec.php | 25 + .../complex/classes/src/functions/sech.php | 25 + .../complex/classes/src/functions/sin.php | 32 + .../complex/classes/src/functions/sinh.php | 32 + .../complex/classes/src/functions/sqrt.php | 29 + .../complex/classes/src/functions/tan.php | 40 + .../complex/classes/src/functions/tanh.php | 35 + .../complex/classes/src/functions/theta.php | 38 + .../complex/classes/src/operations/add.php | 46 + .../classes/src/operations/divideby.php | 56 + .../classes/src/operations/divideinto.php | 56 + .../classes/src/operations/multiply.php | 48 + .../classes/src/operations/subtract.php | 46 + vendor/markbaker/complex/composer.json | 94 + .../complex/examples/complexTest.php | 154 + .../complex/examples/testFunctions.php | 52 + .../complex/examples/testOperations.php | 34 + vendor/markbaker/complex/license.md | 25 + .../matrix/.github/workflows/main.yaml | 112 + vendor/markbaker/matrix/README.md | 207 + vendor/markbaker/matrix/buildPhar.php | 62 + .../markbaker/matrix/classes/src/Builder.php | 70 + .../src/Decomposition/Decomposition.php | 27 + .../matrix/classes/src/Decomposition/LU.php | 260 + .../matrix/classes/src/Decomposition/QR.php | 194 + .../matrix/classes/src/Exception.php | 13 + .../matrix/classes/src/Functions.php | 337 + .../matrix/classes/src/Functions/adjoint.php | 30 + .../classes/src/Functions/antidiagonal.php | 29 + .../classes/src/Functions/cofactors.php | 30 + .../classes/src/Functions/determinant.php | 30 + .../matrix/classes/src/Functions/diagonal.php | 30 + .../matrix/classes/src/Functions/identity.php | 30 + .../matrix/classes/src/Functions/inverse.php | 30 + .../matrix/classes/src/Functions/minors.php | 30 + .../matrix/classes/src/Functions/trace.php | 30 + .../classes/src/Functions/transpose.php | 30 + .../markbaker/matrix/classes/src/Matrix.php | 423 + .../matrix/classes/src/Operations/add.php | 44 + .../classes/src/Operations/directsum.php | 44 + .../classes/src/Operations/divideby.php | 44 + .../classes/src/Operations/divideinto.php | 45 + .../classes/src/Operations/multiply.php | 44 + .../classes/src/Operations/subtract.php | 44 + .../matrix/classes/src/Operators/Addition.php | 68 + .../classes/src/Operators/DirectSum.php | 64 + .../matrix/classes/src/Operators/Division.php | 38 + .../classes/src/Operators/Multiplication.php | 77 + .../matrix/classes/src/Operators/Operator.php | 78 + .../classes/src/Operators/Subtraction.php | 68 + vendor/markbaker/matrix/composer.json | 82 + vendor/markbaker/matrix/examples/test.php | 19 + vendor/markbaker/matrix/infection.json.dist | 17 + vendor/markbaker/matrix/license.md | 25 + vendor/markbaker/matrix/phpstan.neon | 5 + vendor/monolog/monolog/CHANGELOG.md | 427 + vendor/monolog/monolog/LICENSE | 19 + vendor/monolog/monolog/README.md | 94 + vendor/monolog/monolog/composer.json | 60 + vendor/monolog/monolog/phpstan.neon.dist | 16 + .../monolog/src/Monolog/ErrorHandler.php | 239 + .../Monolog/Formatter/ChromePHPFormatter.php | 78 + .../Monolog/Formatter/ElasticaFormatter.php | 89 + .../Monolog/Formatter/FlowdockFormatter.php | 116 + .../Monolog/Formatter/FluentdFormatter.php | 88 + .../Monolog/Formatter/FormatterInterface.php | 36 + .../Formatter/GelfMessageFormatter.php | 138 + .../src/Monolog/Formatter/HtmlFormatter.php | 142 + .../src/Monolog/Formatter/JsonFormatter.php | 214 + .../src/Monolog/Formatter/LineFormatter.php | 181 + .../src/Monolog/Formatter/LogglyFormatter.php | 47 + .../Monolog/Formatter/LogstashFormatter.php | 166 + .../Monolog/Formatter/MongoDBFormatter.php | 107 + .../Monolog/Formatter/NormalizerFormatter.php | 199 + .../src/Monolog/Formatter/ScalarFormatter.php | 48 + .../Monolog/Formatter/WildfireFormatter.php | 113 + .../src/Monolog/Handler/AbstractHandler.php | 196 + .../Handler/AbstractProcessingHandler.php | 68 + .../Monolog/Handler/AbstractSyslogHandler.php | 101 + .../src/Monolog/Handler/AmqpHandler.php | 148 + .../Monolog/Handler/BrowserConsoleHandler.php | 241 + .../src/Monolog/Handler/BufferHandler.php | 148 + .../src/Monolog/Handler/ChromePHPHandler.php | 212 + .../src/Monolog/Handler/CouchDBHandler.php | 72 + .../src/Monolog/Handler/CubeHandler.php | 152 + .../monolog/src/Monolog/Handler/Curl/Util.php | 57 + .../Monolog/Handler/DeduplicationHandler.php | 169 + .../Handler/DoctrineCouchDBHandler.php | 45 + .../src/Monolog/Handler/DynamoDbHandler.php | 108 + .../Monolog/Handler/ElasticSearchHandler.php | 128 + .../src/Monolog/Handler/ErrorLogHandler.php | 82 + .../src/Monolog/Handler/FilterHandler.php | 172 + .../ActivationStrategyInterface.php | 28 + .../ChannelLevelActivationStrategy.php | 59 + .../ErrorLevelActivationStrategy.php | 34 + .../Monolog/Handler/FingersCrossedHandler.php | 207 + .../src/Monolog/Handler/FirePHPHandler.php | 195 + .../src/Monolog/Handler/FleepHookHandler.php | 126 + .../src/Monolog/Handler/FlowdockHandler.php | 128 + .../Handler/FormattableHandlerInterface.php | 39 + .../Handler/FormattableHandlerTrait.php | 63 + .../src/Monolog/Handler/GelfHandler.php | 65 + .../src/Monolog/Handler/GroupHandler.php | 117 + .../src/Monolog/Handler/HandlerInterface.php | 90 + .../src/Monolog/Handler/HandlerWrapper.php | 116 + .../src/Monolog/Handler/HipChatHandler.php | 367 + .../src/Monolog/Handler/IFTTTHandler.php | 70 + .../src/Monolog/Handler/InsightOpsHandler.php | 62 + .../src/Monolog/Handler/LogEntriesHandler.php | 55 + .../src/Monolog/Handler/LogglyHandler.php | 102 + .../src/Monolog/Handler/MailHandler.php | 67 + .../src/Monolog/Handler/MandrillHandler.php | 68 + .../Handler/MissingExtensionException.php | 21 + .../src/Monolog/Handler/MongoDBHandler.php | 59 + .../Monolog/Handler/NativeMailerHandler.php | 185 + .../src/Monolog/Handler/NewRelicHandler.php | 205 + .../src/Monolog/Handler/NullHandler.php | 45 + .../src/Monolog/Handler/PHPConsoleHandler.php | 243 + .../Handler/ProcessableHandlerInterface.php | 40 + .../Handler/ProcessableHandlerTrait.php | 73 + .../src/Monolog/Handler/PsrHandler.php | 56 + .../src/Monolog/Handler/PushoverHandler.php | 185 + .../src/Monolog/Handler/RavenHandler.php | 234 + .../src/Monolog/Handler/RedisHandler.php | 98 + .../src/Monolog/Handler/RollbarHandler.php | 144 + .../Monolog/Handler/RotatingFileHandler.php | 191 + .../src/Monolog/Handler/SamplingHandler.php | 113 + .../src/Monolog/Handler/Slack/SlackRecord.php | 299 + .../src/Monolog/Handler/SlackHandler.php | 221 + .../Monolog/Handler/SlackWebhookHandler.php | 121 + .../src/Monolog/Handler/SlackbotHandler.php | 84 + .../src/Monolog/Handler/SocketHandler.php | 385 + .../src/Monolog/Handler/StreamHandler.php | 179 + .../Monolog/Handler/SwiftMailerHandler.php | 111 + .../src/Monolog/Handler/SyslogHandler.php | 67 + .../Monolog/Handler/SyslogUdp/UdpSocket.php | 56 + .../src/Monolog/Handler/SyslogUdpHandler.php | 124 + .../src/Monolog/Handler/TestHandler.php | 177 + .../Handler/WhatFailureGroupHandler.php | 72 + .../Monolog/Handler/ZendMonitorHandler.php | 101 + vendor/monolog/monolog/src/Monolog/Logger.php | 796 + .../src/Monolog/Processor/GitProcessor.php | 64 + .../Processor/IntrospectionProcessor.php | 112 + .../Processor/MemoryPeakUsageProcessor.php | 35 + .../src/Monolog/Processor/MemoryProcessor.php | 63 + .../Processor/MemoryUsageProcessor.php | 35 + .../Monolog/Processor/MercurialProcessor.php | 63 + .../Monolog/Processor/ProcessIdProcessor.php | 31 + .../Monolog/Processor/ProcessorInterface.php | 25 + .../Processor/PsrLogMessageProcessor.php | 81 + .../src/Monolog/Processor/TagProcessor.php | 44 + .../src/Monolog/Processor/UidProcessor.php | 59 + .../src/Monolog/Processor/WebProcessor.php | 113 + .../monolog/monolog/src/Monolog/Registry.php | 134 + .../src/Monolog/ResettableInterface.php | 31 + .../monolog/src/Monolog/SignalHandler.php | 115 + vendor/monolog/monolog/src/Monolog/Utils.php | 189 + vendor/nelexa/zip/.phpstorm.meta.php | 128 + vendor/nelexa/zip/LICENSE | 21 + vendor/nelexa/zip/README.RU.md | 816 + vendor/nelexa/zip/README.md | 832 + vendor/nelexa/zip/composer.json | 60 + vendor/nelexa/zip/src/Constants/DosAttrs.php | 33 + .../nelexa/zip/src/Constants/DosCodePage.php | 105 + .../src/Constants/GeneralPurposeBitFlag.php | 71 + vendor/nelexa/zip/src/Constants/UnixStat.php | 84 + .../zip/src/Constants/ZipCompressionLevel.php | 54 + .../src/Constants/ZipCompressionMethod.php | 102 + .../nelexa/zip/src/Constants/ZipConstants.php | 99 + .../zip/src/Constants/ZipEncryptionMethod.php | 93 + .../nelexa/zip/src/Constants/ZipOptions.php | 62 + .../nelexa/zip/src/Constants/ZipPlatform.php | 53 + .../nelexa/zip/src/Constants/ZipVersion.php | 81 + .../zip/src/Exception/Crc32Exception.php | 71 + .../Exception/InvalidArgumentException.php | 14 + .../zip/src/Exception/RuntimeException.php | 14 + .../Exception/ZipAuthenticationException.php | 13 + .../zip/src/Exception/ZipCryptoException.php | 14 + .../Exception/ZipEntryNotFoundException.php | 40 + .../nelexa/zip/src/Exception/ZipException.php | 15 + .../Exception/ZipUnsupportMethodException.php | 10 + .../Filter/Cipher/Pkware/PKCryptContext.php | 419 + .../Pkware/PKDecryptionStreamFilter.php | 118 + .../Pkware/PKEncryptionStreamFilter.php | 128 + .../Cipher/WinZipAes/WinZipAesContext.php | 166 + .../WinZipAesDecryptionStreamFilter.php | 187 + .../WinZipAesEncryptionStreamFilter.php | 158 + .../zip/src/IO/Stream/ResponseStream.php | 338 + .../src/IO/Stream/ZipEntryStreamWrapper.php | 309 + vendor/nelexa/zip/src/IO/ZipReader.php | 898 + vendor/nelexa/zip/src/IO/ZipWriter.php | 886 + .../nelexa/zip/src/Model/Data/ZipFileData.php | 81 + .../nelexa/zip/src/Model/Data/ZipNewData.php | 132 + .../zip/src/Model/Data/ZipSourceFileData.php | 172 + .../zip/src/Model/EndOfCentralDirectory.php | 93 + .../src/Model/Extra/ExtraFieldsCollection.php | 276 + .../Fields/AbstractUnicodeExtraField.php | 133 + .../Extra/Fields/ApkAlignmentExtraField.php | 176 + .../src/Model/Extra/Fields/AsiExtraField.php | 302 + .../Fields/ExtendedTimestampExtraField.php | 446 + .../Extra/Fields/JarMarkerExtraField.php | 118 + .../Model/Extra/Fields/NewUnixExtraField.php | 237 + .../src/Model/Extra/Fields/NtfsExtraField.php | 339 + .../Model/Extra/Fields/OldUnixExtraField.php | 327 + .../Extra/Fields/UnicodeCommentExtraField.php | 76 + .../Extra/Fields/UnicodePathExtraField.php | 77 + .../Extra/Fields/UnrecognizedExtraField.php | 116 + .../Extra/Fields/WinZipAesExtraField.php | 387 + .../Model/Extra/Fields/Zip64ExtraField.php | 311 + .../zip/src/Model/Extra/ZipExtraDriver.php | 107 + .../zip/src/Model/Extra/ZipExtraField.php | 63 + .../zip/src/Model/ImmutableZipContainer.php | 72 + vendor/nelexa/zip/src/Model/ZipContainer.php | 386 + vendor/nelexa/zip/src/Model/ZipData.php | 28 + vendor/nelexa/zip/src/Model/ZipEntry.php | 1573 + .../nelexa/zip/src/Model/ZipEntryMatcher.php | 206 + vendor/nelexa/zip/src/Model/ZipInfo.php | 266 + vendor/nelexa/zip/src/Util/CryptoUtil.php | 77 + .../nelexa/zip/src/Util/DateTimeConverter.php | 99 + vendor/nelexa/zip/src/Util/FileAttribUtil.php | 108 + vendor/nelexa/zip/src/Util/FilesUtil.php | 450 + .../Iterator/IgnoreFilesFilterIterator.php | 66 + .../IgnoreFilesRecursiveFilterIterator.php | 74 + vendor/nelexa/zip/src/Util/PackUtil.php | 69 + vendor/nelexa/zip/src/Util/StringUtil.php | 48 + vendor/nelexa/zip/src/ZipFile.php | 2004 + vendor/nelexa/zip/src/ZipFileInterface.php | 902 + vendor/overtrue/pinyin/LICENSE | 21 + vendor/overtrue/pinyin/README.md | 120 + vendor/overtrue/pinyin/composer.json | 33 + vendor/overtrue/pinyin/data/surnames | 84 + vendor/overtrue/pinyin/data/words_0 | 8003 ++++ vendor/overtrue/pinyin/data/words_1 | 8003 ++++ vendor/overtrue/pinyin/data/words_2 | 8003 ++++ vendor/overtrue/pinyin/data/words_3 | 8003 ++++ vendor/overtrue/pinyin/data/words_4 | 8003 ++++ vendor/overtrue/pinyin/data/words_5 | 2056 + .../pinyin/src/DictLoaderInterface.php | 42 + vendor/overtrue/pinyin/src/FileDictLoader.php | 76 + .../pinyin/src/GeneratorFileDictLoader.php | 142 + .../pinyin/src/MemoryFileDictLoader.php | 96 + vendor/overtrue/pinyin/src/Pinyin.php | 309 + vendor/overtrue/socialite/.github/FUNDING.yml | 9 + vendor/overtrue/socialite/.gitignore | 9 + vendor/overtrue/socialite/.php_cs | 28 + vendor/overtrue/socialite/.travis.yml | 13 + vendor/overtrue/socialite/LICENSE.txt | 21 + vendor/overtrue/socialite/README.md | 267 + vendor/overtrue/socialite/composer.json | 34 + vendor/overtrue/socialite/phpunit.xml | 18 + vendor/overtrue/socialite/src/AccessToken.php | 84 + .../socialite/src/AccessTokenInterface.php | 25 + .../src/AuthorizeFailedException.php | 35 + vendor/overtrue/socialite/src/Config.php | 180 + .../socialite/src/FactoryInterface.php | 27 + .../overtrue/socialite/src/HasAttributes.php | 135 + .../src/InvalidArgumentException.php | 16 + .../socialite/src/InvalidStateException.php | 16 + .../socialite/src/ProviderInterface.php | 31 + .../src/Providers/AbstractProvider.php | 585 + .../socialite/src/Providers/BaiduProvider.php | 134 + .../src/Providers/DouYinProvider.php | 169 + .../src/Providers/DoubanProvider.php | 88 + .../src/Providers/FacebookProvider.php | 168 + .../src/Providers/FeiShuProvider.php | 192 + .../src/Providers/GitHubProvider.php | 126 + .../src/Providers/GoogleProvider.php | 119 + .../src/Providers/LinkedinProvider.php | 181 + .../src/Providers/OutlookProvider.php | 89 + .../socialite/src/Providers/QQProvider.php | 206 + .../src/Providers/TaobaoProvider.php | 242 + .../src/Providers/WeChatProvider.php | 234 + .../src/Providers/WeWorkProvider.php | 214 + .../socialite/src/Providers/WeiboProvider.php | 126 + .../socialite/src/SocialiteManager.php | 251 + vendor/overtrue/socialite/src/User.php | 204 + .../overtrue/socialite/src/UserInterface.php | 53 + .../src/WeChatComponentInterface.php | 32 + vendor/overtrue/socialite/tests/OAuthTest.php | 243 + .../tests/Providers/WeWorkProviderTest.php | 60 + vendor/overtrue/socialite/tests/UserTest.php | 45 + .../socialite/tests/WechatProviderTest.php | 137 + vendor/overtrue/wechat/CHANGELOG.md | 1401 + vendor/overtrue/wechat/CONTRIBUTING.md | 67 + vendor/overtrue/wechat/LICENSE | 22 + vendor/overtrue/wechat/README.md | 92 + vendor/overtrue/wechat/composer.json | 59 + .../wechat/src/BasicService/Application.php | 39 + .../BasicService/ContentSecurity/Client.php | 123 + .../ContentSecurity/ServiceProvider.php | 31 + .../wechat/src/BasicService/Jssdk/Client.php | 207 + .../BasicService/Jssdk/ServiceProvider.php | 33 + .../wechat/src/BasicService/Media/Client.php | 212 + .../BasicService/Media/ServiceProvider.php | 44 + .../wechat/src/BasicService/QrCode/Client.php | 120 + .../BasicService/QrCode/ServiceProvider.php | 31 + .../wechat/src/BasicService/Url/Client.php | 47 + .../src/BasicService/Url/ServiceProvider.php | 31 + vendor/overtrue/wechat/src/Factory.php | 54 + .../wechat/src/Kernel/AccessToken.php | 282 + .../overtrue/wechat/src/Kernel/BaseClient.php | 271 + .../wechat/src/Kernel/Clauses/Clause.php | 64 + vendor/overtrue/wechat/src/Kernel/Config.php | 23 + .../Kernel/Contracts/AccessTokenInterface.php | 40 + .../wechat/src/Kernel/Contracts/Arrayable.php | 29 + .../Contracts/EventHandlerInterface.php | 25 + .../src/Kernel/Contracts/MediaInterface.php | 25 + .../src/Kernel/Contracts/MessageInterface.php | 35 + .../src/Kernel/Decorators/FinallyResult.php | 35 + .../src/Kernel/Decorators/TerminateResult.php | 35 + .../overtrue/wechat/src/Kernel/Encryptor.php | 219 + .../Kernel/Events/AccessTokenRefreshed.php | 35 + .../Kernel/Events/ApplicationInitialized.php | 35 + .../src/Kernel/Events/HttpResponseCreated.php | 35 + .../Events/ServerGuardResponseCreated.php | 35 + .../Kernel/Exceptions/BadRequestException.php | 21 + .../Kernel/Exceptions/DecryptException.php | 16 + .../src/Kernel/Exceptions/Exception.php | 23 + .../src/Kernel/Exceptions/HttpException.php | 52 + .../Exceptions/InvalidArgumentException.php | 21 + .../Exceptions/InvalidConfigException.php | 21 + .../Kernel/Exceptions/RuntimeException.php | 21 + .../Exceptions/UnboundServiceException.php | 21 + vendor/overtrue/wechat/src/Kernel/Helpers.php | 57 + .../wechat/src/Kernel/Http/Response.php | 121 + .../wechat/src/Kernel/Http/StreamResponse.php | 86 + .../wechat/src/Kernel/Log/LogManager.php | 608 + .../wechat/src/Kernel/Messages/Article.php | 58 + .../wechat/src/Kernel/Messages/Card.php | 52 + .../src/Kernel/Messages/DeviceEvent.php | 40 + .../wechat/src/Kernel/Messages/DeviceText.php | 50 + .../wechat/src/Kernel/Messages/File.php | 25 + .../wechat/src/Kernel/Messages/Image.php | 27 + .../wechat/src/Kernel/Messages/Link.php | 36 + .../wechat/src/Kernel/Messages/Location.php | 38 + .../wechat/src/Kernel/Messages/Media.php | 70 + .../wechat/src/Kernel/Messages/Message.php | 208 + .../src/Kernel/Messages/MiniProgramPage.php | 31 + .../wechat/src/Kernel/Messages/Music.php | 73 + .../wechat/src/Kernel/Messages/News.php | 73 + .../wechat/src/Kernel/Messages/NewsItem.php | 57 + .../wechat/src/Kernel/Messages/Raw.php | 56 + .../wechat/src/Kernel/Messages/ShortVideo.php | 30 + .../wechat/src/Kernel/Messages/TaskCard.php | 44 + .../wechat/src/Kernel/Messages/Text.php | 54 + .../wechat/src/Kernel/Messages/TextCard.php | 40 + .../wechat/src/Kernel/Messages/Transfer.php | 56 + .../wechat/src/Kernel/Messages/Video.php | 65 + .../wechat/src/Kernel/Messages/Voice.php | 37 + .../Providers/ConfigServiceProvider.php | 39 + .../EventDispatcherServiceProvider.php | 47 + .../Providers/ExtensionServiceProvider.php | 39 + .../Providers/HttpClientServiceProvider.php | 39 + .../Kernel/Providers/LogServiceProvider.php | 79 + .../Providers/RequestServiceProvider.php | 39 + .../wechat/src/Kernel/ServerGuard.php | 375 + .../wechat/src/Kernel/ServiceContainer.php | 167 + .../wechat/src/Kernel/Support/AES.php | 85 + .../wechat/src/Kernel/Support/Arr.php | 466 + .../src/Kernel/Support/ArrayAccessible.php | 66 + .../wechat/src/Kernel/Support/Collection.php | 421 + .../wechat/src/Kernel/Support/File.php | 135 + .../wechat/src/Kernel/Support/Helpers.php | 131 + .../wechat/src/Kernel/Support/Str.php | 193 + .../wechat/src/Kernel/Support/XML.php | 167 + .../src/Kernel/Traits/HasAttributes.php | 251 + .../src/Kernel/Traits/HasHttpRequests.php | 231 + .../src/Kernel/Traits/InteractsWithCache.php | 105 + .../wechat/src/Kernel/Traits/Observable.php | 273 + .../src/Kernel/Traits/ResponseCastable.php | 93 + .../wechat/src/MicroMerchant/Application.php | 171 + .../wechat/src/MicroMerchant/Base/Client.php | 126 + .../MicroMerchant/Base/ServiceProvider.php | 33 + .../src/MicroMerchant/Certficates/Client.php | 93 + .../Certficates/ServiceProvider.php | 33 + .../src/MicroMerchant/Kernel/BaseClient.php | 256 + .../Kernel/Exceptions/EncryptException.php | 23 + .../Exceptions/InvalidExtensionException.php | 23 + .../Exceptions/InvalidSignException.php | 23 + .../src/MicroMerchant/Material/Client.php | 73 + .../Material/ServiceProvider.php | 33 + .../wechat/src/MicroMerchant/Media/Client.php | 49 + .../MicroMerchant/Media/ServiceProvider.php | 44 + .../MicroMerchant/MerchantConfig/Client.php | 134 + .../MerchantConfig/ServiceProvider.php | 33 + .../src/MicroMerchant/Withdraw/Client.php | 67 + .../Withdraw/ServiceProvider.php | 33 + .../MiniProgram/ActivityMessage/Client.php | 85 + .../ActivityMessage/ServiceProvider.php | 28 + .../wechat/src/MiniProgram/AppCode/Client.php | 92 + .../MiniProgram/AppCode/ServiceProvider.php | 33 + .../wechat/src/MiniProgram/Application.php | 83 + .../src/MiniProgram/Auth/AccessToken.php | 39 + .../wechat/src/MiniProgram/Auth/Client.php | 43 + .../src/MiniProgram/Auth/ServiceProvider.php | 32 + .../wechat/src/MiniProgram/Base/Client.php | 38 + .../src/MiniProgram/Base/ServiceProvider.php | 33 + .../CustomerService/ServiceProvider.php | 34 + .../src/MiniProgram/DataCube/Client.php | 174 + .../MiniProgram/DataCube/ServiceProvider.php | 28 + .../wechat/src/MiniProgram/Encryptor.php | 50 + .../wechat/src/MiniProgram/Express/Client.php | 133 + .../MiniProgram/Express/ServiceProvider.php | 33 + .../src/MiniProgram/Mall/CartClient.php | 88 + .../src/MiniProgram/Mall/ForwardsMall.php | 48 + .../src/MiniProgram/Mall/MediaClient.php | 37 + .../src/MiniProgram/Mall/OrderClient.php | 75 + .../src/MiniProgram/Mall/ProductClient.php | 68 + .../src/MiniProgram/Mall/ServiceProvider.php | 44 + .../src/MiniProgram/NearbyPoi/Client.php | 123 + .../MiniProgram/NearbyPoi/ServiceProvider.php | 33 + .../src/MiniProgram/OCR/ServiceProvider.php | 34 + .../src/MiniProgram/OpenData/Client.php | 96 + .../MiniProgram/OpenData/ServiceProvider.php | 28 + .../wechat/src/MiniProgram/Plugin/Client.php | 67 + .../src/MiniProgram/Plugin/DevClient.php | 93 + .../MiniProgram/Plugin/ServiceProvider.php | 42 + .../MiniProgram/Server/ServiceProvider.php | 42 + .../wechat/src/MiniProgram/Soter/Client.php | 41 + .../src/MiniProgram/Soter/ServiceProvider.php | 33 + .../MiniProgram/SubscribeMessage/Client.php | 208 + .../SubscribeMessage/ServiceProvider.php | 28 + .../MiniProgram/TemplateMessage/Client.php | 114 + .../TemplateMessage/ServiceProvider.php | 28 + .../src/MiniProgram/UniformMessage/Client.php | 146 + .../UniformMessage/ServiceProvider.php | 28 + .../src/OfficialAccount/Application.php | 88 + .../src/OfficialAccount/Auth/AccessToken.php | 39 + .../OfficialAccount/Auth/ServiceProvider.php | 33 + .../src/OfficialAccount/AutoReply/Client.php | 34 + .../AutoReply/ServiceProvider.php | 33 + .../src/OfficialAccount/Base/Client.php | 84 + .../OfficialAccount/Base/ServiceProvider.php | 33 + .../OfficialAccount/Broadcasting/Client.php | 383 + .../Broadcasting/MessageBuilder.php | 162 + .../Broadcasting/ServiceProvider.php | 33 + .../Card/BoardingPassClient.php | 33 + .../wechat/src/OfficialAccount/Card/Card.php | 52 + .../src/OfficialAccount/Card/Client.php | 428 + .../src/OfficialAccount/Card/CodeClient.php | 193 + .../src/OfficialAccount/Card/CoinClient.php | 119 + .../Card/GeneralCardClient.php | 71 + .../OfficialAccount/Card/GiftCardClient.php | 74 + .../Card/GiftCardOrderClient.php | 78 + .../Card/GiftCardPageClient.php | 102 + .../OfficialAccount/Card/InvoiceClient.php | 113 + .../src/OfficialAccount/Card/JssdkClient.php | 85 + .../Card/MeetingTicketClient.php | 33 + .../OfficialAccount/Card/MemberCardClient.php | 123 + .../Card/MovieTicketClient.php | 33 + .../OfficialAccount/Card/ServiceProvider.php | 89 + .../Card/SubMerchantClient.php | 121 + .../src/OfficialAccount/Comment/Client.php | 208 + .../Comment/ServiceProvider.php | 44 + .../CustomerService/Client.php | 230 + .../CustomerService/Messenger.php | 165 + .../CustomerService/ServiceProvider.php | 37 + .../CustomerService/SessionClient.php | 104 + .../src/OfficialAccount/DataCube/Client.php | 340 + .../DataCube/ServiceProvider.php | 33 + .../src/OfficialAccount/Device/Client.php | 251 + .../Device/ServiceProvider.php | 33 + .../src/OfficialAccount/Goods/Client.php | 113 + .../OfficialAccount/Goods/ServiceProvider.php | 33 + .../src/OfficialAccount/Material/Client.php | 301 + .../Material/ServiceProvider.php | 44 + .../src/OfficialAccount/Menu/Client.php | 103 + .../OfficialAccount/Menu/ServiceProvider.php | 33 + .../OfficialAccount/OAuth/ServiceProvider.php | 66 + .../wechat/src/OfficialAccount/OCR/Client.php | 85 + .../OfficialAccount/OCR/ServiceProvider.php | 33 + .../wechat/src/OfficialAccount/POI/Client.php | 145 + .../OfficialAccount/POI/ServiceProvider.php | 33 + .../src/OfficialAccount/Semantic/Client.php | 45 + .../Semantic/ServiceProvider.php | 31 + .../src/OfficialAccount/Server/Guard.php | 30 + .../Server/Handlers/EchoStrHandler.php | 51 + .../Server/ServiceProvider.php | 46 + .../OfficialAccount/ShakeAround/Client.php | 81 + .../ShakeAround/DeviceClient.php | 190 + .../ShakeAround/GroupClient.php | 167 + .../ShakeAround/MaterialClient.php | 44 + .../ShakeAround/PageClient.php | 110 + .../ShakeAround/RelationClient.php | 87 + .../ShakeAround/ServiceProvider.php | 57 + .../ShakeAround/ShakeAround.php | 44 + .../ShakeAround/StatsClient.php | 110 + .../src/OfficialAccount/Store/Client.php | 209 + .../OfficialAccount/Store/ServiceProvider.php | 33 + .../TemplateMessage/Client.php | 234 + .../TemplateMessage/ServiceProvider.php | 33 + .../OfficialAccount/User/ServiceProvider.php | 35 + .../src/OfficialAccount/User/TagClient.php | 175 + .../src/OfficialAccount/User/UserClient.php | 172 + .../src/OfficialAccount/WiFi/CardClient.php | 52 + .../src/OfficialAccount/WiFi/Client.php | 98 + .../src/OfficialAccount/WiFi/DeviceClient.php | 127 + .../OfficialAccount/WiFi/ServiceProvider.php | 45 + .../src/OfficialAccount/WiFi/ShopClient.php | 100 + .../wechat/src/OpenPlatform/Application.php | 220 + .../src/OpenPlatform/Auth/AccessToken.php | 49 + .../src/OpenPlatform/Auth/ServiceProvider.php | 37 + .../src/OpenPlatform/Auth/VerifyTicket.php | 91 + .../Authorizer/Aggregate/Account/Client.php | 96 + .../Aggregate/AggregateServiceProvider.php | 22 + .../Authorizer/Auth/AccessToken.php | 79 + .../Authorizer/MiniProgram/Account/Client.php | 76 + .../MiniProgram/Account/ServiceProvider.php | 25 + .../Authorizer/MiniProgram/Application.php | 53 + .../Authorizer/MiniProgram/Auth/Client.php | 64 + .../Authorizer/MiniProgram/Code/Client.php | 267 + .../MiniProgram/Code/ServiceProvider.php | 25 + .../Authorizer/MiniProgram/Domain/Client.php | 54 + .../MiniProgram/Domain/ServiceProvider.php | 25 + .../Authorizer/MiniProgram/Setting/Client.php | 248 + .../MiniProgram/Setting/ServiceProvider.php | 25 + .../Authorizer/MiniProgram/Tester/Client.php | 72 + .../MiniProgram/Tester/ServiceProvider.php | 25 + .../OfficialAccount/Account/Client.php | 81 + .../OfficialAccount/Application.php | 46 + .../OfficialAccount/MiniProgram/Client.php | 77 + .../MiniProgram/ServiceProvider.php | 25 + .../OAuth/ComponentDelegate.php | 54 + .../OpenPlatform/Authorizer/Server/Guard.php | 32 + .../wechat/src/OpenPlatform/Base/Client.php | 166 + .../src/OpenPlatform/Base/ServiceProvider.php | 33 + .../src/OpenPlatform/CodeTemplate/Client.php | 86 + .../CodeTemplate/ServiceProvider.php | 25 + .../src/OpenPlatform/Component/Client.php | 60 + .../Component/ServiceProvider.php | 25 + .../wechat/src/OpenPlatform/Server/Guard.php | 65 + .../Server/Handlers/Authorized.php | 30 + .../Server/Handlers/Unauthorized.php | 30 + .../Server/Handlers/UpdateAuthorized.php | 30 + .../Server/Handlers/VerifyTicketRefreshed.php | 48 + .../OpenPlatform/Server/ServiceProvider.php | 34 + .../wechat/src/OpenWork/Application.php | 85 + .../wechat/src/OpenWork/Auth/AccessToken.php | 52 + .../src/OpenWork/Auth/ServiceProvider.php | 33 + .../wechat/src/OpenWork/Corp/Client.php | 217 + .../src/OpenWork/Corp/ServiceProvider.php | 38 + .../src/OpenWork/MiniProgram/Client.php | 50 + .../OpenWork/MiniProgram/ServiceProvider.php | 31 + .../wechat/src/OpenWork/Provider/Client.php | 206 + .../src/OpenWork/Provider/ServiceProvider.php | 36 + .../wechat/src/OpenWork/Server/Guard.php | 68 + .../Server/Handlers/EchoStrHandler.php | 62 + .../src/OpenWork/Server/ServiceProvider.php | 56 + .../src/OpenWork/SuiteAuth/AccessToken.php | 56 + .../OpenWork/SuiteAuth/ServiceProvider.php | 37 + .../src/OpenWork/SuiteAuth/SuiteTicket.php | 85 + .../wechat/src/OpenWork/Work/Application.php | 41 + .../src/OpenWork/Work/Auth/AccessToken.php | 80 + .../wechat/src/Payment/Application.php | 206 + .../wechat/src/Payment/Base/Client.php | 54 + .../src/Payment/Base/ServiceProvider.php | 33 + .../wechat/src/Payment/Bill/Client.php | 48 + .../src/Payment/Bill/ServiceProvider.php | 33 + .../wechat/src/Payment/Coupon/Client.php | 77 + .../src/Payment/Coupon/ServiceProvider.php | 33 + .../wechat/src/Payment/Jssdk/Client.php | 135 + .../src/Payment/Jssdk/ServiceProvider.php | 33 + .../wechat/src/Payment/Kernel/BaseClient.php | 190 + .../Exceptions/InvalidSignException.php | 18 + .../Kernel/Exceptions/SandboxException.php | 18 + .../wechat/src/Payment/Merchant/Client.php | 94 + .../src/Payment/Merchant/ServiceProvider.php | 33 + .../wechat/src/Payment/Notify/Handler.php | 201 + .../wechat/src/Payment/Notify/Paid.php | 33 + .../wechat/src/Payment/Notify/Refunded.php | 48 + .../wechat/src/Payment/Notify/Scanned.php | 60 + .../wechat/src/Payment/Order/Client.php | 126 + .../src/Payment/Order/ServiceProvider.php | 33 + .../src/Payment/ProfitSharing/Client.php | 201 + .../Payment/ProfitSharing/ServiceProvider.php | 33 + .../wechat/src/Payment/Redpack/Client.php | 88 + .../src/Payment/Redpack/ServiceProvider.php | 33 + .../wechat/src/Payment/Refund/Client.php | 159 + .../src/Payment/Refund/ServiceProvider.php | 33 + .../wechat/src/Payment/Reverse/Client.php | 67 + .../src/Payment/Reverse/ServiceProvider.php | 33 + .../wechat/src/Payment/Sandbox/Client.php | 60 + .../src/Payment/Sandbox/ServiceProvider.php | 33 + .../wechat/src/Payment/Security/Client.php | 38 + .../src/Payment/Security/ServiceProvider.php | 33 + .../wechat/src/Payment/Transfer/Client.php | 122 + .../src/Payment/Transfer/ServiceProvider.php | 33 + .../overtrue/wechat/src/Work/Agent/Client.php | 68 + .../wechat/src/Work/Agent/ServiceProvider.php | 33 + .../overtrue/wechat/src/Work/Application.php | 100 + .../wechat/src/Work/Auth/AccessToken.php | 45 + .../wechat/src/Work/Auth/ServiceProvider.php | 33 + .../overtrue/wechat/src/Work/Base/Client.php | 34 + .../wechat/src/Work/Base/ServiceProvider.php | 33 + .../overtrue/wechat/src/Work/Chat/Client.php | 82 + .../wechat/src/Work/Chat/ServiceProvider.php | 33 + .../wechat/src/Work/Department/Client.php | 81 + .../src/Work/Department/ServiceProvider.php | 33 + .../src/Work/ExternalContact/Client.php | 118 + .../Work/ExternalContact/ContactWayClient.php | 98 + .../Work/ExternalContact/MessageClient.php | 168 + .../Work/ExternalContact/ServiceProvider.php | 45 + .../Work/ExternalContact/StatisticsClient.php | 47 + .../wechat/src/Work/GroupRobot/Client.php | 49 + .../src/Work/GroupRobot/Messages/Image.php | 41 + .../src/Work/GroupRobot/Messages/Markdown.php | 40 + .../src/Work/GroupRobot/Messages/Message.php | 23 + .../src/Work/GroupRobot/Messages/News.php | 55 + .../src/Work/GroupRobot/Messages/NewsItem.php | 45 + .../src/Work/GroupRobot/Messages/Text.php | 70 + .../wechat/src/Work/GroupRobot/Messenger.php | 129 + .../src/Work/GroupRobot/ServiceProvider.php | 37 + .../wechat/src/Work/Invoice/Client.php | 100 + .../src/Work/Invoice/ServiceProvider.php | 33 + .../overtrue/wechat/src/Work/Jssdk/Client.php | 68 + .../wechat/src/Work/Jssdk/ServiceProvider.php | 33 + .../overtrue/wechat/src/Work/Media/Client.php | 116 + .../wechat/src/Work/Media/ServiceProvider.php | 28 + .../overtrue/wechat/src/Work/Menu/Client.php | 61 + .../wechat/src/Work/Menu/ServiceProvider.php | 33 + .../wechat/src/Work/Message/Client.php | 48 + .../wechat/src/Work/Message/Messenger.php | 205 + .../src/Work/Message/ServiceProvider.php | 43 + .../src/Work/MiniProgram/Application.php | 44 + .../src/Work/MiniProgram/Auth/Client.php | 39 + vendor/overtrue/wechat/src/Work/OA/Client.php | 171 + .../wechat/src/Work/OA/ServiceProvider.php | 33 + .../src/Work/OAuth/AccessTokenDelegate.php | 46 + .../wechat/src/Work/OAuth/ServiceProvider.php | 62 + .../overtrue/wechat/src/Work/Server/Guard.php | 48 + .../Work/Server/Handlers/EchoStrHandler.php | 58 + .../src/Work/Server/ServiceProvider.php | 46 + .../overtrue/wechat/src/Work/User/Client.php | 251 + .../wechat/src/Work/User/ServiceProvider.php | 37 + .../wechat/src/Work/User/TagClient.php | 178 + vendor/paragonie/random_compat/LICENSE | 22 + vendor/paragonie/random_compat/build-phar.sh | 5 + vendor/paragonie/random_compat/composer.json | 34 + .../dist/random_compat.phar.pubkey | 5 + .../dist/random_compat.phar.pubkey.asc | 11 + vendor/paragonie/random_compat/lib/random.php | 32 + .../random_compat/other/build_phar.php | 57 + .../random_compat/psalm-autoload.php | 9 + vendor/paragonie/random_compat/psalm.xml | 19 + .../phpoffice/phpspreadsheet/.gitattributes | 4 + vendor/phpoffice/phpspreadsheet/.gitignore | 10 + vendor/phpoffice/phpspreadsheet/.php_cs.dist | 183 + .../phpoffice/phpspreadsheet/.scrutinizer.yml | 27 + vendor/phpoffice/phpspreadsheet/.travis.yml | 57 + .../phpspreadsheet/CHANGELOG.PHPExcel.md | 1593 + vendor/phpoffice/phpspreadsheet/CHANGELOG.md | 471 + .../phpoffice/phpspreadsheet/CONTRIBUTING.md | 11 + vendor/phpoffice/phpspreadsheet/LICENSE | 21 + .../phpspreadsheet/bin/generate-document | 24 + .../phpspreadsheet/bin/migrate-from-phpexcel | 8 + .../phpoffice/phpspreadsheet/bin/pre-commit | 33 + vendor/phpoffice/phpspreadsheet/composer.json | 86 + vendor/phpoffice/phpspreadsheet/composer.lock | 3503 ++ .../phpspreadsheet/docs/assets/logo.svg | 947 + .../phpspreadsheet/docs/extra/extra.css | 8 + vendor/phpoffice/phpspreadsheet/docs/faq.md | 57 + vendor/phpoffice/phpspreadsheet/docs/index.md | 98 + .../references/features-cross-reference.md | 1591 + .../references/function-list-by-category.md | 458 + .../docs/references/function-list-by-name.md | 533 + .../docs/topics/accessing-cells.md | 556 + .../docs/topics/architecture.md | 75 + .../phpspreadsheet/docs/topics/autofilters.md | 530 + .../docs/topics/calculation-engine.md | 2098 + .../docs/topics/creating-spreadsheet.md | 59 + .../docs/topics/file-formats.md | 121 + .../docs/topics/images/01-01-autofilter.png | Bin 0 -> 45173 bytes .../docs/topics/images/01-02-autofilter.png | Bin 0 -> 14496 bytes .../topics/images/01-03-filter-icon-1.png | Bin 0 -> 453 bytes .../topics/images/01-03-filter-icon-2.png | Bin 0 -> 640 bytes .../docs/topics/images/01-04-autofilter.png | Bin 0 -> 17489 bytes .../docs/topics/images/01-schematic.png | Bin 0 -> 14519 bytes .../docs/topics/images/02-readers-writers.png | Bin 0 -> 55819 bytes .../topics/images/04-01-simple-autofilter.png | Bin 0 -> 67694 bytes .../images/04-02-dategroup-autofilter.png | Bin 0 -> 49268 bytes .../images/04-03-custom-autofilter-1.png | Bin 0 -> 51786 bytes .../images/04-03-custom-autofilter-2.png | Bin 0 -> 53489 bytes .../images/04-04-dynamic-autofilter.png | Bin 0 -> 111531 bytes .../images/04-05-topten-autofilter-1.png | Bin 0 -> 53737 bytes .../images/04-05-topten-autofilter-2.png | Bin 0 -> 22842 bytes .../topics/images/07-simple-example-1.png | Bin 0 -> 12239 bytes .../topics/images/07-simple-example-2.png | Bin 0 -> 9620 bytes .../topics/images/07-simple-example-3.png | Bin 0 -> 7157 bytes .../topics/images/07-simple-example-4.png | Bin 0 -> 8018 bytes .../docs/topics/images/08-cell-comment.png | Bin 0 -> 31473 bytes .../docs/topics/images/08-column-width.png | Bin 0 -> 14985 bytes .../topics/images/08-page-setup-margins.png | Bin 0 -> 125173 bytes .../images/08-page-setup-scaling-options.png | Bin 0 -> 24136 bytes .../images/08-styling-border-options.png | Bin 0 -> 18878 bytes .../images/09-command-line-calculation.png | Bin 0 -> 44332 bytes .../topics/images/09-formula-in-cell-1.png | Bin 0 -> 26053 bytes .../topics/images/09-formula-in-cell-2.png | Bin 0 -> 34014 bytes .../docs/topics/memory_saving.md | 107 + .../docs/topics/migration-from-PHPExcel.md | 433 + .../topics/reading-and-writing-to-file.md | 928 + .../docs/topics/reading-files.md | 689 + .../phpspreadsheet/docs/topics/recipes.md | 1506 + .../phpspreadsheet/docs/topics/settings.md | 45 + .../phpspreadsheet/docs/topics/worksheets.md | 128 + vendor/phpoffice/phpspreadsheet/mkdocs.yml | 7 + .../phpoffice/phpspreadsheet/phpunit.xml.dist | 23 + .../samples/Autofilter/10_Autofilter.php | 101 + .../Autofilter/10_Autofilter_selection_1.php | 156 + .../Autofilter/10_Autofilter_selection_2.php | 148 + .../10_Autofilter_selection_display.php | 170 + .../samples/Basic/01_Simple.php | 65 + .../samples/Basic/01_Simple_download_ods.php | 61 + .../samples/Basic/01_Simple_download_pdf.php | 56 + .../samples/Basic/01_Simple_download_xls.php | 61 + .../samples/Basic/01_Simple_download_xlsx.php | 60 + .../phpspreadsheet/samples/Basic/02_Types.php | 162 + .../samples/Basic/03_Formulas.php | 81 + .../samples/Basic/04_Printing.php | 64 + .../samples/Basic/05_Feature_demo.php | 7 + .../samples/Basic/06_Largescale.php | 8 + .../samples/Basic/07_Reader.php | 19 + .../Basic/08_Conditional_formatting.php | 115 + .../Basic/08_Conditional_formatting_2.php | 70 + .../samples/Basic/09_Pagebreaks.php | 63 + .../samples/Basic/11_Documentsecurity.php | 48 + .../samples/Basic/12_CellProtection.php | 47 + .../samples/Basic/13_Calculation.php | 176 + .../Basic/13_CalculationCyclicFormulae.php | 33 + .../phpspreadsheet/samples/Basic/14_Xls.php | 13 + .../samples/Basic/15_Datavalidation.php | 80 + .../phpspreadsheet/samples/Basic/16_Csv.php | 41 + .../phpspreadsheet/samples/Basic/17_Html.php | 13 + .../samples/Basic/18_Extendedcalculation.php | 69 + .../samples/Basic/19_Namedrange.php | 70 + .../samples/Basic/20_Read_Excel2003XML.php | 13 + .../samples/Basic/20_Read_Gnumeric.php | 13 + .../samples/Basic/20_Read_Ods.php | 13 + .../samples/Basic/20_Read_Sylk.php | 13 + .../samples/Basic/20_Read_Xls.php | 22 + .../samples/Basic/22_Heavily_formatted.php | 48 + .../samples/Basic/23_Sharedstyles.php | 59 + .../samples/Basic/24_Readfilter.php | 41 + .../samples/Basic/25_In_memory_image.php | 40 + .../phpspreadsheet/samples/Basic/26_Utf8.php | 40 + .../samples/Basic/27_Images_Xls.php | 13 + .../samples/Basic/28_Iterator.php | 34 + .../Basic/29_Advanced_value_binder.php | 132 + .../samples/Basic/30_Template.php | 43 + .../Basic/31_Document_properties_write.php | 68 + .../31_Document_properties_write_xls.php | 68 + .../samples/Basic/37_Page_layout_view.php | 32 + .../samples/Basic/38_Clone_worksheet.php | 57 + .../samples/Basic/39_Dropdown.php | 129 + .../samples/Basic/40_Duplicate_style.php | 36 + .../samples/Basic/41_Password.php | 12 + .../samples/Basic/42_RichText.php | 98 + .../samples/Basic/43_Merge_workbooks.php | 26 + .../samples/Basic/44_Worksheet_info.php | 26 + .../Basic/45_Quadratic_equation_solver.php | 43 + .../samples/Basic/46_ReadHtml.php | 19 + .../samples/Basic/data/continents/Africa.txt | 54 + .../samples/Basic/data/continents/Asia.txt | 44 + .../samples/Basic/data/continents/Europe.txt | 47 + .../Basic/data/continents/North America.txt | 23 + .../samples/Basic/data/continents/Oceania.txt | 14 + .../Basic/data/continents/South America.txt | 12 + .../Calculations/Database/DAVERAGE.php | 56 + .../samples/Calculations/Database/DCOUNT.php | 55 + .../samples/Calculations/Database/DGET.php | 52 + .../samples/Calculations/Database/DMAX.php | 55 + .../samples/Calculations/Database/DMIN.php | 55 + .../Calculations/Database/DPRODUCT.php | 52 + .../samples/Calculations/Database/DSTDEV.php | 56 + .../samples/Calculations/Database/DSTDEVP.php | 55 + .../samples/Calculations/Database/DVAR.php | 55 + .../samples/Calculations/Database/DVARP.php | 56 + .../samples/Calculations/DateTime/DATE.php | 41 + .../Calculations/DateTime/DATEVALUE.php | 39 + .../samples/Calculations/DateTime/TIME.php | 39 + .../Calculations/DateTime/TIMEVALUE.php | 35 + .../samples/Chart/32_Chart_read_write.php | 83 + .../Chart/32_Chart_read_write_HTML.php | 89 + .../samples/Chart/32_Chart_read_write_PDF.php | 91 + .../samples/Chart/33_Chart_create_area.php | 104 + .../samples/Chart/33_Chart_create_bar.php | 15 + .../Chart/33_Chart_create_bar_stacked.php | 107 + .../samples/Chart/33_Chart_create_column.php | 107 + .../Chart/33_Chart_create_column_2.php | 116 + .../Chart/33_Chart_create_composite.php | 160 + .../samples/Chart/33_Chart_create_line.php | 105 + .../Chart/33_Chart_create_multiple_charts.php | 179 + .../samples/Chart/33_Chart_create_pie.php | 175 + .../33_Chart_create_pie_custom_colors.php | 183 + .../samples/Chart/33_Chart_create_radar.php | 117 + .../samples/Chart/33_Chart_create_scatter.php | 101 + .../samples/Chart/33_Chart_create_stock.php | 113 + .../samples/Chart/34_Chart_update.php | 38 + .../samples/Chart/35_Chart_render.php | 75 + .../phpspreadsheet/samples/Header.php | 64 + .../samples/Pdf/21_Pdf_Domdf.php | 20 + .../samples/Pdf/21_Pdf_TCPDF.php | 20 + .../samples/Pdf/21_Pdf_mPDF.php | 20 + .../01_Simple_file_reader_using_IOFactory.php | 11 + ...e_file_reader_using_a_specified_reader.php | 13 + ...using_the_IOFactory_to_return_a_reader.php | 15 + ..._IOFactory_to_identify_a_reader_to_use.php | 17 + ...reader_using_the_read_data_only_option.php | 17 + ...ple_file_reader_loading_all_worksheets.php | 20 + ...eader_loading_a_single_named_worksheet.php | 21 + ...eader_loading_several_named_worksheets.php | 21 + ...Simple_file_reader_using_a_read_filter.php | 40 + ...eader_using_a_configurable_read_filter.php | 52 + ...a_configurable_read_filter_(version_1).php | 64 + ...a_configurable_read_filter_(version_2).php | 67 + ...ple_file_reader_for_multiple_CSV_files.php | 29 + ...ks_to_split_across_multiple_worksheets.php | 86 + ...e_file_using_the_Advanced_Value_Binder.php | 41 + ...dling_loader_exceptions_using_TryCatch.php | 14 + ...eader_loading_several_named_worksheets.php | 20 + ...worksheets_without_loading_entire_file.php | 20 + ...nformation_without_loading_entire_file.php | 23 + .../20_Reader_worksheet_hyperlink_image.php | 54 + ...Long_Integers_with_String_Value_Binder.php | 27 + .../samples/Reader/sampleData/example1.csv | 4 + .../samples/Reader/sampleData/example1.tsv | 4 + .../samples/Reader/sampleData/example1.xls | Bin 0 -> 22528 bytes .../samples/Reader/sampleData/example2.csv | 223 + .../samples/Reader/sampleData/example2.xls | Bin 0 -> 36864 bytes .../Reader/sampleData/longIntegers.csv | 6 + .../Custom_properties.php | 53 + .../Custom_property_names.php | 20 + .../Reading_workbook_data/Properties.php | 64 + .../Worksheet_count_and_names.php | 24 + .../sampleData/example1.xls | Bin 0 -> 20992 bytes .../sampleData/example1.xlsx | Bin 0 -> 9733 bytes .../sampleData/example2.xls | Bin 0 -> 22528 bytes .../samples/bootstrap/css/bootstrap.min.css | 6 + .../bootstrap/css/font-awesome.min.css | 4 + .../samples/bootstrap/css/phpspreadsheet.css | 13 + .../samples/bootstrap/fonts/FontAwesome.otf | Bin 0 -> 124988 bytes .../bootstrap/fonts/fontawesome-webfont.eot | Bin 0 -> 76518 bytes .../bootstrap/fonts/fontawesome-webfont.svg | 685 + .../bootstrap/fonts/fontawesome-webfont.ttf | Bin 0 -> 152796 bytes .../bootstrap/fonts/fontawesome-webfont.woff | Bin 0 -> 90412 bytes .../bootstrap/fonts/fontawesome-webfont.woff2 | Bin 0 -> 71896 bytes .../samples/bootstrap/js/bootstrap.min.js | 7 + .../samples/bootstrap/js/jquery.min.js | 4 + .../samples/images/PhpSpreadsheet_logo.png | Bin 0 -> 7347 bytes .../samples/images/officelogo.jpg | Bin 0 -> 5597 bytes .../phpspreadsheet/samples/images/paid.png | Bin 0 -> 1605 bytes .../samples/images/termsconditions.jpg | Bin 0 -> 528 bytes .../phpspreadsheet/samples/index.php | 39 + .../samples/templates/26template.xlsx | Bin 0 -> 9165 bytes .../samples/templates/27template.xls | Bin 0 -> 364544 bytes .../samples/templates/28iterators.xlsx | Bin 0 -> 8373 bytes .../samples/templates/30template.xls | Bin 0 -> 39424 bytes .../samples/templates/31docproperties.xls | Bin 0 -> 20992 bytes .../samples/templates/31docproperties.xlsx | Bin 0 -> 8844 bytes .../samples/templates/32chartreadwrite.xlsx | Bin 0 -> 27044 bytes .../templates/32complexChartreadwrite.xlsx | Bin 0 -> 13641 bytes .../templates/32readwriteAreaChart1.xlsx | Bin 0 -> 12588 bytes .../templates/32readwriteAreaChart2.xlsx | Bin 0 -> 12687 bytes .../templates/32readwriteAreaChart3.xlsx | Bin 0 -> 12897 bytes .../templates/32readwriteAreaChart3D1.xlsx | Bin 0 -> 13017 bytes .../32readwriteAreaPercentageChart1.xlsx | Bin 0 -> 12714 bytes .../32readwriteAreaPercentageChart2.xlsx | Bin 0 -> 12908 bytes .../32readwriteAreaPercentageChart3D1.xlsx | Bin 0 -> 12999 bytes .../32readwriteAreaStackedChart1.xlsx | Bin 0 -> 12671 bytes .../32readwriteAreaStackedChart2.xlsx | Bin 0 -> 12883 bytes .../32readwriteAreaStackedChart3D1.xlsx | Bin 0 -> 12974 bytes .../templates/32readwriteBarChart1.xlsx | Bin 0 -> 12644 bytes .../templates/32readwriteBarChart2.xlsx | Bin 0 -> 12723 bytes .../templates/32readwriteBarChart3.xlsx | Bin 0 -> 12923 bytes .../templates/32readwriteBarChart3D1.xlsx | Bin 0 -> 13017 bytes .../32readwriteBarPercentageChart1.xlsx | Bin 0 -> 12729 bytes .../32readwriteBarPercentageChart2.xlsx | Bin 0 -> 12969 bytes .../32readwriteBarPercentageChart3D1.xlsx | Bin 0 -> 13045 bytes .../32readwriteBarStackedChart1.xlsx | Bin 0 -> 12724 bytes .../32readwriteBarStackedChart2.xlsx | Bin 0 -> 12940 bytes .../32readwriteBarStackedChart3D1.xlsx | Bin 0 -> 13022 bytes .../templates/32readwriteBubbleChart1.xlsx | Bin 0 -> 30266 bytes .../templates/32readwriteBubbleChart3D1.xlsx | Bin 0 -> 30257 bytes .../32readwriteChartWithImages1.xlsx | Bin 0 -> 43212 bytes .../templates/32readwriteColumnChart1.xlsx | Bin 0 -> 12632 bytes .../templates/32readwriteColumnChart2.xlsx | Bin 0 -> 12724 bytes .../templates/32readwriteColumnChart3.xlsx | Bin 0 -> 12946 bytes .../templates/32readwriteColumnChart3D1.xlsx | Bin 0 -> 13027 bytes .../templates/32readwriteColumnChart4.xlsx | Bin 0 -> 30563 bytes .../32readwriteColumnPercentageChart1.xlsx | Bin 0 -> 12733 bytes .../32readwriteColumnPercentageChart2.xlsx | Bin 0 -> 12931 bytes .../32readwriteColumnPercentageChart3D1.xlsx | Bin 0 -> 13045 bytes .../32readwriteColumnStackedChart1.xlsx | Bin 0 -> 12740 bytes .../32readwriteColumnStackedChart2.xlsx | Bin 0 -> 12951 bytes .../32readwriteColumnStackedChart3D1.xlsx | Bin 0 -> 13017 bytes .../templates/32readwriteDonutChart1.xlsx | Bin 0 -> 11793 bytes .../templates/32readwriteDonutChart2.xlsx | Bin 0 -> 11948 bytes .../templates/32readwriteDonutChart3.xlsx | Bin 0 -> 12017 bytes .../templates/32readwriteDonutChart4.xlsx | Bin 0 -> 12036 bytes .../32readwriteDonutChartExploded1.xlsx | Bin 0 -> 30250 bytes .../32readwriteDonutChartMultiseries1.xlsx | Bin 0 -> 30180 bytes .../templates/32readwriteLineChart1.xlsx | Bin 0 -> 12610 bytes .../templates/32readwriteLineChart2.xlsx | Bin 0 -> 12694 bytes .../templates/32readwriteLineChart3.xlsx | Bin 0 -> 12918 bytes .../templates/32readwriteLineChart3D1.xlsx | Bin 0 -> 12994 bytes .../32readwriteLineChartNoPointMarkers1.xlsx | Bin 0 -> 12930 bytes .../32readwriteLinePercentageChart1.xlsx | Bin 0 -> 12730 bytes .../32readwriteLinePercentageChart2.xlsx | Bin 0 -> 12968 bytes .../32readwriteLineStackedChart1.xlsx | Bin 0 -> 12717 bytes .../32readwriteLineStackedChart2.xlsx | Bin 0 -> 12941 bytes .../templates/32readwritePieChart1.xlsx | Bin 0 -> 11789 bytes .../templates/32readwritePieChart2.xlsx | Bin 0 -> 11940 bytes .../templates/32readwritePieChart3.xlsx | Bin 0 -> 12002 bytes .../templates/32readwritePieChart3D1.xlsx | Bin 0 -> 11993 bytes .../templates/32readwritePieChart4.xlsx | Bin 0 -> 12027 bytes .../32readwritePieChartExploded1.xlsx | Bin 0 -> 30238 bytes .../32readwritePieChartExploded3D1.xlsx | Bin 0 -> 30287 bytes .../templates/32readwriteRadarChart1.xlsx | Bin 0 -> 30490 bytes .../templates/32readwriteRadarChart2.xlsx | Bin 0 -> 30414 bytes .../templates/32readwriteRadarChart3.xlsx | Bin 0 -> 30424 bytes .../templates/32readwriteScatterChart1.xlsx | Bin 0 -> 30452 bytes .../templates/32readwriteScatterChart2.xlsx | Bin 0 -> 30357 bytes .../templates/32readwriteScatterChart3.xlsx | Bin 0 -> 30380 bytes .../templates/32readwriteScatterChart4.xlsx | Bin 0 -> 30404 bytes .../templates/32readwriteScatterChart5.xlsx | Bin 0 -> 30414 bytes .../templates/32readwriteStockChart1.xlsx | Bin 0 -> 30218 bytes .../templates/32readwriteStockChart2.xlsx | Bin 0 -> 30413 bytes .../templates/32readwriteStockChart3.xlsx | Bin 0 -> 30504 bytes .../templates/32readwriteStockChart4.xlsx | Bin 0 -> 30676 bytes .../templates/32readwriteSurfaceChart1.xlsx | Bin 0 -> 30488 bytes .../templates/32readwriteSurfaceChart2.xlsx | Bin 0 -> 30330 bytes .../templates/32readwriteSurfaceChart3.xlsx | Bin 0 -> 30511 bytes .../templates/32readwriteSurfaceChart4.xlsx | Bin 0 -> 30315 bytes .../samples/templates/36writeLineChart1.xlsx | Bin 0 -> 12918 bytes .../samples/templates/43mergeBook1.xlsx | Bin 0 -> 9629 bytes .../samples/templates/43mergeBook2.xlsx | Bin 0 -> 9618 bytes .../samples/templates/46readHtml.html | 130 + .../samples/templates/Excel2003XMLTest.xml | 39531 ++++++++++++++++ .../samples/templates/GnumericTest.gnumeric | Bin 0 -> 7823 bytes .../samples/templates/OOCalcTest.ods | Bin 0 -> 20326 bytes .../samples/templates/SylkTest.slk | 152 + .../samples/templates/chartSpreadsheet.php | 98 + .../samples/templates/largeSpreadsheet.php | 56 + .../samples/templates/sampleSpreadsheet.php | 371 + .../phpspreadsheet/src/Bootstrap.php | 22 + .../Calculation/Calculation.php | 4820 ++ .../PhpSpreadsheet/Calculation/Category.php | 19 + .../PhpSpreadsheet/Calculation/Database.php | 632 + .../PhpSpreadsheet/Calculation/DateTime.php | 1691 + .../Engine/CyclicReferenceStack.php | 73 + .../Calculation/Engine/Logger.php | 128 + .../Calculation/Engineering.php | 2807 ++ .../PhpSpreadsheet/Calculation/Exception.php | 26 + .../Calculation/ExceptionHandler.php | 22 + .../PhpSpreadsheet/Calculation/Financial.php | 2439 + .../Calculation/FormulaParser.php | 623 + .../Calculation/FormulaToken.php | 150 + .../PhpSpreadsheet/Calculation/Functions.php | 680 + .../PhpSpreadsheet/Calculation/Logical.php | 375 + .../PhpSpreadsheet/Calculation/LookupRef.php | 968 + .../PhpSpreadsheet/Calculation/MathTrig.php | 1848 + .../Calculation/Statistical.php | 3773 ++ .../PhpSpreadsheet/Calculation/TextData.php | 674 + .../Calculation/Token/Stack.php | 149 + .../Calculation/functionlist.txt | 392 + .../Calculation/locale/bg/config | 27 + .../Calculation/locale/bg/functions | 417 + .../Calculation/locale/cs/config | 23 + .../Calculation/locale/cs/functions | 416 + .../Calculation/locale/da/config | 25 + .../Calculation/locale/da/functions | 416 + .../Calculation/locale/de/config | 24 + .../Calculation/locale/de/functions | 416 + .../Calculation/locale/en/uk/config | 8 + .../Calculation/locale/es/config | 24 + .../Calculation/locale/es/functions | 416 + .../Calculation/locale/fi/config | 24 + .../Calculation/locale/fi/functions | 416 + .../Calculation/locale/fr/config | 24 + .../Calculation/locale/fr/functions | 416 + .../Calculation/locale/hu/config | 23 + .../Calculation/locale/hu/functions | 416 + .../Calculation/locale/it/config | 24 + .../Calculation/locale/it/functions | 416 + .../Calculation/locale/nl/config | 24 + .../Calculation/locale/nl/functions | 416 + .../Calculation/locale/no/config | 24 + .../Calculation/locale/no/functions | 416 + .../Calculation/locale/pl/config | 24 + .../Calculation/locale/pl/functions | 416 + .../Calculation/locale/pt/br/config | 24 + .../Calculation/locale/pt/br/functions | 408 + .../Calculation/locale/pt/config | 24 + .../Calculation/locale/pt/functions | 408 + .../Calculation/locale/ru/config | 24 + .../Calculation/locale/ru/functions | 416 + .../Calculation/locale/sv/config | 24 + .../Calculation/locale/sv/functions | 408 + .../Calculation/locale/tr/config | 24 + .../Calculation/locale/tr/functions | 416 + .../Cell/AdvancedValueBinder.php | 176 + .../src/PhpSpreadsheet/Cell/Cell.php | 701 + .../src/PhpSpreadsheet/Cell/Coordinate.php | 520 + .../src/PhpSpreadsheet/Cell/DataType.php | 85 + .../PhpSpreadsheet/Cell/DataValidation.php | 481 + .../src/PhpSpreadsheet/Cell/DataValidator.php | 77 + .../Cell/DefaultValueBinder.php | 82 + .../src/PhpSpreadsheet/Cell/Hyperlink.php | 113 + .../src/PhpSpreadsheet/Cell/IValueBinder.php | 16 + .../PhpSpreadsheet/Cell/StringValueBinder.php | 31 + .../src/PhpSpreadsheet/Chart/Axis.php | 557 + .../src/PhpSpreadsheet/Chart/Chart.php | 680 + .../src/PhpSpreadsheet/Chart/DataSeries.php | 390 + .../PhpSpreadsheet/Chart/DataSeriesValues.php | 401 + .../src/PhpSpreadsheet/Chart/Exception.php | 9 + .../src/PhpSpreadsheet/Chart/GridLines.php | 455 + .../src/PhpSpreadsheet/Chart/Layout.php | 483 + .../src/PhpSpreadsheet/Chart/Legend.php | 158 + .../src/PhpSpreadsheet/Chart/PlotArea.php | 112 + .../src/PhpSpreadsheet/Chart/Properties.php | 369 + .../Chart/Renderer/IRenderer.php | 24 + .../PhpSpreadsheet/Chart/Renderer/JpGraph.php | 856 + .../Chart/Renderer/PHP Charting Libraries.txt | 20 + .../Chart/Renderer/Polyfill.php | 9 + .../src/PhpSpreadsheet/Chart/Title.php | 66 + .../src/PhpSpreadsheet/Collection/Cells.php | 506 + .../Collection/CellsFactory.php | 21 + .../src/PhpSpreadsheet/Collection/Memory.php | 79 + .../src/PhpSpreadsheet/Comment.php | 331 + .../PhpSpreadsheet/Document/Properties.php | 629 + .../src/PhpSpreadsheet/Document/Security.php | 205 + .../src/PhpSpreadsheet/DocumentGenerator.php | 111 + .../src/PhpSpreadsheet/Exception.php | 7 + .../src/PhpSpreadsheet/HashTable.php | 179 + .../src/PhpSpreadsheet/Helper/Html.php | 841 + .../src/PhpSpreadsheet/Helper/Migrator.php | 333 + .../src/PhpSpreadsheet/Helper/Sample.php | 230 + .../src/PhpSpreadsheet/IComparable.php | 13 + .../src/PhpSpreadsheet/IOFactory.php | 228 + .../src/PhpSpreadsheet/NamedRange.php | 240 + .../src/PhpSpreadsheet/Reader/BaseReader.php | 160 + .../src/PhpSpreadsheet/Reader/Csv.php | 563 + .../Reader/DefaultReadFilter.php | 20 + .../src/PhpSpreadsheet/Reader/Exception.php | 9 + .../src/PhpSpreadsheet/Reader/Gnumeric.php | 889 + .../src/PhpSpreadsheet/Reader/Html.php | 969 + .../src/PhpSpreadsheet/Reader/IReadFilter.php | 17 + .../src/PhpSpreadsheet/Reader/IReader.php | 137 + .../src/PhpSpreadsheet/Reader/Ods.php | 708 + .../PhpSpreadsheet/Reader/Ods/Properties.php | 135 + .../Reader/Security/XmlScanner.php | 154 + .../src/PhpSpreadsheet/Reader/Slk.php | 496 + .../src/PhpSpreadsheet/Reader/Xls.php | 7947 ++++ .../src/PhpSpreadsheet/Reader/Xls/Color.php | 36 + .../PhpSpreadsheet/Reader/Xls/Color/BIFF5.php | 81 + .../PhpSpreadsheet/Reader/Xls/Color/BIFF8.php | 81 + .../Reader/Xls/Color/BuiltIn.php | 35 + .../PhpSpreadsheet/Reader/Xls/ErrorCode.php | 32 + .../src/PhpSpreadsheet/Reader/Xls/Escher.php | 677 + .../src/PhpSpreadsheet/Reader/Xls/MD5.php | 184 + .../src/PhpSpreadsheet/Reader/Xls/RC4.php | 61 + .../Reader/Xls/Style/Border.php | 42 + .../Reader/Xls/Style/FillPattern.php | 47 + .../src/PhpSpreadsheet/Reader/Xlsx.php | 2043 + .../PhpSpreadsheet/Reader/Xlsx/AutoFilter.php | 145 + .../Reader/Xlsx/BaseParserClass.php | 19 + .../src/PhpSpreadsheet/Reader/Xlsx/Chart.php | 570 + .../Reader/Xlsx/ColumnAndRowAttributes.php | 204 + .../Reader/Xlsx/ConditionalStyles.php | 94 + .../Reader/Xlsx/DataValidations.php | 50 + .../PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php | 58 + .../PhpSpreadsheet/Reader/Xlsx/PageSetup.php | 150 + .../PhpSpreadsheet/Reader/Xlsx/Properties.php | 91 + .../Reader/Xlsx/SheetViewOptions.php | 124 + .../PhpSpreadsheet/Reader/Xlsx/SheetViews.php | 137 + .../src/PhpSpreadsheet/Reader/Xlsx/Styles.php | 281 + .../src/PhpSpreadsheet/Reader/Xlsx/Theme.php | 93 + .../src/PhpSpreadsheet/Reader/Xml.php | 881 + .../src/PhpSpreadsheet/ReferenceHelper.php | 910 + .../PhpSpreadsheet/RichText/ITextElement.php | 36 + .../src/PhpSpreadsheet/RichText/RichText.php | 177 + .../src/PhpSpreadsheet/RichText/Run.php | 65 + .../PhpSpreadsheet/RichText/TextElement.php | 86 + .../src/PhpSpreadsheet/Settings.php | 167 + .../src/PhpSpreadsheet/Shared/CodePage.php | 138 + .../src/PhpSpreadsheet/Shared/Date.php | 493 + .../src/PhpSpreadsheet/Shared/Drawing.php | 249 + .../src/PhpSpreadsheet/Shared/Escher.php | 64 + .../Shared/Escher/DgContainer.php | 52 + .../Escher/DgContainer/SpgrContainer.php | 79 + .../DgContainer/SpgrContainer/SpContainer.php | 369 + .../Shared/Escher/DggContainer.php | 175 + .../Escher/DggContainer/BstoreContainer.php | 34 + .../DggContainer/BstoreContainer/BSE.php | 89 + .../DggContainer/BstoreContainer/BSE/Blip.php | 60 + .../src/PhpSpreadsheet/Shared/File.php | 144 + .../src/PhpSpreadsheet/Shared/Font.php | 762 + .../PhpSpreadsheet/Shared/JAMA/CHANGELOG.TXT | 16 + .../Shared/JAMA/CholeskyDecomposition.php | 147 + .../Shared/JAMA/EigenvalueDecomposition.php | 861 + .../Shared/JAMA/LUDecomposition.php | 285 + .../src/PhpSpreadsheet/Shared/JAMA/Matrix.php | 1233 + .../Shared/JAMA/QRDecomposition.php | 249 + .../JAMA/SingularValueDecomposition.php | 528 + .../Shared/JAMA/utils/Maths.php | 30 + .../src/PhpSpreadsheet/Shared/OLE.php | 573 + .../Shared/OLE/ChainedBlockStream.php | 196 + .../src/PhpSpreadsheet/Shared/OLE/PPS.php | 238 + .../PhpSpreadsheet/Shared/OLE/PPS/File.php | 66 + .../PhpSpreadsheet/Shared/OLE/PPS/Root.php | 466 + .../src/PhpSpreadsheet/Shared/OLERead.php | 352 + .../PhpSpreadsheet/Shared/PasswordHasher.php | 37 + .../PhpSpreadsheet/Shared/StringHelper.php | 722 + .../src/PhpSpreadsheet/Shared/TimeZone.php | 87 + .../PhpSpreadsheet/Shared/Trend/BestFit.php | 463 + .../Shared/Trend/ExponentialBestFit.php | 122 + .../Shared/Trend/LinearBestFit.php | 81 + .../Shared/Trend/LogarithmicBestFit.php | 90 + .../Shared/Trend/PolynomialBestFit.php | 200 + .../Shared/Trend/PowerBestFit.php | 114 + .../src/PhpSpreadsheet/Shared/Trend/Trend.php | 120 + .../src/PhpSpreadsheet/Shared/XMLWriter.php | 92 + .../src/PhpSpreadsheet/Shared/Xls.php | 281 + .../src/PhpSpreadsheet/Spreadsheet.php | 1490 + .../src/PhpSpreadsheet/Style/Alignment.php | 466 + .../src/PhpSpreadsheet/Style/Border.php | 239 + .../src/PhpSpreadsheet/Style/Borders.php | 423 + .../src/PhpSpreadsheet/Style/Color.php | 411 + .../src/PhpSpreadsheet/Style/Conditional.php | 273 + .../src/PhpSpreadsheet/Style/Fill.php | 326 + .../src/PhpSpreadsheet/Style/Font.php | 556 + .../src/PhpSpreadsheet/Style/NumberFormat.php | 820 + .../src/PhpSpreadsheet/Style/Protection.php | 190 + .../src/PhpSpreadsheet/Style/Style.php | 641 + .../src/PhpSpreadsheet/Style/Supervisor.php | 117 + .../PhpSpreadsheet/Worksheet/AutoFilter.php | 873 + .../Worksheet/AutoFilter/Column.php | 388 + .../Worksheet/AutoFilter/Column/Rule.php | 455 + .../PhpSpreadsheet/Worksheet/BaseDrawing.php | 537 + .../PhpSpreadsheet/Worksheet/CellIterator.php | 61 + .../src/PhpSpreadsheet/Worksheet/Column.php | 64 + .../Worksheet/ColumnCellIterator.php | 197 + .../Worksheet/ColumnDimension.php | 115 + .../Worksheet/ColumnIterator.php | 175 + .../PhpSpreadsheet/Worksheet/Dimension.php | 165 + .../src/PhpSpreadsheet/Worksheet/Drawing.php | 116 + .../Worksheet/Drawing/Shadow.php | 289 + .../PhpSpreadsheet/Worksheet/HeaderFooter.php | 491 + .../Worksheet/HeaderFooterDrawing.php | 24 + .../src/PhpSpreadsheet/Worksheet/Iterator.php | 87 + .../Worksheet/MemoryDrawing.php | 169 + .../PhpSpreadsheet/Worksheet/PageMargins.php | 214 + .../PhpSpreadsheet/Worksheet/PageSetup.php | 851 + .../PhpSpreadsheet/Worksheet/Protection.php | 586 + .../src/PhpSpreadsheet/Worksheet/Row.php | 74 + .../Worksheet/RowCellIterator.php | 205 + .../PhpSpreadsheet/Worksheet/RowDimension.php | 115 + .../PhpSpreadsheet/Worksheet/RowIterator.php | 170 + .../PhpSpreadsheet/Worksheet/SheetView.php | 199 + .../PhpSpreadsheet/Worksheet/Worksheet.php | 3084 ++ .../src/PhpSpreadsheet/Writer/BaseWriter.php | 86 + .../src/PhpSpreadsheet/Writer/Csv.php | 342 + .../src/PhpSpreadsheet/Writer/Exception.php | 9 + .../src/PhpSpreadsheet/Writer/Html.php | 1690 + .../src/PhpSpreadsheet/Writer/IWriter.php | 93 + .../src/PhpSpreadsheet/Writer/Ods.php | 178 + .../Writer/Ods/Cell/Comment.php | 33 + .../src/PhpSpreadsheet/Writer/Ods/Content.php | 395 + .../src/PhpSpreadsheet/Writer/Ods/Meta.php | 77 + .../src/PhpSpreadsheet/Writer/Ods/MetaInf.php | 62 + .../PhpSpreadsheet/Writer/Ods/Mimetype.php | 20 + .../PhpSpreadsheet/Writer/Ods/Settings.php | 54 + .../src/PhpSpreadsheet/Writer/Ods/Styles.php | 70 + .../PhpSpreadsheet/Writer/Ods/Thumbnails.php | 20 + .../PhpSpreadsheet/Writer/Ods/WriterPart.php | 35 + .../src/PhpSpreadsheet/Writer/Pdf.php | 283 + .../src/PhpSpreadsheet/Writer/Pdf/Dompdf.php | 78 + .../src/PhpSpreadsheet/Writer/Pdf/Mpdf.php | 112 + .../src/PhpSpreadsheet/Writer/Pdf/Tcpdf.php | 98 + .../src/PhpSpreadsheet/Writer/Xls.php | 946 + .../PhpSpreadsheet/Writer/Xls/BIFFwriter.php | 224 + .../src/PhpSpreadsheet/Writer/Xls/Escher.php | 510 + .../src/PhpSpreadsheet/Writer/Xls/Font.php | 149 + .../src/PhpSpreadsheet/Writer/Xls/Parser.php | 1438 + .../PhpSpreadsheet/Writer/Xls/Workbook.php | 1150 + .../PhpSpreadsheet/Writer/Xls/Worksheet.php | 4484 ++ .../src/PhpSpreadsheet/Writer/Xls/Xf.php | 548 + .../src/PhpSpreadsheet/Writer/Xlsx.php | 558 + .../src/PhpSpreadsheet/Writer/Xlsx/Chart.php | 1541 + .../PhpSpreadsheet/Writer/Xlsx/Comments.php | 242 + .../Writer/Xlsx/ContentTypes.php | 249 + .../PhpSpreadsheet/Writer/Xlsx/DocProps.php | 251 + .../PhpSpreadsheet/Writer/Xlsx/Drawing.php | 519 + .../src/PhpSpreadsheet/Writer/Xlsx/Rels.php | 466 + .../PhpSpreadsheet/Writer/Xlsx/RelsRibbon.php | 49 + .../PhpSpreadsheet/Writer/Xlsx/RelsVBA.php | 44 + .../Writer/Xlsx/StringTable.php | 281 + .../src/PhpSpreadsheet/Writer/Xlsx/Style.php | 686 + .../src/PhpSpreadsheet/Writer/Xlsx/Theme.php | 846 + .../PhpSpreadsheet/Writer/Xlsx/Workbook.php | 426 + .../PhpSpreadsheet/Writer/Xlsx/Worksheet.php | 1241 + .../PhpSpreadsheet/Writer/Xlsx/WriterPart.php | 35 + vendor/pimple/pimple/.gitignore | 3 + vendor/pimple/pimple/.travis.yml | 40 + vendor/pimple/pimple/CHANGELOG | 59 + vendor/pimple/pimple/LICENSE | 19 + vendor/pimple/pimple/README.rst | 326 + vendor/pimple/pimple/composer.json | 29 + vendor/pimple/pimple/ext/pimple/.gitignore | 30 + vendor/pimple/pimple/ext/pimple/README.md | 12 + vendor/pimple/pimple/ext/pimple/config.m4 | 63 + vendor/pimple/pimple/ext/pimple/config.w32 | 13 + vendor/pimple/pimple/ext/pimple/php_pimple.h | 137 + vendor/pimple/pimple/ext/pimple/pimple.c | 1114 + .../pimple/pimple/ext/pimple/pimple_compat.h | 81 + .../pimple/pimple/ext/pimple/tests/001.phpt | 45 + .../pimple/pimple/ext/pimple/tests/002.phpt | 15 + .../pimple/pimple/ext/pimple/tests/003.phpt | 16 + .../pimple/pimple/ext/pimple/tests/004.phpt | 30 + .../pimple/pimple/ext/pimple/tests/005.phpt | 27 + .../pimple/pimple/ext/pimple/tests/006.phpt | 51 + .../pimple/pimple/ext/pimple/tests/007.phpt | 22 + .../pimple/pimple/ext/pimple/tests/008.phpt | 29 + .../pimple/pimple/ext/pimple/tests/009.phpt | 13 + .../pimple/pimple/ext/pimple/tests/010.phpt | 45 + .../pimple/pimple/ext/pimple/tests/011.phpt | 19 + .../pimple/pimple/ext/pimple/tests/012.phpt | 28 + .../pimple/pimple/ext/pimple/tests/013.phpt | 33 + .../pimple/pimple/ext/pimple/tests/014.phpt | 30 + .../pimple/pimple/ext/pimple/tests/015.phpt | 17 + .../pimple/pimple/ext/pimple/tests/016.phpt | 24 + .../pimple/pimple/ext/pimple/tests/017.phpt | 17 + .../pimple/pimple/ext/pimple/tests/017_1.phpt | 17 + .../pimple/pimple/ext/pimple/tests/018.phpt | 23 + .../pimple/pimple/ext/pimple/tests/019.phpt | 18 + .../pimple/pimple/ext/pimple/tests/bench.phpb | 51 + .../pimple/ext/pimple/tests/bench_shared.phpb | 25 + vendor/pimple/pimple/phpunit.xml.dist | 14 + vendor/pimple/pimple/src/Pimple/Container.php | 298 + .../Exception/ExpectedInvokableException.php | 38 + .../Exception/FrozenServiceException.php | 45 + .../InvalidServiceIdentifierException.php | 45 + .../Exception/UnknownIdentifierException.php | 45 + .../pimple/src/Pimple/Psr11/Container.php | 55 + .../src/Pimple/Psr11/ServiceLocator.php | 75 + .../pimple/src/Pimple/ServiceIterator.php | 69 + .../src/Pimple/ServiceProviderInterface.php | 46 + .../src/Pimple/Tests/Fixtures/Invokable.php | 38 + .../Pimple/Tests/Fixtures/NonInvokable.php | 34 + .../Tests/Fixtures/PimpleServiceProvider.php | 54 + .../src/Pimple/Tests/Fixtures/Service.php | 35 + .../PimpleServiceProviderInterfaceTest.php | 76 + .../pimple/src/Pimple/Tests/PimpleTest.php | 589 + .../src/Pimple/Tests/Psr11/ContainerTest.php | 77 + .../Pimple/Tests/Psr11/ServiceLocatorTest.php | 134 + .../src/Pimple/Tests/ServiceIteratorTest.php | 52 + vendor/psr/cache/CHANGELOG.md | 16 + vendor/psr/cache/LICENSE.txt | 19 + vendor/psr/cache/README.md | 9 + vendor/psr/cache/composer.json | 25 + vendor/psr/cache/src/CacheException.php | 10 + vendor/psr/cache/src/CacheItemInterface.php | 105 + .../psr/cache/src/CacheItemPoolInterface.php | 138 + .../cache/src/InvalidArgumentException.php | 13 + vendor/psr/container/.gitignore | 3 + vendor/psr/container/LICENSE | 21 + vendor/psr/container/README.md | 5 + vendor/psr/container/composer.json | 27 + .../src/ContainerExceptionInterface.php | 13 + .../psr/container/src/ContainerInterface.php | 37 + .../src/NotFoundExceptionInterface.php | 13 + vendor/psr/http-message/CHANGELOG.md | 36 + vendor/psr/http-message/LICENSE | 19 + vendor/psr/http-message/README.md | 13 + vendor/psr/http-message/composer.json | 26 + .../psr/http-message/src/MessageInterface.php | 187 + .../psr/http-message/src/RequestInterface.php | 129 + .../http-message/src/ResponseInterface.php | 68 + .../src/ServerRequestInterface.php | 261 + .../psr/http-message/src/StreamInterface.php | 158 + .../src/UploadedFileInterface.php | 123 + vendor/psr/http-message/src/UriInterface.php | 323 + vendor/psr/log/LICENSE | 19 + vendor/psr/log/Psr/Log/AbstractLogger.php | 128 + .../log/Psr/Log/InvalidArgumentException.php | 7 + vendor/psr/log/Psr/Log/LogLevel.php | 18 + .../psr/log/Psr/Log/LoggerAwareInterface.php | 18 + vendor/psr/log/Psr/Log/LoggerAwareTrait.php | 26 + vendor/psr/log/Psr/Log/LoggerInterface.php | 125 + vendor/psr/log/Psr/Log/LoggerTrait.php | 142 + vendor/psr/log/Psr/Log/NullLogger.php | 30 + vendor/psr/log/Psr/Log/Test/DummyTest.php | 18 + .../log/Psr/Log/Test/LoggerInterfaceTest.php | 138 + vendor/psr/log/Psr/Log/Test/TestLogger.php | 147 + vendor/psr/log/README.md | 58 + vendor/psr/log/composer.json | 26 + vendor/psr/simple-cache/.editorconfig | 12 + vendor/psr/simple-cache/LICENSE.md | 21 + vendor/psr/simple-cache/README.md | 8 + vendor/psr/simple-cache/composer.json | 25 + .../psr/simple-cache/src/CacheException.php | 10 + .../psr/simple-cache/src/CacheInterface.php | 114 + .../src/InvalidArgumentException.php | 13 + vendor/ralouphie/getallheaders/LICENSE | 21 + vendor/ralouphie/getallheaders/README.md | 27 + vendor/ralouphie/getallheaders/composer.json | 26 + .../getallheaders/src/getallheaders.php | 46 + vendor/symfony/cache-contracts/.gitignore | 3 + .../cache-contracts/CacheInterface.php | 57 + vendor/symfony/cache-contracts/CacheTrait.php | 80 + .../cache-contracts/CallbackInterface.php | 30 + .../symfony/cache-contracts/ItemInterface.php | 65 + vendor/symfony/cache-contracts/LICENSE | 19 + vendor/symfony/cache-contracts/README.md | 9 + .../TagAwareCacheInterface.php | 38 + vendor/symfony/cache-contracts/composer.json | 38 + .../symfony/cache/Adapter/AbstractAdapter.php | 210 + .../cache/Adapter/AbstractTagAwareAdapter.php | 334 + .../cache/Adapter/AdapterInterface.php | 49 + vendor/symfony/cache/Adapter/ApcuAdapter.php | 27 + vendor/symfony/cache/Adapter/ArrayAdapter.php | 176 + vendor/symfony/cache/Adapter/ChainAdapter.php | 333 + .../symfony/cache/Adapter/DoctrineAdapter.php | 27 + .../cache/Adapter/FilesystemAdapter.php | 29 + .../Adapter/FilesystemTagAwareAdapter.php | 239 + .../cache/Adapter/MemcachedAdapter.php | 37 + vendor/symfony/cache/Adapter/NullAdapter.php | 156 + vendor/symfony/cache/Adapter/PdoAdapter.php | 54 + .../symfony/cache/Adapter/PhpArrayAdapter.php | 339 + .../symfony/cache/Adapter/PhpFilesAdapter.php | 38 + vendor/symfony/cache/Adapter/ProxyAdapter.php | 270 + vendor/symfony/cache/Adapter/Psr16Adapter.php | 86 + vendor/symfony/cache/Adapter/RedisAdapter.php | 32 + .../cache/Adapter/RedisTagAwareAdapter.php | 321 + .../cache/Adapter/SimpleCacheAdapter.php | 21 + .../symfony/cache/Adapter/TagAwareAdapter.php | 445 + .../Adapter/TagAwareAdapterInterface.php | 33 + .../cache/Adapter/TraceableAdapter.php | 298 + .../Adapter/TraceableTagAwareAdapter.php | 38 + vendor/symfony/cache/CHANGELOG.md | 73 + vendor/symfony/cache/CacheItem.php | 204 + .../DataCollector/CacheDataCollector.php | 194 + .../CacheCollectorPass.php | 81 + .../CachePoolClearerPass.php | 48 + .../DependencyInjection/CachePoolPass.php | 228 + .../CachePoolPrunerPass.php | 60 + vendor/symfony/cache/DoctrineProvider.php | 120 + .../cache/Exception/CacheException.php | 25 + .../Exception/InvalidArgumentException.php | 25 + .../cache/Exception/LogicException.php | 25 + vendor/symfony/cache/LICENSE | 19 + vendor/symfony/cache/LockRegistry.php | 161 + .../cache/Marshaller/DefaultMarshaller.php | 99 + .../cache/Marshaller/DeflateMarshaller.php | 53 + .../cache/Marshaller/MarshallerInterface.php | 40 + .../cache/Marshaller/TagAwareMarshaller.php | 89 + vendor/symfony/cache/PruneableInterface.php | 23 + vendor/symfony/cache/Psr16Cache.php | 284 + vendor/symfony/cache/README.md | 19 + vendor/symfony/cache/ResettableInterface.php | 21 + vendor/symfony/cache/Simple/AbstractCache.php | 199 + vendor/symfony/cache/Simple/ApcuCache.php | 31 + vendor/symfony/cache/Simple/ArrayCache.php | 167 + vendor/symfony/cache/Simple/ChainCache.php | 271 + vendor/symfony/cache/Simple/DoctrineCache.php | 34 + .../symfony/cache/Simple/FilesystemCache.php | 36 + .../symfony/cache/Simple/MemcachedCache.php | 34 + vendor/symfony/cache/Simple/NullCache.php | 104 + vendor/symfony/cache/Simple/PdoCache.php | 59 + vendor/symfony/cache/Simple/PhpArrayCache.php | 256 + vendor/symfony/cache/Simple/PhpFilesCache.php | 45 + vendor/symfony/cache/Simple/Psr6Cache.php | 23 + vendor/symfony/cache/Simple/RedisCache.php | 37 + .../symfony/cache/Simple/TraceableCache.php | 258 + .../cache/Traits/AbstractAdapterTrait.php | 164 + vendor/symfony/cache/Traits/AbstractTrait.php | 320 + vendor/symfony/cache/Traits/ApcuTrait.php | 128 + vendor/symfony/cache/Traits/ArrayTrait.php | 184 + .../symfony/cache/Traits/ContractsTrait.php | 109 + vendor/symfony/cache/Traits/DoctrineTrait.php | 98 + .../cache/Traits/FilesystemCommonTrait.php | 196 + .../symfony/cache/Traits/FilesystemTrait.php | 124 + .../symfony/cache/Traits/MemcachedTrait.php | 343 + vendor/symfony/cache/Traits/PdoTrait.php | 505 + vendor/symfony/cache/Traits/PhpArrayTrait.php | 169 + vendor/symfony/cache/Traits/PhpFilesTrait.php | 313 + vendor/symfony/cache/Traits/ProxyTrait.php | 43 + .../cache/Traits/RedisClusterNodeProxy.php | 53 + .../cache/Traits/RedisClusterProxy.php | 63 + vendor/symfony/cache/Traits/RedisProxy.php | 65 + vendor/symfony/cache/Traits/RedisTrait.php | 561 + vendor/symfony/cache/composer.json | 58 + .../event-dispatcher-contracts/.gitignore | 3 + .../event-dispatcher-contracts/Event.php | 96 + .../EventDispatcherInterface.php | 58 + .../event-dispatcher-contracts/LICENSE | 19 + .../event-dispatcher-contracts/README.md | 9 + .../event-dispatcher-contracts/composer.json | 38 + vendor/symfony/event-dispatcher/CHANGELOG.md | 67 + .../Debug/TraceableEventDispatcher.php | 407 + .../TraceableEventDispatcherInterface.php | 42 + .../Debug/WrappedListener.php | 136 + .../AddEventAliasesPass.php | 42 + .../RegisterListenersPass.php | 178 + vendor/symfony/event-dispatcher/Event.php | 38 + .../event-dispatcher/EventDispatcher.php | 314 + .../EventDispatcherInterface.php | 82 + .../EventSubscriberInterface.php | 49 + .../symfony/event-dispatcher/GenericEvent.php | 184 + .../ImmutableEventDispatcher.php | 102 + vendor/symfony/event-dispatcher/LICENSE | 19 + .../LegacyEventDispatcherProxy.php | 147 + .../event-dispatcher/LegacyEventProxy.php | 62 + vendor/symfony/event-dispatcher/README.md | 15 + vendor/symfony/event-dispatcher/composer.json | 51 + vendor/symfony/finder/CHANGELOG.md | 74 + .../symfony/finder/Comparator/Comparator.php | 98 + .../finder/Comparator/DateComparator.php | 51 + .../finder/Comparator/NumberComparator.php | 79 + .../Exception/AccessDeniedException.php | 19 + .../Exception/DirectoryNotFoundException.php | 19 + vendor/symfony/finder/Finder.php | 823 + vendor/symfony/finder/Gitignore.php | 83 + vendor/symfony/finder/Glob.php | 116 + .../finder/Iterator/CustomFilterIterator.php | 62 + .../Iterator/DateRangeFilterIterator.php | 59 + .../Iterator/DepthRangeFilterIterator.php | 46 + .../ExcludeDirectoryFilterIterator.php | 93 + .../Iterator/FileTypeFilterIterator.php | 54 + .../Iterator/FilecontentFilterIterator.php | 59 + .../Iterator/FilenameFilterIterator.php | 48 + .../symfony/finder/Iterator/LazyIterator.php | 32 + .../Iterator/MultiplePcreFilterIterator.php | 118 + .../finder/Iterator/PathFilterIterator.php | 57 + .../Iterator/RecursiveDirectoryIterator.php | 149 + .../Iterator/SizeRangeFilterIterator.php | 58 + .../finder/Iterator/SortableIterator.php | 101 + vendor/symfony/finder/LICENSE | 19 + vendor/symfony/finder/README.md | 14 + vendor/symfony/finder/SplFileInfo.php | 85 + vendor/symfony/finder/composer.json | 29 + .../symfony/http-foundation/AcceptHeader.php | 176 + .../http-foundation/AcceptHeaderItem.php | 191 + .../symfony/http-foundation/ApacheRequest.php | 47 + .../http-foundation/BinaryFileResponse.php | 362 + vendor/symfony/http-foundation/CHANGELOG.md | 237 + vendor/symfony/http-foundation/Cookie.php | 309 + .../Exception/ConflictingHeadersException.php | 21 + .../Exception/RequestExceptionInterface.php | 21 + .../SuspiciousOperationException.php | 20 + .../ExpressionRequestMatcher.php | 47 + .../File/Exception/AccessDeniedException.php | 25 + .../Exception/CannotWriteFileException.php | 21 + .../File/Exception/ExtensionFileException.php | 21 + .../File/Exception/FileException.php | 21 + .../File/Exception/FileNotFoundException.php | 25 + .../File/Exception/FormSizeFileException.php | 21 + .../File/Exception/IniSizeFileException.php | 21 + .../File/Exception/NoFileException.php | 21 + .../File/Exception/NoTmpDirFileException.php | 21 + .../File/Exception/PartialFileException.php | 21 + .../Exception/UnexpectedTypeException.php | 20 + .../File/Exception/UploadException.php | 21 + vendor/symfony/http-foundation/File/File.php | 135 + .../File/MimeType/ExtensionGuesser.php | 102 + .../MimeType/ExtensionGuesserInterface.php | 31 + .../MimeType/FileBinaryMimeTypeGuesser.php | 104 + .../File/MimeType/FileinfoMimeTypeGuesser.php | 80 + .../MimeType/MimeTypeExtensionGuesser.php | 826 + .../File/MimeType/MimeTypeGuesser.php | 138 + .../MimeType/MimeTypeGuesserInterface.php | 38 + .../symfony/http-foundation/File/Stream.php | 31 + .../http-foundation/File/UploadedFile.php | 310 + vendor/symfony/http-foundation/FileBag.php | 142 + vendor/symfony/http-foundation/HeaderBag.php | 319 + .../symfony/http-foundation/HeaderUtils.php | 237 + vendor/symfony/http-foundation/IpUtils.php | 192 + .../symfony/http-foundation/JsonResponse.php | 221 + vendor/symfony/http-foundation/LICENSE | 19 + .../symfony/http-foundation/ParameterBag.php | 229 + vendor/symfony/http-foundation/README.md | 14 + .../http-foundation/RedirectResponse.php | 114 + vendor/symfony/http-foundation/Request.php | 2085 + .../http-foundation/RequestMatcher.php | 199 + .../RequestMatcherInterface.php | 27 + .../symfony/http-foundation/RequestStack.php | 99 + vendor/symfony/http-foundation/Response.php | 1260 + .../http-foundation/ResponseHeaderBag.php | 310 + vendor/symfony/http-foundation/ServerBag.php | 99 + .../Session/Attribute/AttributeBag.php | 150 + .../Attribute/AttributeBagInterface.php | 67 + .../Attribute/NamespacedAttributeBag.php | 159 + .../Session/Flash/AutoExpireFlashBag.php | 161 + .../Session/Flash/FlashBag.php | 152 + .../Session/Flash/FlashBagInterface.php | 93 + .../http-foundation/Session/Session.php | 275 + .../Session/SessionBagInterface.php | 46 + .../Session/SessionBagProxy.php | 83 + .../Session/SessionInterface.php | 178 + .../http-foundation/Session/SessionUtils.php | 59 + .../Handler/AbstractSessionHandler.php | 162 + .../Handler/MemcachedSessionHandler.php | 122 + .../Handler/MigratingSessionHandler.php | 132 + .../Storage/Handler/MongoDbSessionHandler.php | 188 + .../Handler/NativeFileSessionHandler.php | 55 + .../Storage/Handler/NullSessionHandler.php | 80 + .../Storage/Handler/PdoSessionHandler.php | 906 + .../Storage/Handler/RedisSessionHandler.php | 125 + .../Storage/Handler/SessionHandlerFactory.php | 87 + .../Storage/Handler/StrictSessionHandler.php | 108 + .../Session/Storage/MetadataBag.php | 168 + .../Storage/MockArraySessionStorage.php | 252 + .../Storage/MockFileSessionStorage.php | 161 + .../Session/Storage/NativeSessionStorage.php | 476 + .../Storage/PhpBridgeSessionStorage.php | 64 + .../Session/Storage/Proxy/AbstractProxy.php | 122 + .../Storage/Proxy/SessionHandlerProxy.php | 109 + .../Storage/SessionStorageInterface.php | 137 + .../http-foundation/StreamedResponse.php | 142 + .../Constraint/RequestAttributeValueSame.php | 55 + .../Constraint/ResponseCookieValueSame.php | 85 + .../Test/Constraint/ResponseHasCookie.php | 77 + .../Test/Constraint/ResponseHasHeader.php | 53 + .../Test/Constraint/ResponseHeaderSame.php | 55 + .../Test/Constraint/ResponseIsRedirected.php | 56 + .../Test/Constraint/ResponseIsSuccessful.php | 56 + .../Constraint/ResponseStatusCodeSame.php | 63 + vendor/symfony/http-foundation/UrlHelper.php | 102 + vendor/symfony/http-foundation/composer.json | 35 + vendor/symfony/mime/Address.php | 135 + vendor/symfony/mime/BodyRendererInterface.php | 20 + vendor/symfony/mime/CHANGELOG.md | 20 + vendor/symfony/mime/CharacterStream.php | 221 + vendor/symfony/mime/Crypto/SMime.php | 111 + vendor/symfony/mime/Crypto/SMimeEncrypter.php | 63 + vendor/symfony/mime/Crypto/SMimeSigner.php | 65 + .../AddMimeTypeGuesserPass.php | 46 + vendor/symfony/mime/Email.php | 616 + .../mime/Encoder/AddressEncoderInterface.php | 28 + .../mime/Encoder/Base64ContentEncoder.php | 48 + vendor/symfony/mime/Encoder/Base64Encoder.php | 41 + .../mime/Encoder/Base64MimeHeaderEncoder.php | 43 + .../mime/Encoder/ContentEncoderInterface.php | 30 + .../mime/Encoder/EightBitContentEncoder.php | 35 + .../symfony/mime/Encoder/EncoderInterface.php | 26 + .../mime/Encoder/IdnAddressEncoder.php | 52 + .../Encoder/MimeHeaderEncoderInterface.php | 23 + .../symfony/mime/Encoder/QpContentEncoder.php | 64 + vendor/symfony/mime/Encoder/QpEncoder.php | 195 + .../mime/Encoder/QpMimeHeaderEncoder.php | 40 + .../symfony/mime/Encoder/Rfc2231Encoder.php | 50 + .../Exception/AddressEncoderException.php | 19 + .../mime/Exception/ExceptionInterface.php | 19 + .../Exception/InvalidArgumentException.php | 19 + .../symfony/mime/Exception/LogicException.php | 19 + .../mime/Exception/RfcComplianceException.php | 19 + .../mime/Exception/RuntimeException.php | 19 + .../mime/FileBinaryMimeTypeGuesser.php | 93 + .../symfony/mime/FileinfoMimeTypeGuesser.php | 69 + vendor/symfony/mime/Header/AbstractHeader.php | 279 + vendor/symfony/mime/Header/DateHeader.php | 66 + .../symfony/mime/Header/HeaderInterface.php | 65 + vendor/symfony/mime/Header/Headers.php | 282 + .../mime/Header/IdentificationHeader.php | 110 + vendor/symfony/mime/Header/MailboxHeader.php | 85 + .../symfony/mime/Header/MailboxListHeader.php | 136 + .../mime/Header/ParameterizedHeader.php | 191 + vendor/symfony/mime/Header/PathHeader.php | 62 + .../mime/Header/UnstructuredHeader.php | 69 + vendor/symfony/mime/LICENSE | 19 + vendor/symfony/mime/Message.php | 158 + vendor/symfony/mime/MessageConverter.php | 125 + .../symfony/mime/MimeTypeGuesserInterface.php | 35 + vendor/symfony/mime/MimeTypes.php | 3154 ++ vendor/symfony/mime/MimeTypesInterface.php | 32 + .../mime/Part/AbstractMultipartPart.php | 99 + vendor/symfony/mime/Part/AbstractPart.php | 65 + vendor/symfony/mime/Part/DataPart.php | 176 + vendor/symfony/mime/Part/MessagePart.php | 62 + .../mime/Part/Multipart/AlternativePart.php | 25 + .../mime/Part/Multipart/DigestPart.php | 31 + .../mime/Part/Multipart/FormDataPart.php | 103 + .../symfony/mime/Part/Multipart/MixedPart.php | 25 + .../mime/Part/Multipart/RelatedPart.php | 55 + vendor/symfony/mime/Part/SMimePart.php | 121 + vendor/symfony/mime/Part/TextPart.php | 209 + vendor/symfony/mime/README.md | 13 + vendor/symfony/mime/RawMessage.php | 91 + .../mime/Resources/bin/update_mime_types.php | 166 + .../Test/Constraint/EmailAddressContains.php | 74 + .../Test/Constraint/EmailAttachmentCount.php | 60 + .../mime/Test/Constraint/EmailHasHeader.php | 57 + .../mime/Test/Constraint/EmailHeaderSame.php | 69 + .../Test/Constraint/EmailHtmlBodyContains.php | 58 + .../Test/Constraint/EmailTextBodyContains.php | 58 + vendor/symfony/mime/composer.json | 39 + vendor/symfony/polyfill-intl-idn/Idn.php | 925 + vendor/symfony/polyfill-intl-idn/Info.php | 23 + vendor/symfony/polyfill-intl-idn/LICENSE | 19 + vendor/symfony/polyfill-intl-idn/README.md | 12 + .../Resources/unidata/DisallowedRanges.php | 375 + .../Resources/unidata/Regex.php | 24 + .../Resources/unidata/deviation.php | 8 + .../Resources/unidata/disallowed.php | 2638 ++ .../unidata/disallowed_STD3_mapped.php | 308 + .../unidata/disallowed_STD3_valid.php | 71 + .../Resources/unidata/ignored.php | 273 + .../Resources/unidata/mapped.php | 5778 +++ .../Resources/unidata/virama.php | 65 + .../symfony/polyfill-intl-idn/bootstrap.php | 145 + .../symfony/polyfill-intl-idn/bootstrap80.php | 125 + .../symfony/polyfill-intl-idn/composer.json | 44 + .../symfony/polyfill-intl-normalizer/LICENSE | 19 + .../polyfill-intl-normalizer/Normalizer.php | 310 + .../polyfill-intl-normalizer/README.md | 14 + .../Resources/stubs/Normalizer.php | 17 + .../unidata/canonicalComposition.php | 945 + .../unidata/canonicalDecomposition.php | 2065 + .../Resources/unidata/combiningClass.php | 876 + .../unidata/compatibilityDecomposition.php | 3695 ++ .../polyfill-intl-normalizer/bootstrap.php | 23 + .../polyfill-intl-normalizer/bootstrap80.php | 19 + .../polyfill-intl-normalizer/composer.json | 39 + vendor/symfony/polyfill-mbstring/LICENSE | 19 + vendor/symfony/polyfill-mbstring/Mbstring.php | 873 + vendor/symfony/polyfill-mbstring/README.md | 13 + .../Resources/unidata/lowerCase.php | 1397 + .../Resources/unidata/titleCaseRegexp.php | 5 + .../Resources/unidata/upperCase.php | 1489 + .../symfony/polyfill-mbstring/bootstrap.php | 147 + .../symfony/polyfill-mbstring/bootstrap80.php | 143 + .../symfony/polyfill-mbstring/composer.json | 41 + vendor/symfony/polyfill-php72/LICENSE | 19 + vendor/symfony/polyfill-php72/Php72.php | 217 + vendor/symfony/polyfill-php72/README.md | 28 + vendor/symfony/polyfill-php72/bootstrap.php | 57 + vendor/symfony/polyfill-php72/composer.json | 35 + vendor/symfony/polyfill-php73/LICENSE | 19 + vendor/symfony/polyfill-php73/Php73.php | 43 + vendor/symfony/polyfill-php73/README.md | 18 + .../Resources/stubs/JsonException.php | 16 + vendor/symfony/polyfill-php73/bootstrap.php | 31 + vendor/symfony/polyfill-php73/composer.json | 36 + vendor/symfony/polyfill-php80/LICENSE | 19 + vendor/symfony/polyfill-php80/Php80.php | 115 + vendor/symfony/polyfill-php80/PhpToken.php | 103 + vendor/symfony/polyfill-php80/README.md | 24 + .../Resources/stubs/Attribute.php | 22 + .../Resources/stubs/PhpToken.php | 7 + .../Resources/stubs/Stringable.php | 11 + .../Resources/stubs/UnhandledMatchError.php | 7 + .../Resources/stubs/ValueError.php | 7 + vendor/symfony/polyfill-php80/bootstrap.php | 42 + vendor/symfony/polyfill-php80/composer.json | 40 + .../psr-http-message-bridge/.gitignore | 5 + .../psr-http-message-bridge/.php_cs.dist | 24 + .../psr-http-message-bridge/.travis.yml | 48 + .../psr-http-message-bridge/CHANGELOG.md | 40 + .../Factory/DiactorosFactory.php | 171 + .../Factory/HttpFoundationFactory.php | 240 + .../Factory/PsrHttpFactory.php | 168 + .../Factory/UploadedFile.php | 73 + .../HttpFoundationFactoryInterface.php | 39 + .../HttpMessageFactoryInterface.php | 39 + .../symfony/psr-http-message-bridge/LICENSE | 19 + .../symfony/psr-http-message-bridge/README.md | 20 + .../AbstractHttpMessageFactoryTest.php | 217 + .../Tests/Factory/DiactorosFactoryTest.php | 33 + .../Factory/HttpFoundationFactoryTest.php | 272 + .../Tests/Factory/PsrHttpFactoryTest.php | 30 + .../Tests/Fixtures/Message.php | 93 + .../Tests/Fixtures/Response.php | 45 + .../Tests/Fixtures/ServerRequest.php | 141 + .../Tests/Fixtures/Stream.php | 100 + .../Tests/Fixtures/UploadedFile.php | 65 + .../Tests/Fixtures/Uri.php | 135 + .../Tests/Functional/CovertTest.php | 234 + .../psr-http-message-bridge/composer.json | 42 + .../psr-http-message-bridge/phpunit.xml.dist | 29 + vendor/symfony/service-contracts/.gitignore | 3 + vendor/symfony/service-contracts/LICENSE | 19 + vendor/symfony/service-contracts/README.md | 9 + .../service-contracts/ResetInterface.php | 30 + .../service-contracts/ServiceLocatorTrait.php | 128 + .../ServiceProviderInterface.php | 36 + .../ServiceSubscriberInterface.php | 53 + .../ServiceSubscriberTrait.php | 78 + .../Test/ServiceLocatorTest.php | 95 + .../symfony/service-contracts/composer.json | 38 + vendor/symfony/var-exporter/CHANGELOG.md | 7 + .../Exception/ClassNotFoundException.php | 20 + .../Exception/ExceptionInterface.php | 16 + .../NotInstantiableTypeException.php | 20 + vendor/symfony/var-exporter/Instantiator.php | 94 + .../var-exporter/Internal/Exporter.php | 413 + .../var-exporter/Internal/Hydrator.php | 152 + .../var-exporter/Internal/Reference.php | 30 + .../var-exporter/Internal/Registry.php | 146 + .../symfony/var-exporter/Internal/Values.php | 27 + vendor/symfony/var-exporter/LICENSE | 19 + vendor/symfony/var-exporter/README.md | 38 + vendor/symfony/var-exporter/VarExporter.php | 114 + vendor/symfony/var-exporter/composer.json | 32 + vendor/topthink/think-captcha/.gitignore | 3 + vendor/topthink/think-captcha/LICENSE | 32 + vendor/topthink/think-captcha/README.md | 33 + .../topthink/think-captcha/assets/bgs/1.jpg | Bin 0 -> 30428 bytes .../topthink/think-captcha/assets/bgs/2.jpg | Bin 0 -> 29677 bytes .../topthink/think-captcha/assets/bgs/3.jpg | Bin 0 -> 32109 bytes .../topthink/think-captcha/assets/bgs/4.jpg | Bin 0 -> 29081 bytes .../topthink/think-captcha/assets/bgs/5.jpg | Bin 0 -> 27940 bytes .../topthink/think-captcha/assets/bgs/6.jpg | Bin 0 -> 31381 bytes .../topthink/think-captcha/assets/bgs/7.jpg | Bin 0 -> 30234 bytes .../topthink/think-captcha/assets/bgs/8.jpg | Bin 0 -> 30188 bytes .../topthink/think-captcha/assets/ttfs/1.ttf | Bin 0 -> 57520 bytes .../topthink/think-captcha/assets/ttfs/2.ttf | Bin 0 -> 28328 bytes .../topthink/think-captcha/assets/ttfs/3.ttf | Bin 0 -> 39308 bytes .../topthink/think-captcha/assets/ttfs/4.ttf | Bin 0 -> 34852 bytes .../topthink/think-captcha/assets/ttfs/5.ttf | Bin 0 -> 32664 bytes .../topthink/think-captcha/assets/ttfs/6.ttf | Bin 0 -> 28036 bytes .../think-captcha/assets/zhttfs/1.ttf | Bin 0 -> 2183628 bytes vendor/topthink/think-captcha/composer.json | 20 + vendor/topthink/think-captcha/src/Captcha.php | 320 + .../think-captcha/src/CaptchaController.php | 23 + vendor/topthink/think-captcha/src/helper.php | 64 + vendor/topthink/think-helper/.gitignore | 2 + vendor/topthink/think-helper/LICENSE | 201 + vendor/topthink/think-helper/README.md | 92 + vendor/topthink/think-helper/composer.json | 19 + vendor/topthink/think-helper/src/Arr.php | 41 + vendor/topthink/think-helper/src/Hash.php | 48 + vendor/topthink/think-helper/src/Str.php | 202 + vendor/topthink/think-helper/src/Time.php | 203 + .../topthink/think-helper/src/hash/Bcrypt.php | 51 + vendor/topthink/think-helper/src/hash/Md5.php | 42 + vendor/topthink/think-helper/src/helper.php | 93 + vendor/topthink/think-installer/.gitignore | 3 + vendor/topthink/think-installer/composer.json | 25 + .../think-installer/src/LibraryInstaller.php | 28 + .../topthink/think-installer/src/Plugin.php | 34 + .../topthink/think-installer/src/Promise.php | 11 + .../think-installer/src/ThinkExtend.php | 77 + .../think-installer/src/ThinkFramework.php | 62 + .../think-installer/src/ThinkTesting.php | 64 + vendor/topthink/think-queue/.gitignore | 4 + vendor/topthink/think-queue/LICENSE | 201 + vendor/topthink/think-queue/README.md | 132 + vendor/topthink/think-queue/composer.json | 32 + vendor/topthink/think-queue/src/Queue.php | 49 + vendor/topthink/think-queue/src/common.php | 36 + vendor/topthink/think-queue/src/config.php | 14 + .../src/queue/CallQueuedHandler.php | 36 + .../think-queue/src/queue/Connector.php | 69 + vendor/topthink/think-queue/src/queue/Job.php | 213 + .../think-queue/src/queue/Listener.php | 164 + .../think-queue/src/queue/Queueable.php | 46 + .../think-queue/src/queue/ShouldQueue.php | 17 + .../topthink/think-queue/src/queue/Worker.php | 119 + .../think-queue/src/queue/command/Listen.php | 65 + .../think-queue/src/queue/command/Restart.php | 31 + .../src/queue/command/Subscribe.php | 46 + .../think-queue/src/queue/command/Work.php | 210 + .../src/queue/connector/Database.php | 171 + .../think-queue/src/queue/connector/Redis.php | 236 + .../think-queue/src/queue/connector/Sync.php | 57 + .../src/queue/connector/Topthink.php | 225 + .../think-queue/src/queue/job/Database.php | 88 + .../think-queue/src/queue/job/Redis.php | 92 + .../think-queue/src/queue/job/Sync.php | 56 + .../think-queue/src/queue/job/Topthink.php | 85 + vendor/txthinking/mailer/LICENSE | 20 + vendor/txthinking/mailer/composer.json | 38 + vendor/txthinking/mailer/src/Mailer.php | 197 + .../src/Mailer/Exceptions/CodeException.php | 40 + .../src/Mailer/Exceptions/CryptoException.php | 32 + .../src/Mailer/Exceptions/SMTPException.php | 35 + .../src/Mailer/Exceptions/SendException.php | 31 + .../txthinking/mailer/src/Mailer/Message.php | 454 + vendor/txthinking/mailer/src/Mailer/SMTP.php | 463 + 2217 files changed, 438950 insertions(+), 184 deletions(-) delete mode 100644 public/1.html delete mode 100644 public/sign.js delete mode 100644 public/solidityCode.js delete mode 100644 public/web3.min.js delete mode 100644 runtime/.gitkeep create mode 100644 thinkphp/.gitignore create mode 100644 thinkphp/.htaccess create mode 100644 thinkphp/.travis.yml create mode 100644 thinkphp/CONTRIBUTING.md create mode 100644 thinkphp/LICENSE.txt create mode 100644 thinkphp/README.md create mode 100644 thinkphp/base.php create mode 100644 thinkphp/codecov.yml create mode 100644 thinkphp/composer.json create mode 100644 thinkphp/console.php create mode 100644 thinkphp/convention.php create mode 100644 thinkphp/helper.php create mode 100644 thinkphp/lang/zh-cn.php create mode 100644 thinkphp/library/think/App.php create mode 100644 thinkphp/library/think/Build.php create mode 100644 thinkphp/library/think/Cache.php create mode 100644 thinkphp/library/think/Collection.php create mode 100644 thinkphp/library/think/Config.php create mode 100644 thinkphp/library/think/Console.php create mode 100644 thinkphp/library/think/Controller.php create mode 100644 thinkphp/library/think/Cookie.php create mode 100644 thinkphp/library/think/Db.php create mode 100644 thinkphp/library/think/Debug.php create mode 100644 thinkphp/library/think/Env.php create mode 100644 thinkphp/library/think/Error.php create mode 100644 thinkphp/library/think/Exception.php create mode 100644 thinkphp/library/think/File.php create mode 100644 thinkphp/library/think/Hook.php create mode 100644 thinkphp/library/think/Lang.php create mode 100644 thinkphp/library/think/Loader.php create mode 100644 thinkphp/library/think/Log.php create mode 100644 thinkphp/library/think/Model.php create mode 100644 thinkphp/library/think/Paginator.php create mode 100644 thinkphp/library/think/Process.php create mode 100644 thinkphp/library/think/Request.php create mode 100644 thinkphp/library/think/Response.php create mode 100644 thinkphp/library/think/Route.php create mode 100644 thinkphp/library/think/Session.php create mode 100644 thinkphp/library/think/Template.php create mode 100644 thinkphp/library/think/Url.php create mode 100644 thinkphp/library/think/Validate.php create mode 100644 thinkphp/library/think/View.php create mode 100644 thinkphp/library/think/cache/Driver.php create mode 100644 thinkphp/library/think/cache/driver/File.php create mode 100644 thinkphp/library/think/cache/driver/Lite.php create mode 100644 thinkphp/library/think/cache/driver/Memcache.php create mode 100644 thinkphp/library/think/cache/driver/Memcached.php create mode 100644 thinkphp/library/think/cache/driver/Redis.php create mode 100644 thinkphp/library/think/cache/driver/Sqlite.php create mode 100644 thinkphp/library/think/cache/driver/Wincache.php create mode 100644 thinkphp/library/think/cache/driver/Xcache.php create mode 100644 thinkphp/library/think/config/driver/Ini.php create mode 100644 thinkphp/library/think/config/driver/Json.php create mode 100644 thinkphp/library/think/config/driver/Xml.php create mode 100644 thinkphp/library/think/console/Command.php create mode 100644 thinkphp/library/think/console/Input.php create mode 100644 thinkphp/library/think/console/LICENSE create mode 100644 thinkphp/library/think/console/Output.php create mode 100644 thinkphp/library/think/console/bin/README.md create mode 100644 thinkphp/library/think/console/bin/hiddeninput.exe create mode 100644 thinkphp/library/think/console/command/Build.php create mode 100644 thinkphp/library/think/console/command/Clear.php create mode 100644 thinkphp/library/think/console/command/Help.php create mode 100644 thinkphp/library/think/console/command/Lists.php create mode 100644 thinkphp/library/think/console/command/Make.php create mode 100644 thinkphp/library/think/console/command/make/Controller.php create mode 100644 thinkphp/library/think/console/command/make/Model.php create mode 100644 thinkphp/library/think/console/command/make/stubs/controller.plain.stub create mode 100644 thinkphp/library/think/console/command/make/stubs/controller.stub create mode 100644 thinkphp/library/think/console/command/make/stubs/model.stub create mode 100644 thinkphp/library/think/console/command/optimize/Autoload.php create mode 100644 thinkphp/library/think/console/command/optimize/Config.php create mode 100644 thinkphp/library/think/console/command/optimize/Route.php create mode 100644 thinkphp/library/think/console/command/optimize/Schema.php create mode 100644 thinkphp/library/think/console/input/Argument.php create mode 100644 thinkphp/library/think/console/input/Definition.php create mode 100644 thinkphp/library/think/console/input/Option.php create mode 100644 thinkphp/library/think/console/output/Ask.php create mode 100644 thinkphp/library/think/console/output/Descriptor.php create mode 100644 thinkphp/library/think/console/output/Formatter.php create mode 100644 thinkphp/library/think/console/output/Question.php create mode 100644 thinkphp/library/think/console/output/descriptor/Console.php create mode 100644 thinkphp/library/think/console/output/driver/Buffer.php create mode 100644 thinkphp/library/think/console/output/driver/Console.php create mode 100644 thinkphp/library/think/console/output/driver/Nothing.php create mode 100644 thinkphp/library/think/console/output/formatter/Stack.php create mode 100644 thinkphp/library/think/console/output/formatter/Style.php create mode 100644 thinkphp/library/think/console/output/question/Choice.php create mode 100644 thinkphp/library/think/console/output/question/Confirmation.php create mode 100644 thinkphp/library/think/controller/Rest.php create mode 100644 thinkphp/library/think/controller/Yar.php create mode 100644 thinkphp/library/think/db/Builder.php create mode 100644 thinkphp/library/think/db/Connection.php create mode 100644 thinkphp/library/think/db/Expression.php create mode 100644 thinkphp/library/think/db/Query.php create mode 100644 thinkphp/library/think/db/builder/Mysql.php create mode 100644 thinkphp/library/think/db/builder/Pgsql.php create mode 100644 thinkphp/library/think/db/builder/Sqlite.php create mode 100644 thinkphp/library/think/db/builder/Sqlsrv.php create mode 100644 thinkphp/library/think/db/connector/Mysql.php create mode 100644 thinkphp/library/think/db/connector/Pgsql.php create mode 100644 thinkphp/library/think/db/connector/Sqlite.php create mode 100644 thinkphp/library/think/db/connector/Sqlsrv.php create mode 100644 thinkphp/library/think/db/connector/pgsql.sql create mode 100644 thinkphp/library/think/db/exception/BindParamException.php create mode 100644 thinkphp/library/think/db/exception/DataNotFoundException.php create mode 100644 thinkphp/library/think/db/exception/ModelNotFoundException.php create mode 100644 thinkphp/library/think/debug/Console.php create mode 100644 thinkphp/library/think/debug/Html.php create mode 100644 thinkphp/library/think/exception/ClassNotFoundException.php create mode 100644 thinkphp/library/think/exception/DbException.php create mode 100644 thinkphp/library/think/exception/ErrorException.php create mode 100644 thinkphp/library/think/exception/Handle.php create mode 100644 thinkphp/library/think/exception/HttpException.php create mode 100644 thinkphp/library/think/exception/HttpResponseException.php create mode 100644 thinkphp/library/think/exception/PDOException.php create mode 100644 thinkphp/library/think/exception/RouteNotFoundException.php create mode 100644 thinkphp/library/think/exception/TemplateNotFoundException.php create mode 100644 thinkphp/library/think/exception/ThrowableError.php create mode 100644 thinkphp/library/think/exception/ValidateException.php create mode 100644 thinkphp/library/think/log/driver/File.php create mode 100644 thinkphp/library/think/log/driver/Socket.php create mode 100644 thinkphp/library/think/log/driver/Test.php create mode 100644 thinkphp/library/think/model/Collection.php create mode 100644 thinkphp/library/think/model/Merge.php create mode 100644 thinkphp/library/think/model/Pivot.php create mode 100644 thinkphp/library/think/model/Relation.php create mode 100644 thinkphp/library/think/model/relation/BelongsTo.php create mode 100644 thinkphp/library/think/model/relation/BelongsToMany.php create mode 100644 thinkphp/library/think/model/relation/HasMany.php create mode 100644 thinkphp/library/think/model/relation/HasManyThrough.php create mode 100644 thinkphp/library/think/model/relation/HasOne.php create mode 100644 thinkphp/library/think/model/relation/MorphMany.php create mode 100644 thinkphp/library/think/model/relation/MorphOne.php create mode 100644 thinkphp/library/think/model/relation/MorphTo.php create mode 100644 thinkphp/library/think/model/relation/OneToOne.php create mode 100644 thinkphp/library/think/paginator/driver/Bootstrap.php create mode 100644 thinkphp/library/think/process/Builder.php create mode 100644 thinkphp/library/think/process/Utils.php create mode 100644 thinkphp/library/think/process/exception/Failed.php create mode 100644 thinkphp/library/think/process/exception/Timeout.php create mode 100644 thinkphp/library/think/process/pipes/Pipes.php create mode 100644 thinkphp/library/think/process/pipes/Unix.php create mode 100644 thinkphp/library/think/process/pipes/Windows.php create mode 100644 thinkphp/library/think/response/Json.php create mode 100644 thinkphp/library/think/response/Jsonp.php create mode 100644 thinkphp/library/think/response/Redirect.php create mode 100644 thinkphp/library/think/response/View.php create mode 100644 thinkphp/library/think/response/Xml.php create mode 100644 thinkphp/library/think/session/driver/Memcache.php create mode 100644 thinkphp/library/think/session/driver/Memcached.php create mode 100644 thinkphp/library/think/session/driver/Redis.php create mode 100644 thinkphp/library/think/template/TagLib.php create mode 100644 thinkphp/library/think/template/driver/File.php create mode 100644 thinkphp/library/think/template/taglib/Cx.php create mode 100644 thinkphp/library/think/view/driver/Php.php create mode 100644 thinkphp/library/think/view/driver/Think.php create mode 100644 thinkphp/library/traits/controller/Jump.php create mode 100644 thinkphp/library/traits/model/SoftDelete.php create mode 100644 thinkphp/library/traits/think/Instance.php create mode 100644 thinkphp/logo.png create mode 100644 thinkphp/phpunit.xml create mode 100644 thinkphp/start.php create mode 100644 thinkphp/tests/.gitignore create mode 100644 thinkphp/tests/README.md create mode 100644 thinkphp/tests/application/config.php create mode 100644 thinkphp/tests/application/database.php create mode 100644 thinkphp/tests/application/index/controller/Index.php create mode 100644 thinkphp/tests/application/route.php create mode 100644 thinkphp/tests/application/views/display.html create mode 100644 thinkphp/tests/application/views/display.phtml create mode 100644 thinkphp/tests/application/views/extend.html create mode 100644 thinkphp/tests/application/views/extend2.html create mode 100644 thinkphp/tests/application/views/include.html create mode 100644 thinkphp/tests/application/views/include2.html create mode 100644 thinkphp/tests/application/views/layout.html create mode 100644 thinkphp/tests/application/views/layout2.html create mode 100644 thinkphp/tests/conf/memcached.ini create mode 100644 thinkphp/tests/conf/redis.ini create mode 100644 thinkphp/tests/conf/timezone.ini create mode 100644 thinkphp/tests/mock.php create mode 100644 thinkphp/tests/script/install.sh create mode 100644 thinkphp/tests/thinkphp/baseTest.php create mode 100644 thinkphp/tests/thinkphp/library/think/appTest.php create mode 100644 thinkphp/tests/thinkphp/library/think/behavior/One.php create mode 100644 thinkphp/tests/thinkphp/library/think/behavior/Three.php create mode 100644 thinkphp/tests/thinkphp/library/think/behavior/Two.php create mode 100644 thinkphp/tests/thinkphp/library/think/buildTest.php create mode 100644 thinkphp/tests/thinkphp/library/think/cache/driver/cacheTestCase.php create mode 100644 thinkphp/tests/thinkphp/library/think/cache/driver/fileTest.php create mode 100644 thinkphp/tests/thinkphp/library/think/cache/driver/liteTest.php create mode 100644 thinkphp/tests/thinkphp/library/think/cache/driver/memcacheTest.php create mode 100644 thinkphp/tests/thinkphp/library/think/cache/driver/memcachedTest.php create mode 100644 thinkphp/tests/thinkphp/library/think/cache/driver/redisTest.php create mode 100644 thinkphp/tests/thinkphp/library/think/cacheTest.php create mode 100644 thinkphp/tests/thinkphp/library/think/config/ConfigInitTrait.php create mode 100644 thinkphp/tests/thinkphp/library/think/config/driver/fixtures/config.ini create mode 100644 thinkphp/tests/thinkphp/library/think/config/driver/fixtures/config.json create mode 100644 thinkphp/tests/thinkphp/library/think/config/driver/fixtures/config.xml create mode 100644 thinkphp/tests/thinkphp/library/think/config/driver/iniTest.php create mode 100644 thinkphp/tests/thinkphp/library/think/config/driver/jsonTest.php create mode 100644 thinkphp/tests/thinkphp/library/think/config/driver/xmlTest.php create mode 100644 thinkphp/tests/thinkphp/library/think/configTest.php create mode 100644 thinkphp/tests/thinkphp/library/think/controller/.gitignore create mode 100644 thinkphp/tests/thinkphp/library/think/controllerTest.php create mode 100644 thinkphp/tests/thinkphp/library/think/cookieTest.php create mode 100644 thinkphp/tests/thinkphp/library/think/db/driver/.gitignore create mode 100644 thinkphp/tests/thinkphp/library/think/dbTest.php create mode 100644 thinkphp/tests/thinkphp/library/think/debugTest.php create mode 100644 thinkphp/tests/thinkphp/library/think/exceptionTest.php create mode 100644 thinkphp/tests/thinkphp/library/think/hookTest.php create mode 100644 thinkphp/tests/thinkphp/library/think/lang/lang.php create mode 100644 thinkphp/tests/thinkphp/library/think/langTest.php create mode 100644 thinkphp/tests/thinkphp/library/think/loader/test/Hello.php create mode 100644 thinkphp/tests/thinkphp/library/think/loaderTest.php create mode 100644 thinkphp/tests/thinkphp/library/think/log/driver/fileTest.php create mode 100644 thinkphp/tests/thinkphp/library/think/logTest.php create mode 100644 thinkphp/tests/thinkphp/library/think/model/.gitignore create mode 100644 thinkphp/tests/thinkphp/library/think/paginateTest.php create mode 100644 thinkphp/tests/thinkphp/library/think/requestTest.php create mode 100644 thinkphp/tests/thinkphp/library/think/responseTest.php create mode 100644 thinkphp/tests/thinkphp/library/think/routeTest.php create mode 100644 thinkphp/tests/thinkphp/library/think/session/.gitignore create mode 100644 thinkphp/tests/thinkphp/library/think/sessionTest.php create mode 100644 thinkphp/tests/thinkphp/library/think/template/driver/.gitignore create mode 100644 thinkphp/tests/thinkphp/library/think/template/taglib/cxTest.php create mode 100644 thinkphp/tests/thinkphp/library/think/templateTest.php create mode 100644 thinkphp/tests/thinkphp/library/think/urlTest.php create mode 100644 thinkphp/tests/thinkphp/library/think/validateTest.php create mode 100644 thinkphp/tests/thinkphp/library/think/view/driver/.gitignore create mode 100644 thinkphp/tests/thinkphp/library/think/view/theme/index/template.html create mode 100644 thinkphp/tests/thinkphp/library/think/viewTest.php create mode 100644 thinkphp/tests/thinkphp/library/traits/controller/jumpTest.php create mode 100644 thinkphp/tests/thinkphp/library/traits/model/softDeleteTest.php create mode 100644 thinkphp/tests/thinkphp/library/traits/think/instanceTest.php create mode 100644 thinkphp/tpl/default_index.tpl create mode 100644 thinkphp/tpl/dispatch_jump.tpl create mode 100644 thinkphp/tpl/page_trace.tpl create mode 100644 thinkphp/tpl/think_exception.tpl create mode 100644 vendor/autoload.php create mode 100644 vendor/composer/ClassLoader.php create mode 100644 vendor/composer/LICENSE create mode 100644 vendor/composer/autoload_classmap.php create mode 100644 vendor/composer/autoload_files.php create mode 100644 vendor/composer/autoload_namespaces.php create mode 100644 vendor/composer/autoload_psr4.php create mode 100644 vendor/composer/autoload_real.php create mode 100644 vendor/composer/autoload_static.php create mode 100644 vendor/composer/installed.json create mode 100644 vendor/easywechat-composer/easywechat-composer/.gitignore create mode 100644 vendor/easywechat-composer/easywechat-composer/.php_cs create mode 100644 vendor/easywechat-composer/easywechat-composer/.travis.yml create mode 100644 vendor/easywechat-composer/easywechat-composer/LICENSE create mode 100644 vendor/easywechat-composer/easywechat-composer/README.md create mode 100644 vendor/easywechat-composer/easywechat-composer/composer.json create mode 100644 vendor/easywechat-composer/easywechat-composer/extensions.php create mode 100644 vendor/easywechat-composer/easywechat-composer/phpunit.xml create mode 100644 vendor/easywechat-composer/easywechat-composer/src/Commands/ExtensionsCommand.php create mode 100644 vendor/easywechat-composer/easywechat-composer/src/Commands/Provider.php create mode 100644 vendor/easywechat-composer/easywechat-composer/src/Contracts/Encrypter.php create mode 100644 vendor/easywechat-composer/easywechat-composer/src/Delegation/DelegationOptions.php create mode 100644 vendor/easywechat-composer/easywechat-composer/src/Delegation/DelegationTo.php create mode 100644 vendor/easywechat-composer/easywechat-composer/src/Delegation/Hydrate.php create mode 100644 vendor/easywechat-composer/easywechat-composer/src/EasyWeChat.php create mode 100644 vendor/easywechat-composer/easywechat-composer/src/Encryption/DefaultEncrypter.php create mode 100644 vendor/easywechat-composer/easywechat-composer/src/Exceptions/DecryptException.php create mode 100644 vendor/easywechat-composer/easywechat-composer/src/Exceptions/DelegationException.php create mode 100644 vendor/easywechat-composer/easywechat-composer/src/Exceptions/EncryptException.php create mode 100644 vendor/easywechat-composer/easywechat-composer/src/Extension.php create mode 100644 vendor/easywechat-composer/easywechat-composer/src/Http/DelegationResponse.php create mode 100644 vendor/easywechat-composer/easywechat-composer/src/Http/Response.php create mode 100644 vendor/easywechat-composer/easywechat-composer/src/Laravel/Http/Controllers/DelegatesController.php create mode 100644 vendor/easywechat-composer/easywechat-composer/src/Laravel/ServiceProvider.php create mode 100644 vendor/easywechat-composer/easywechat-composer/src/Laravel/config.php create mode 100644 vendor/easywechat-composer/easywechat-composer/src/Laravel/routes.php create mode 100644 vendor/easywechat-composer/easywechat-composer/src/ManifestManager.php create mode 100644 vendor/easywechat-composer/easywechat-composer/src/Plugin.php create mode 100644 vendor/easywechat-composer/easywechat-composer/src/Traits/MakesHttpRequests.php create mode 100644 vendor/easywechat-composer/easywechat-composer/src/Traits/WithAggregator.php create mode 100644 vendor/easywechat-composer/easywechat-composer/tests/ManifestManagerTest.php create mode 100644 vendor/guzzlehttp/guzzle/.php_cs create mode 100644 vendor/guzzlehttp/guzzle/CHANGELOG.md create mode 100644 vendor/guzzlehttp/guzzle/Dockerfile create mode 100644 vendor/guzzlehttp/guzzle/LICENSE create mode 100644 vendor/guzzlehttp/guzzle/README.md create mode 100644 vendor/guzzlehttp/guzzle/UPGRADING.md create mode 100644 vendor/guzzlehttp/guzzle/composer.json create mode 100644 vendor/guzzlehttp/guzzle/src/Client.php create mode 100644 vendor/guzzlehttp/guzzle/src/ClientInterface.php create mode 100644 vendor/guzzlehttp/guzzle/src/Cookie/CookieJar.php create mode 100644 vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php create mode 100644 vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php create mode 100644 vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php create mode 100644 vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php create mode 100644 vendor/guzzlehttp/guzzle/src/Exception/BadResponseException.php create mode 100644 vendor/guzzlehttp/guzzle/src/Exception/ClientException.php create mode 100644 vendor/guzzlehttp/guzzle/src/Exception/ConnectException.php create mode 100644 vendor/guzzlehttp/guzzle/src/Exception/GuzzleException.php create mode 100644 vendor/guzzlehttp/guzzle/src/Exception/InvalidArgumentException.php create mode 100644 vendor/guzzlehttp/guzzle/src/Exception/RequestException.php create mode 100644 vendor/guzzlehttp/guzzle/src/Exception/SeekException.php create mode 100644 vendor/guzzlehttp/guzzle/src/Exception/ServerException.php create mode 100644 vendor/guzzlehttp/guzzle/src/Exception/TooManyRedirectsException.php create mode 100644 vendor/guzzlehttp/guzzle/src/Exception/TransferException.php create mode 100644 vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php create mode 100644 vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php create mode 100644 vendor/guzzlehttp/guzzle/src/Handler/CurlHandler.php create mode 100644 vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php create mode 100644 vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php create mode 100644 vendor/guzzlehttp/guzzle/src/Handler/MockHandler.php create mode 100644 vendor/guzzlehttp/guzzle/src/Handler/Proxy.php create mode 100644 vendor/guzzlehttp/guzzle/src/Handler/StreamHandler.php create mode 100644 vendor/guzzlehttp/guzzle/src/HandlerStack.php create mode 100644 vendor/guzzlehttp/guzzle/src/MessageFormatter.php create mode 100644 vendor/guzzlehttp/guzzle/src/Middleware.php create mode 100644 vendor/guzzlehttp/guzzle/src/Pool.php create mode 100644 vendor/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php create mode 100644 vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php create mode 100644 vendor/guzzlehttp/guzzle/src/RequestOptions.php create mode 100644 vendor/guzzlehttp/guzzle/src/RetryMiddleware.php create mode 100644 vendor/guzzlehttp/guzzle/src/TransferStats.php create mode 100644 vendor/guzzlehttp/guzzle/src/UriTemplate.php create mode 100644 vendor/guzzlehttp/guzzle/src/Utils.php create mode 100644 vendor/guzzlehttp/guzzle/src/functions.php create mode 100644 vendor/guzzlehttp/guzzle/src/functions_include.php create mode 100644 vendor/guzzlehttp/promises/CHANGELOG.md create mode 100644 vendor/guzzlehttp/promises/LICENSE create mode 100644 vendor/guzzlehttp/promises/Makefile create mode 100644 vendor/guzzlehttp/promises/README.md create mode 100644 vendor/guzzlehttp/promises/composer.json create mode 100644 vendor/guzzlehttp/promises/src/AggregateException.php create mode 100644 vendor/guzzlehttp/promises/src/CancellationException.php create mode 100644 vendor/guzzlehttp/promises/src/Coroutine.php create mode 100644 vendor/guzzlehttp/promises/src/Create.php create mode 100644 vendor/guzzlehttp/promises/src/Each.php create mode 100644 vendor/guzzlehttp/promises/src/EachPromise.php create mode 100644 vendor/guzzlehttp/promises/src/FulfilledPromise.php create mode 100644 vendor/guzzlehttp/promises/src/Is.php create mode 100644 vendor/guzzlehttp/promises/src/Promise.php create mode 100644 vendor/guzzlehttp/promises/src/PromiseInterface.php create mode 100644 vendor/guzzlehttp/promises/src/PromisorInterface.php create mode 100644 vendor/guzzlehttp/promises/src/RejectedPromise.php create mode 100644 vendor/guzzlehttp/promises/src/RejectionException.php create mode 100644 vendor/guzzlehttp/promises/src/TaskQueue.php create mode 100644 vendor/guzzlehttp/promises/src/TaskQueueInterface.php create mode 100644 vendor/guzzlehttp/promises/src/Utils.php create mode 100644 vendor/guzzlehttp/promises/src/functions.php create mode 100644 vendor/guzzlehttp/promises/src/functions_include.php create mode 100644 vendor/guzzlehttp/psr7/.github/FUNDING.yml create mode 100644 vendor/guzzlehttp/psr7/.github/stale.yml create mode 100644 vendor/guzzlehttp/psr7/.github/workflows/ci.yml create mode 100644 vendor/guzzlehttp/psr7/.github/workflows/integration.yml create mode 100644 vendor/guzzlehttp/psr7/.github/workflows/static.yml create mode 100644 vendor/guzzlehttp/psr7/.php_cs.dist create mode 100644 vendor/guzzlehttp/psr7/CHANGELOG.md create mode 100644 vendor/guzzlehttp/psr7/LICENSE create mode 100644 vendor/guzzlehttp/psr7/README.md create mode 100644 vendor/guzzlehttp/psr7/composer.json create mode 100644 vendor/guzzlehttp/psr7/src/AppendStream.php create mode 100644 vendor/guzzlehttp/psr7/src/BufferStream.php create mode 100644 vendor/guzzlehttp/psr7/src/CachingStream.php create mode 100644 vendor/guzzlehttp/psr7/src/DroppingStream.php create mode 100644 vendor/guzzlehttp/psr7/src/FnStream.php create mode 100644 vendor/guzzlehttp/psr7/src/Header.php create mode 100644 vendor/guzzlehttp/psr7/src/InflateStream.php create mode 100644 vendor/guzzlehttp/psr7/src/LazyOpenStream.php create mode 100644 vendor/guzzlehttp/psr7/src/LimitStream.php create mode 100644 vendor/guzzlehttp/psr7/src/Message.php create mode 100644 vendor/guzzlehttp/psr7/src/MessageTrait.php create mode 100644 vendor/guzzlehttp/psr7/src/MimeType.php create mode 100644 vendor/guzzlehttp/psr7/src/MultipartStream.php create mode 100644 vendor/guzzlehttp/psr7/src/NoSeekStream.php create mode 100644 vendor/guzzlehttp/psr7/src/PumpStream.php create mode 100644 vendor/guzzlehttp/psr7/src/Query.php create mode 100644 vendor/guzzlehttp/psr7/src/Request.php create mode 100644 vendor/guzzlehttp/psr7/src/Response.php create mode 100644 vendor/guzzlehttp/psr7/src/Rfc7230.php create mode 100644 vendor/guzzlehttp/psr7/src/ServerRequest.php create mode 100644 vendor/guzzlehttp/psr7/src/Stream.php create mode 100644 vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php create mode 100644 vendor/guzzlehttp/psr7/src/StreamWrapper.php create mode 100644 vendor/guzzlehttp/psr7/src/UploadedFile.php create mode 100644 vendor/guzzlehttp/psr7/src/Uri.php create mode 100644 vendor/guzzlehttp/psr7/src/UriNormalizer.php create mode 100644 vendor/guzzlehttp/psr7/src/UriResolver.php create mode 100644 vendor/guzzlehttp/psr7/src/Utils.php create mode 100644 vendor/guzzlehttp/psr7/src/functions.php create mode 100644 vendor/guzzlehttp/psr7/src/functions_include.php create mode 100644 vendor/karsonzhang/fastadmin-addons/.gitignore create mode 100644 vendor/karsonzhang/fastadmin-addons/LICENSE.txt create mode 100644 vendor/karsonzhang/fastadmin-addons/README.md create mode 100644 vendor/karsonzhang/fastadmin-addons/composer.json create mode 100644 vendor/karsonzhang/fastadmin-addons/src/Addons.php create mode 100644 vendor/karsonzhang/fastadmin-addons/src/addons/AddonException.php create mode 100644 vendor/karsonzhang/fastadmin-addons/src/addons/Controller.php create mode 100644 vendor/karsonzhang/fastadmin-addons/src/addons/Route.php create mode 100644 vendor/karsonzhang/fastadmin-addons/src/addons/Service.php create mode 100644 vendor/karsonzhang/fastadmin-addons/src/common.php create mode 100644 vendor/karsonzhang/fastadmin-addons/src/config.php create mode 100644 vendor/markbaker/complex/README.md create mode 100644 vendor/markbaker/complex/classes/Autoloader.php create mode 100644 vendor/markbaker/complex/classes/Bootstrap.php create mode 100644 vendor/markbaker/complex/classes/src/Complex.php create mode 100644 vendor/markbaker/complex/classes/src/Exception.php create mode 100644 vendor/markbaker/complex/classes/src/functions/abs.php create mode 100644 vendor/markbaker/complex/classes/src/functions/acos.php create mode 100644 vendor/markbaker/complex/classes/src/functions/acosh.php create mode 100644 vendor/markbaker/complex/classes/src/functions/acot.php create mode 100644 vendor/markbaker/complex/classes/src/functions/acoth.php create mode 100644 vendor/markbaker/complex/classes/src/functions/acsc.php create mode 100644 vendor/markbaker/complex/classes/src/functions/acsch.php create mode 100644 vendor/markbaker/complex/classes/src/functions/argument.php create mode 100644 vendor/markbaker/complex/classes/src/functions/asec.php create mode 100644 vendor/markbaker/complex/classes/src/functions/asech.php create mode 100644 vendor/markbaker/complex/classes/src/functions/asin.php create mode 100644 vendor/markbaker/complex/classes/src/functions/asinh.php create mode 100644 vendor/markbaker/complex/classes/src/functions/atan.php create mode 100644 vendor/markbaker/complex/classes/src/functions/atanh.php create mode 100644 vendor/markbaker/complex/classes/src/functions/conjugate.php create mode 100644 vendor/markbaker/complex/classes/src/functions/cos.php create mode 100644 vendor/markbaker/complex/classes/src/functions/cosh.php create mode 100644 vendor/markbaker/complex/classes/src/functions/cot.php create mode 100644 vendor/markbaker/complex/classes/src/functions/coth.php create mode 100644 vendor/markbaker/complex/classes/src/functions/csc.php create mode 100644 vendor/markbaker/complex/classes/src/functions/csch.php create mode 100644 vendor/markbaker/complex/classes/src/functions/exp.php create mode 100644 vendor/markbaker/complex/classes/src/functions/inverse.php create mode 100644 vendor/markbaker/complex/classes/src/functions/ln.php create mode 100644 vendor/markbaker/complex/classes/src/functions/log10.php create mode 100644 vendor/markbaker/complex/classes/src/functions/log2.php create mode 100644 vendor/markbaker/complex/classes/src/functions/negative.php create mode 100644 vendor/markbaker/complex/classes/src/functions/pow.php create mode 100644 vendor/markbaker/complex/classes/src/functions/rho.php create mode 100644 vendor/markbaker/complex/classes/src/functions/sec.php create mode 100644 vendor/markbaker/complex/classes/src/functions/sech.php create mode 100644 vendor/markbaker/complex/classes/src/functions/sin.php create mode 100644 vendor/markbaker/complex/classes/src/functions/sinh.php create mode 100644 vendor/markbaker/complex/classes/src/functions/sqrt.php create mode 100644 vendor/markbaker/complex/classes/src/functions/tan.php create mode 100644 vendor/markbaker/complex/classes/src/functions/tanh.php create mode 100644 vendor/markbaker/complex/classes/src/functions/theta.php create mode 100644 vendor/markbaker/complex/classes/src/operations/add.php create mode 100644 vendor/markbaker/complex/classes/src/operations/divideby.php create mode 100644 vendor/markbaker/complex/classes/src/operations/divideinto.php create mode 100644 vendor/markbaker/complex/classes/src/operations/multiply.php create mode 100644 vendor/markbaker/complex/classes/src/operations/subtract.php create mode 100644 vendor/markbaker/complex/composer.json create mode 100644 vendor/markbaker/complex/examples/complexTest.php create mode 100644 vendor/markbaker/complex/examples/testFunctions.php create mode 100644 vendor/markbaker/complex/examples/testOperations.php create mode 100644 vendor/markbaker/complex/license.md create mode 100644 vendor/markbaker/matrix/.github/workflows/main.yaml create mode 100644 vendor/markbaker/matrix/README.md create mode 100644 vendor/markbaker/matrix/buildPhar.php create mode 100644 vendor/markbaker/matrix/classes/src/Builder.php create mode 100644 vendor/markbaker/matrix/classes/src/Decomposition/Decomposition.php create mode 100644 vendor/markbaker/matrix/classes/src/Decomposition/LU.php create mode 100644 vendor/markbaker/matrix/classes/src/Decomposition/QR.php create mode 100644 vendor/markbaker/matrix/classes/src/Exception.php create mode 100644 vendor/markbaker/matrix/classes/src/Functions.php create mode 100644 vendor/markbaker/matrix/classes/src/Functions/adjoint.php create mode 100644 vendor/markbaker/matrix/classes/src/Functions/antidiagonal.php create mode 100644 vendor/markbaker/matrix/classes/src/Functions/cofactors.php create mode 100644 vendor/markbaker/matrix/classes/src/Functions/determinant.php create mode 100644 vendor/markbaker/matrix/classes/src/Functions/diagonal.php create mode 100644 vendor/markbaker/matrix/classes/src/Functions/identity.php create mode 100644 vendor/markbaker/matrix/classes/src/Functions/inverse.php create mode 100644 vendor/markbaker/matrix/classes/src/Functions/minors.php create mode 100644 vendor/markbaker/matrix/classes/src/Functions/trace.php create mode 100644 vendor/markbaker/matrix/classes/src/Functions/transpose.php create mode 100644 vendor/markbaker/matrix/classes/src/Matrix.php create mode 100644 vendor/markbaker/matrix/classes/src/Operations/add.php create mode 100644 vendor/markbaker/matrix/classes/src/Operations/directsum.php create mode 100644 vendor/markbaker/matrix/classes/src/Operations/divideby.php create mode 100644 vendor/markbaker/matrix/classes/src/Operations/divideinto.php create mode 100644 vendor/markbaker/matrix/classes/src/Operations/multiply.php create mode 100644 vendor/markbaker/matrix/classes/src/Operations/subtract.php create mode 100644 vendor/markbaker/matrix/classes/src/Operators/Addition.php create mode 100644 vendor/markbaker/matrix/classes/src/Operators/DirectSum.php create mode 100644 vendor/markbaker/matrix/classes/src/Operators/Division.php create mode 100644 vendor/markbaker/matrix/classes/src/Operators/Multiplication.php create mode 100644 vendor/markbaker/matrix/classes/src/Operators/Operator.php create mode 100644 vendor/markbaker/matrix/classes/src/Operators/Subtraction.php create mode 100644 vendor/markbaker/matrix/composer.json create mode 100644 vendor/markbaker/matrix/examples/test.php create mode 100644 vendor/markbaker/matrix/infection.json.dist create mode 100644 vendor/markbaker/matrix/license.md create mode 100644 vendor/markbaker/matrix/phpstan.neon create mode 100644 vendor/monolog/monolog/CHANGELOG.md create mode 100644 vendor/monolog/monolog/LICENSE create mode 100644 vendor/monolog/monolog/README.md create mode 100644 vendor/monolog/monolog/composer.json create mode 100644 vendor/monolog/monolog/phpstan.neon.dist create mode 100644 vendor/monolog/monolog/src/Monolog/ErrorHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php create mode 100644 vendor/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php create mode 100644 vendor/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php create mode 100644 vendor/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.php create mode 100644 vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php create mode 100644 vendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php create mode 100644 vendor/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php create mode 100644 vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php create mode 100644 vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php create mode 100644 vendor/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php create mode 100644 vendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php create mode 100644 vendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php create mode 100644 vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php create mode 100644 vendor/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php create mode 100644 vendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/AmqpHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/ElasticSearchHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/FilterHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerInterface.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerTrait.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/HipChatHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/InsightOpsHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerInterface.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerTrait.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/RavenHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/RollbarHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/SamplingHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/SlackbotHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Logger.php create mode 100644 vendor/monolog/monolog/src/Monolog/Processor/GitProcessor.php create mode 100644 vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php create mode 100644 vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php create mode 100644 vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php create mode 100644 vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php create mode 100644 vendor/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php create mode 100644 vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php create mode 100644 vendor/monolog/monolog/src/Monolog/Processor/ProcessorInterface.php create mode 100644 vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php create mode 100644 vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php create mode 100644 vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php create mode 100644 vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php create mode 100644 vendor/monolog/monolog/src/Monolog/Registry.php create mode 100644 vendor/monolog/monolog/src/Monolog/ResettableInterface.php create mode 100644 vendor/monolog/monolog/src/Monolog/SignalHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Utils.php create mode 100644 vendor/nelexa/zip/.phpstorm.meta.php create mode 100644 vendor/nelexa/zip/LICENSE create mode 100644 vendor/nelexa/zip/README.RU.md create mode 100644 vendor/nelexa/zip/README.md create mode 100644 vendor/nelexa/zip/composer.json create mode 100644 vendor/nelexa/zip/src/Constants/DosAttrs.php create mode 100644 vendor/nelexa/zip/src/Constants/DosCodePage.php create mode 100644 vendor/nelexa/zip/src/Constants/GeneralPurposeBitFlag.php create mode 100644 vendor/nelexa/zip/src/Constants/UnixStat.php create mode 100644 vendor/nelexa/zip/src/Constants/ZipCompressionLevel.php create mode 100644 vendor/nelexa/zip/src/Constants/ZipCompressionMethod.php create mode 100644 vendor/nelexa/zip/src/Constants/ZipConstants.php create mode 100644 vendor/nelexa/zip/src/Constants/ZipEncryptionMethod.php create mode 100644 vendor/nelexa/zip/src/Constants/ZipOptions.php create mode 100644 vendor/nelexa/zip/src/Constants/ZipPlatform.php create mode 100644 vendor/nelexa/zip/src/Constants/ZipVersion.php create mode 100644 vendor/nelexa/zip/src/Exception/Crc32Exception.php create mode 100644 vendor/nelexa/zip/src/Exception/InvalidArgumentException.php create mode 100644 vendor/nelexa/zip/src/Exception/RuntimeException.php create mode 100644 vendor/nelexa/zip/src/Exception/ZipAuthenticationException.php create mode 100644 vendor/nelexa/zip/src/Exception/ZipCryptoException.php create mode 100644 vendor/nelexa/zip/src/Exception/ZipEntryNotFoundException.php create mode 100644 vendor/nelexa/zip/src/Exception/ZipException.php create mode 100644 vendor/nelexa/zip/src/Exception/ZipUnsupportMethodException.php create mode 100644 vendor/nelexa/zip/src/IO/Filter/Cipher/Pkware/PKCryptContext.php create mode 100644 vendor/nelexa/zip/src/IO/Filter/Cipher/Pkware/PKDecryptionStreamFilter.php create mode 100644 vendor/nelexa/zip/src/IO/Filter/Cipher/Pkware/PKEncryptionStreamFilter.php create mode 100644 vendor/nelexa/zip/src/IO/Filter/Cipher/WinZipAes/WinZipAesContext.php create mode 100644 vendor/nelexa/zip/src/IO/Filter/Cipher/WinZipAes/WinZipAesDecryptionStreamFilter.php create mode 100644 vendor/nelexa/zip/src/IO/Filter/Cipher/WinZipAes/WinZipAesEncryptionStreamFilter.php create mode 100644 vendor/nelexa/zip/src/IO/Stream/ResponseStream.php create mode 100644 vendor/nelexa/zip/src/IO/Stream/ZipEntryStreamWrapper.php create mode 100644 vendor/nelexa/zip/src/IO/ZipReader.php create mode 100644 vendor/nelexa/zip/src/IO/ZipWriter.php create mode 100644 vendor/nelexa/zip/src/Model/Data/ZipFileData.php create mode 100644 vendor/nelexa/zip/src/Model/Data/ZipNewData.php create mode 100644 vendor/nelexa/zip/src/Model/Data/ZipSourceFileData.php create mode 100644 vendor/nelexa/zip/src/Model/EndOfCentralDirectory.php create mode 100644 vendor/nelexa/zip/src/Model/Extra/ExtraFieldsCollection.php create mode 100644 vendor/nelexa/zip/src/Model/Extra/Fields/AbstractUnicodeExtraField.php create mode 100644 vendor/nelexa/zip/src/Model/Extra/Fields/ApkAlignmentExtraField.php create mode 100644 vendor/nelexa/zip/src/Model/Extra/Fields/AsiExtraField.php create mode 100644 vendor/nelexa/zip/src/Model/Extra/Fields/ExtendedTimestampExtraField.php create mode 100644 vendor/nelexa/zip/src/Model/Extra/Fields/JarMarkerExtraField.php create mode 100644 vendor/nelexa/zip/src/Model/Extra/Fields/NewUnixExtraField.php create mode 100644 vendor/nelexa/zip/src/Model/Extra/Fields/NtfsExtraField.php create mode 100644 vendor/nelexa/zip/src/Model/Extra/Fields/OldUnixExtraField.php create mode 100644 vendor/nelexa/zip/src/Model/Extra/Fields/UnicodeCommentExtraField.php create mode 100644 vendor/nelexa/zip/src/Model/Extra/Fields/UnicodePathExtraField.php create mode 100644 vendor/nelexa/zip/src/Model/Extra/Fields/UnrecognizedExtraField.php create mode 100644 vendor/nelexa/zip/src/Model/Extra/Fields/WinZipAesExtraField.php create mode 100644 vendor/nelexa/zip/src/Model/Extra/Fields/Zip64ExtraField.php create mode 100644 vendor/nelexa/zip/src/Model/Extra/ZipExtraDriver.php create mode 100644 vendor/nelexa/zip/src/Model/Extra/ZipExtraField.php create mode 100644 vendor/nelexa/zip/src/Model/ImmutableZipContainer.php create mode 100644 vendor/nelexa/zip/src/Model/ZipContainer.php create mode 100644 vendor/nelexa/zip/src/Model/ZipData.php create mode 100644 vendor/nelexa/zip/src/Model/ZipEntry.php create mode 100644 vendor/nelexa/zip/src/Model/ZipEntryMatcher.php create mode 100644 vendor/nelexa/zip/src/Model/ZipInfo.php create mode 100644 vendor/nelexa/zip/src/Util/CryptoUtil.php create mode 100644 vendor/nelexa/zip/src/Util/DateTimeConverter.php create mode 100644 vendor/nelexa/zip/src/Util/FileAttribUtil.php create mode 100644 vendor/nelexa/zip/src/Util/FilesUtil.php create mode 100644 vendor/nelexa/zip/src/Util/Iterator/IgnoreFilesFilterIterator.php create mode 100644 vendor/nelexa/zip/src/Util/Iterator/IgnoreFilesRecursiveFilterIterator.php create mode 100644 vendor/nelexa/zip/src/Util/PackUtil.php create mode 100644 vendor/nelexa/zip/src/Util/StringUtil.php create mode 100644 vendor/nelexa/zip/src/ZipFile.php create mode 100644 vendor/nelexa/zip/src/ZipFileInterface.php create mode 100644 vendor/overtrue/pinyin/LICENSE create mode 100644 vendor/overtrue/pinyin/README.md create mode 100644 vendor/overtrue/pinyin/composer.json create mode 100644 vendor/overtrue/pinyin/data/surnames create mode 100644 vendor/overtrue/pinyin/data/words_0 create mode 100644 vendor/overtrue/pinyin/data/words_1 create mode 100644 vendor/overtrue/pinyin/data/words_2 create mode 100644 vendor/overtrue/pinyin/data/words_3 create mode 100644 vendor/overtrue/pinyin/data/words_4 create mode 100644 vendor/overtrue/pinyin/data/words_5 create mode 100644 vendor/overtrue/pinyin/src/DictLoaderInterface.php create mode 100644 vendor/overtrue/pinyin/src/FileDictLoader.php create mode 100644 vendor/overtrue/pinyin/src/GeneratorFileDictLoader.php create mode 100644 vendor/overtrue/pinyin/src/MemoryFileDictLoader.php create mode 100644 vendor/overtrue/pinyin/src/Pinyin.php create mode 100644 vendor/overtrue/socialite/.github/FUNDING.yml create mode 100644 vendor/overtrue/socialite/.gitignore create mode 100644 vendor/overtrue/socialite/.php_cs create mode 100644 vendor/overtrue/socialite/.travis.yml create mode 100644 vendor/overtrue/socialite/LICENSE.txt create mode 100644 vendor/overtrue/socialite/README.md create mode 100644 vendor/overtrue/socialite/composer.json create mode 100644 vendor/overtrue/socialite/phpunit.xml create mode 100644 vendor/overtrue/socialite/src/AccessToken.php create mode 100644 vendor/overtrue/socialite/src/AccessTokenInterface.php create mode 100644 vendor/overtrue/socialite/src/AuthorizeFailedException.php create mode 100644 vendor/overtrue/socialite/src/Config.php create mode 100644 vendor/overtrue/socialite/src/FactoryInterface.php create mode 100644 vendor/overtrue/socialite/src/HasAttributes.php create mode 100644 vendor/overtrue/socialite/src/InvalidArgumentException.php create mode 100644 vendor/overtrue/socialite/src/InvalidStateException.php create mode 100644 vendor/overtrue/socialite/src/ProviderInterface.php create mode 100644 vendor/overtrue/socialite/src/Providers/AbstractProvider.php create mode 100644 vendor/overtrue/socialite/src/Providers/BaiduProvider.php create mode 100644 vendor/overtrue/socialite/src/Providers/DouYinProvider.php create mode 100644 vendor/overtrue/socialite/src/Providers/DoubanProvider.php create mode 100644 vendor/overtrue/socialite/src/Providers/FacebookProvider.php create mode 100644 vendor/overtrue/socialite/src/Providers/FeiShuProvider.php create mode 100644 vendor/overtrue/socialite/src/Providers/GitHubProvider.php create mode 100644 vendor/overtrue/socialite/src/Providers/GoogleProvider.php create mode 100644 vendor/overtrue/socialite/src/Providers/LinkedinProvider.php create mode 100644 vendor/overtrue/socialite/src/Providers/OutlookProvider.php create mode 100644 vendor/overtrue/socialite/src/Providers/QQProvider.php create mode 100644 vendor/overtrue/socialite/src/Providers/TaobaoProvider.php create mode 100644 vendor/overtrue/socialite/src/Providers/WeChatProvider.php create mode 100644 vendor/overtrue/socialite/src/Providers/WeWorkProvider.php create mode 100644 vendor/overtrue/socialite/src/Providers/WeiboProvider.php create mode 100644 vendor/overtrue/socialite/src/SocialiteManager.php create mode 100644 vendor/overtrue/socialite/src/User.php create mode 100644 vendor/overtrue/socialite/src/UserInterface.php create mode 100644 vendor/overtrue/socialite/src/WeChatComponentInterface.php create mode 100644 vendor/overtrue/socialite/tests/OAuthTest.php create mode 100644 vendor/overtrue/socialite/tests/Providers/WeWorkProviderTest.php create mode 100644 vendor/overtrue/socialite/tests/UserTest.php create mode 100644 vendor/overtrue/socialite/tests/WechatProviderTest.php create mode 100644 vendor/overtrue/wechat/CHANGELOG.md create mode 100644 vendor/overtrue/wechat/CONTRIBUTING.md create mode 100644 vendor/overtrue/wechat/LICENSE create mode 100644 vendor/overtrue/wechat/README.md create mode 100644 vendor/overtrue/wechat/composer.json create mode 100644 vendor/overtrue/wechat/src/BasicService/Application.php create mode 100644 vendor/overtrue/wechat/src/BasicService/ContentSecurity/Client.php create mode 100644 vendor/overtrue/wechat/src/BasicService/ContentSecurity/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/BasicService/Jssdk/Client.php create mode 100644 vendor/overtrue/wechat/src/BasicService/Jssdk/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/BasicService/Media/Client.php create mode 100644 vendor/overtrue/wechat/src/BasicService/Media/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/BasicService/QrCode/Client.php create mode 100644 vendor/overtrue/wechat/src/BasicService/QrCode/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/BasicService/Url/Client.php create mode 100644 vendor/overtrue/wechat/src/BasicService/Url/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/Factory.php create mode 100644 vendor/overtrue/wechat/src/Kernel/AccessToken.php create mode 100644 vendor/overtrue/wechat/src/Kernel/BaseClient.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Clauses/Clause.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Config.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Contracts/AccessTokenInterface.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Contracts/Arrayable.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Contracts/EventHandlerInterface.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Contracts/MediaInterface.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Contracts/MessageInterface.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Decorators/FinallyResult.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Decorators/TerminateResult.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Encryptor.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Events/AccessTokenRefreshed.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Events/ApplicationInitialized.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Events/HttpResponseCreated.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Events/ServerGuardResponseCreated.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Exceptions/BadRequestException.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Exceptions/DecryptException.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Exceptions/Exception.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Exceptions/HttpException.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Exceptions/InvalidArgumentException.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Exceptions/InvalidConfigException.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Exceptions/RuntimeException.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Exceptions/UnboundServiceException.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Helpers.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Http/Response.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Http/StreamResponse.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Log/LogManager.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Messages/Article.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Messages/Card.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Messages/DeviceEvent.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Messages/DeviceText.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Messages/File.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Messages/Image.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Messages/Link.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Messages/Location.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Messages/Media.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Messages/Message.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Messages/MiniProgramPage.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Messages/Music.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Messages/News.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Messages/NewsItem.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Messages/Raw.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Messages/ShortVideo.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Messages/TaskCard.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Messages/Text.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Messages/TextCard.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Messages/Transfer.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Messages/Video.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Messages/Voice.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Providers/ConfigServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Providers/EventDispatcherServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Providers/ExtensionServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Providers/HttpClientServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Providers/LogServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Providers/RequestServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/Kernel/ServerGuard.php create mode 100644 vendor/overtrue/wechat/src/Kernel/ServiceContainer.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Support/AES.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Support/Arr.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Support/ArrayAccessible.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Support/Collection.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Support/File.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Support/Helpers.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Support/Str.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Support/XML.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Traits/HasAttributes.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Traits/HasHttpRequests.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Traits/InteractsWithCache.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Traits/Observable.php create mode 100644 vendor/overtrue/wechat/src/Kernel/Traits/ResponseCastable.php create mode 100644 vendor/overtrue/wechat/src/MicroMerchant/Application.php create mode 100644 vendor/overtrue/wechat/src/MicroMerchant/Base/Client.php create mode 100644 vendor/overtrue/wechat/src/MicroMerchant/Base/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/MicroMerchant/Certficates/Client.php create mode 100644 vendor/overtrue/wechat/src/MicroMerchant/Certficates/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/MicroMerchant/Kernel/BaseClient.php create mode 100644 vendor/overtrue/wechat/src/MicroMerchant/Kernel/Exceptions/EncryptException.php create mode 100644 vendor/overtrue/wechat/src/MicroMerchant/Kernel/Exceptions/InvalidExtensionException.php create mode 100644 vendor/overtrue/wechat/src/MicroMerchant/Kernel/Exceptions/InvalidSignException.php create mode 100644 vendor/overtrue/wechat/src/MicroMerchant/Material/Client.php create mode 100644 vendor/overtrue/wechat/src/MicroMerchant/Material/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/MicroMerchant/Media/Client.php create mode 100644 vendor/overtrue/wechat/src/MicroMerchant/Media/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/MicroMerchant/MerchantConfig/Client.php create mode 100644 vendor/overtrue/wechat/src/MicroMerchant/MerchantConfig/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/MicroMerchant/Withdraw/Client.php create mode 100644 vendor/overtrue/wechat/src/MicroMerchant/Withdraw/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/MiniProgram/ActivityMessage/Client.php create mode 100644 vendor/overtrue/wechat/src/MiniProgram/ActivityMessage/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/MiniProgram/AppCode/Client.php create mode 100644 vendor/overtrue/wechat/src/MiniProgram/AppCode/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/MiniProgram/Application.php create mode 100644 vendor/overtrue/wechat/src/MiniProgram/Auth/AccessToken.php create mode 100644 vendor/overtrue/wechat/src/MiniProgram/Auth/Client.php create mode 100644 vendor/overtrue/wechat/src/MiniProgram/Auth/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/MiniProgram/Base/Client.php create mode 100644 vendor/overtrue/wechat/src/MiniProgram/Base/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/MiniProgram/CustomerService/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/MiniProgram/DataCube/Client.php create mode 100644 vendor/overtrue/wechat/src/MiniProgram/DataCube/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/MiniProgram/Encryptor.php create mode 100644 vendor/overtrue/wechat/src/MiniProgram/Express/Client.php create mode 100644 vendor/overtrue/wechat/src/MiniProgram/Express/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/MiniProgram/Mall/CartClient.php create mode 100644 vendor/overtrue/wechat/src/MiniProgram/Mall/ForwardsMall.php create mode 100644 vendor/overtrue/wechat/src/MiniProgram/Mall/MediaClient.php create mode 100644 vendor/overtrue/wechat/src/MiniProgram/Mall/OrderClient.php create mode 100644 vendor/overtrue/wechat/src/MiniProgram/Mall/ProductClient.php create mode 100644 vendor/overtrue/wechat/src/MiniProgram/Mall/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/MiniProgram/NearbyPoi/Client.php create mode 100644 vendor/overtrue/wechat/src/MiniProgram/NearbyPoi/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/MiniProgram/OCR/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/MiniProgram/OpenData/Client.php create mode 100644 vendor/overtrue/wechat/src/MiniProgram/OpenData/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/MiniProgram/Plugin/Client.php create mode 100644 vendor/overtrue/wechat/src/MiniProgram/Plugin/DevClient.php create mode 100644 vendor/overtrue/wechat/src/MiniProgram/Plugin/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/MiniProgram/Server/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/MiniProgram/Soter/Client.php create mode 100644 vendor/overtrue/wechat/src/MiniProgram/Soter/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/MiniProgram/SubscribeMessage/Client.php create mode 100644 vendor/overtrue/wechat/src/MiniProgram/SubscribeMessage/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/MiniProgram/TemplateMessage/Client.php create mode 100644 vendor/overtrue/wechat/src/MiniProgram/TemplateMessage/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/MiniProgram/UniformMessage/Client.php create mode 100644 vendor/overtrue/wechat/src/MiniProgram/UniformMessage/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Application.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Auth/AccessToken.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Auth/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/AutoReply/Client.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/AutoReply/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Base/Client.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Base/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Broadcasting/Client.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Broadcasting/MessageBuilder.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Broadcasting/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Card/BoardingPassClient.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Card/Card.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Card/Client.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Card/CodeClient.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Card/CoinClient.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Card/GeneralCardClient.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Card/GiftCardClient.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Card/GiftCardOrderClient.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Card/GiftCardPageClient.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Card/InvoiceClient.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Card/JssdkClient.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Card/MeetingTicketClient.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Card/MemberCardClient.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Card/MovieTicketClient.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Card/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Card/SubMerchantClient.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Comment/Client.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Comment/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/CustomerService/Client.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/CustomerService/Messenger.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/CustomerService/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/CustomerService/SessionClient.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/DataCube/Client.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/DataCube/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Device/Client.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Device/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Goods/Client.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Goods/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Material/Client.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Material/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Menu/Client.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Menu/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/OAuth/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/OCR/Client.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/OCR/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/POI/Client.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/POI/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Semantic/Client.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Semantic/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Server/Guard.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Server/Handlers/EchoStrHandler.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Server/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/Client.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/DeviceClient.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/GroupClient.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/MaterialClient.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/PageClient.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/RelationClient.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/ShakeAround.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/StatsClient.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Store/Client.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Store/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/TemplateMessage/Client.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/TemplateMessage/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/User/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/User/TagClient.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/User/UserClient.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/WiFi/CardClient.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/WiFi/Client.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/WiFi/DeviceClient.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/WiFi/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/WiFi/ShopClient.php create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Application.php create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Auth/AccessToken.php create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Auth/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Auth/VerifyTicket.php create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Aggregate/Account/Client.php create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Aggregate/AggregateServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Auth/AccessToken.php create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Account/Client.php create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Account/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Application.php create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Auth/Client.php create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Code/Client.php create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Code/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Domain/Client.php create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Domain/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Setting/Client.php create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Setting/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Tester/Client.php create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Tester/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/Account/Client.php create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/Application.php create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/MiniProgram/Client.php create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/MiniProgram/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/OAuth/ComponentDelegate.php create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Server/Guard.php create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Base/Client.php create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Base/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/CodeTemplate/Client.php create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/CodeTemplate/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Component/Client.php create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Component/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Server/Guard.php create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/Authorized.php create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/Unauthorized.php create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/UpdateAuthorized.php create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/VerifyTicketRefreshed.php create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Server/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/OpenWork/Application.php create mode 100644 vendor/overtrue/wechat/src/OpenWork/Auth/AccessToken.php create mode 100644 vendor/overtrue/wechat/src/OpenWork/Auth/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/OpenWork/Corp/Client.php create mode 100644 vendor/overtrue/wechat/src/OpenWork/Corp/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/OpenWork/MiniProgram/Client.php create mode 100644 vendor/overtrue/wechat/src/OpenWork/MiniProgram/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/OpenWork/Provider/Client.php create mode 100644 vendor/overtrue/wechat/src/OpenWork/Provider/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/OpenWork/Server/Guard.php create mode 100644 vendor/overtrue/wechat/src/OpenWork/Server/Handlers/EchoStrHandler.php create mode 100644 vendor/overtrue/wechat/src/OpenWork/Server/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/OpenWork/SuiteAuth/AccessToken.php create mode 100644 vendor/overtrue/wechat/src/OpenWork/SuiteAuth/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/OpenWork/SuiteAuth/SuiteTicket.php create mode 100644 vendor/overtrue/wechat/src/OpenWork/Work/Application.php create mode 100644 vendor/overtrue/wechat/src/OpenWork/Work/Auth/AccessToken.php create mode 100644 vendor/overtrue/wechat/src/Payment/Application.php create mode 100644 vendor/overtrue/wechat/src/Payment/Base/Client.php create mode 100644 vendor/overtrue/wechat/src/Payment/Base/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/Payment/Bill/Client.php create mode 100644 vendor/overtrue/wechat/src/Payment/Bill/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/Payment/Coupon/Client.php create mode 100644 vendor/overtrue/wechat/src/Payment/Coupon/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/Payment/Jssdk/Client.php create mode 100644 vendor/overtrue/wechat/src/Payment/Jssdk/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/Payment/Kernel/BaseClient.php create mode 100644 vendor/overtrue/wechat/src/Payment/Kernel/Exceptions/InvalidSignException.php create mode 100644 vendor/overtrue/wechat/src/Payment/Kernel/Exceptions/SandboxException.php create mode 100644 vendor/overtrue/wechat/src/Payment/Merchant/Client.php create mode 100644 vendor/overtrue/wechat/src/Payment/Merchant/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/Payment/Notify/Handler.php create mode 100644 vendor/overtrue/wechat/src/Payment/Notify/Paid.php create mode 100644 vendor/overtrue/wechat/src/Payment/Notify/Refunded.php create mode 100644 vendor/overtrue/wechat/src/Payment/Notify/Scanned.php create mode 100644 vendor/overtrue/wechat/src/Payment/Order/Client.php create mode 100644 vendor/overtrue/wechat/src/Payment/Order/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/Payment/ProfitSharing/Client.php create mode 100644 vendor/overtrue/wechat/src/Payment/ProfitSharing/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/Payment/Redpack/Client.php create mode 100644 vendor/overtrue/wechat/src/Payment/Redpack/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/Payment/Refund/Client.php create mode 100644 vendor/overtrue/wechat/src/Payment/Refund/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/Payment/Reverse/Client.php create mode 100644 vendor/overtrue/wechat/src/Payment/Reverse/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/Payment/Sandbox/Client.php create mode 100644 vendor/overtrue/wechat/src/Payment/Sandbox/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/Payment/Security/Client.php create mode 100644 vendor/overtrue/wechat/src/Payment/Security/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/Payment/Transfer/Client.php create mode 100644 vendor/overtrue/wechat/src/Payment/Transfer/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/Work/Agent/Client.php create mode 100644 vendor/overtrue/wechat/src/Work/Agent/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/Work/Application.php create mode 100644 vendor/overtrue/wechat/src/Work/Auth/AccessToken.php create mode 100644 vendor/overtrue/wechat/src/Work/Auth/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/Work/Base/Client.php create mode 100644 vendor/overtrue/wechat/src/Work/Base/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/Work/Chat/Client.php create mode 100644 vendor/overtrue/wechat/src/Work/Chat/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/Work/Department/Client.php create mode 100644 vendor/overtrue/wechat/src/Work/Department/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/Work/ExternalContact/Client.php create mode 100644 vendor/overtrue/wechat/src/Work/ExternalContact/ContactWayClient.php create mode 100644 vendor/overtrue/wechat/src/Work/ExternalContact/MessageClient.php create mode 100644 vendor/overtrue/wechat/src/Work/ExternalContact/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/Work/ExternalContact/StatisticsClient.php create mode 100644 vendor/overtrue/wechat/src/Work/GroupRobot/Client.php create mode 100644 vendor/overtrue/wechat/src/Work/GroupRobot/Messages/Image.php create mode 100644 vendor/overtrue/wechat/src/Work/GroupRobot/Messages/Markdown.php create mode 100644 vendor/overtrue/wechat/src/Work/GroupRobot/Messages/Message.php create mode 100644 vendor/overtrue/wechat/src/Work/GroupRobot/Messages/News.php create mode 100644 vendor/overtrue/wechat/src/Work/GroupRobot/Messages/NewsItem.php create mode 100644 vendor/overtrue/wechat/src/Work/GroupRobot/Messages/Text.php create mode 100644 vendor/overtrue/wechat/src/Work/GroupRobot/Messenger.php create mode 100644 vendor/overtrue/wechat/src/Work/GroupRobot/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/Work/Invoice/Client.php create mode 100644 vendor/overtrue/wechat/src/Work/Invoice/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/Work/Jssdk/Client.php create mode 100644 vendor/overtrue/wechat/src/Work/Jssdk/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/Work/Media/Client.php create mode 100644 vendor/overtrue/wechat/src/Work/Media/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/Work/Menu/Client.php create mode 100644 vendor/overtrue/wechat/src/Work/Menu/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/Work/Message/Client.php create mode 100644 vendor/overtrue/wechat/src/Work/Message/Messenger.php create mode 100644 vendor/overtrue/wechat/src/Work/Message/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/Work/MiniProgram/Application.php create mode 100644 vendor/overtrue/wechat/src/Work/MiniProgram/Auth/Client.php create mode 100644 vendor/overtrue/wechat/src/Work/OA/Client.php create mode 100644 vendor/overtrue/wechat/src/Work/OA/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/Work/OAuth/AccessTokenDelegate.php create mode 100644 vendor/overtrue/wechat/src/Work/OAuth/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/Work/Server/Guard.php create mode 100644 vendor/overtrue/wechat/src/Work/Server/Handlers/EchoStrHandler.php create mode 100644 vendor/overtrue/wechat/src/Work/Server/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/Work/User/Client.php create mode 100644 vendor/overtrue/wechat/src/Work/User/ServiceProvider.php create mode 100644 vendor/overtrue/wechat/src/Work/User/TagClient.php create mode 100644 vendor/paragonie/random_compat/LICENSE create mode 100644 vendor/paragonie/random_compat/build-phar.sh create mode 100644 vendor/paragonie/random_compat/composer.json create mode 100644 vendor/paragonie/random_compat/dist/random_compat.phar.pubkey create mode 100644 vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc create mode 100644 vendor/paragonie/random_compat/lib/random.php create mode 100644 vendor/paragonie/random_compat/other/build_phar.php create mode 100644 vendor/paragonie/random_compat/psalm-autoload.php create mode 100644 vendor/paragonie/random_compat/psalm.xml create mode 100644 vendor/phpoffice/phpspreadsheet/.gitattributes create mode 100644 vendor/phpoffice/phpspreadsheet/.gitignore create mode 100644 vendor/phpoffice/phpspreadsheet/.php_cs.dist create mode 100644 vendor/phpoffice/phpspreadsheet/.scrutinizer.yml create mode 100644 vendor/phpoffice/phpspreadsheet/.travis.yml create mode 100644 vendor/phpoffice/phpspreadsheet/CHANGELOG.PHPExcel.md create mode 100644 vendor/phpoffice/phpspreadsheet/CHANGELOG.md create mode 100644 vendor/phpoffice/phpspreadsheet/CONTRIBUTING.md create mode 100644 vendor/phpoffice/phpspreadsheet/LICENSE create mode 100644 vendor/phpoffice/phpspreadsheet/bin/generate-document create mode 100644 vendor/phpoffice/phpspreadsheet/bin/migrate-from-phpexcel create mode 100644 vendor/phpoffice/phpspreadsheet/bin/pre-commit create mode 100644 vendor/phpoffice/phpspreadsheet/composer.json create mode 100644 vendor/phpoffice/phpspreadsheet/composer.lock create mode 100644 vendor/phpoffice/phpspreadsheet/docs/assets/logo.svg create mode 100644 vendor/phpoffice/phpspreadsheet/docs/extra/extra.css create mode 100644 vendor/phpoffice/phpspreadsheet/docs/faq.md create mode 100644 vendor/phpoffice/phpspreadsheet/docs/index.md create mode 100644 vendor/phpoffice/phpspreadsheet/docs/references/features-cross-reference.md create mode 100644 vendor/phpoffice/phpspreadsheet/docs/references/function-list-by-category.md create mode 100644 vendor/phpoffice/phpspreadsheet/docs/references/function-list-by-name.md create mode 100644 vendor/phpoffice/phpspreadsheet/docs/topics/accessing-cells.md create mode 100644 vendor/phpoffice/phpspreadsheet/docs/topics/architecture.md create mode 100644 vendor/phpoffice/phpspreadsheet/docs/topics/autofilters.md create mode 100644 vendor/phpoffice/phpspreadsheet/docs/topics/calculation-engine.md create mode 100644 vendor/phpoffice/phpspreadsheet/docs/topics/creating-spreadsheet.md create mode 100644 vendor/phpoffice/phpspreadsheet/docs/topics/file-formats.md create mode 100644 vendor/phpoffice/phpspreadsheet/docs/topics/images/01-01-autofilter.png create mode 100644 vendor/phpoffice/phpspreadsheet/docs/topics/images/01-02-autofilter.png create mode 100644 vendor/phpoffice/phpspreadsheet/docs/topics/images/01-03-filter-icon-1.png create mode 100644 vendor/phpoffice/phpspreadsheet/docs/topics/images/01-03-filter-icon-2.png create mode 100644 vendor/phpoffice/phpspreadsheet/docs/topics/images/01-04-autofilter.png create mode 100644 vendor/phpoffice/phpspreadsheet/docs/topics/images/01-schematic.png create mode 100644 vendor/phpoffice/phpspreadsheet/docs/topics/images/02-readers-writers.png create mode 100644 vendor/phpoffice/phpspreadsheet/docs/topics/images/04-01-simple-autofilter.png create mode 100644 vendor/phpoffice/phpspreadsheet/docs/topics/images/04-02-dategroup-autofilter.png create mode 100644 vendor/phpoffice/phpspreadsheet/docs/topics/images/04-03-custom-autofilter-1.png create mode 100644 vendor/phpoffice/phpspreadsheet/docs/topics/images/04-03-custom-autofilter-2.png create mode 100644 vendor/phpoffice/phpspreadsheet/docs/topics/images/04-04-dynamic-autofilter.png create mode 100644 vendor/phpoffice/phpspreadsheet/docs/topics/images/04-05-topten-autofilter-1.png create mode 100644 vendor/phpoffice/phpspreadsheet/docs/topics/images/04-05-topten-autofilter-2.png create mode 100644 vendor/phpoffice/phpspreadsheet/docs/topics/images/07-simple-example-1.png create mode 100644 vendor/phpoffice/phpspreadsheet/docs/topics/images/07-simple-example-2.png create mode 100644 vendor/phpoffice/phpspreadsheet/docs/topics/images/07-simple-example-3.png create mode 100644 vendor/phpoffice/phpspreadsheet/docs/topics/images/07-simple-example-4.png create mode 100644 vendor/phpoffice/phpspreadsheet/docs/topics/images/08-cell-comment.png create mode 100644 vendor/phpoffice/phpspreadsheet/docs/topics/images/08-column-width.png create mode 100644 vendor/phpoffice/phpspreadsheet/docs/topics/images/08-page-setup-margins.png create mode 100644 vendor/phpoffice/phpspreadsheet/docs/topics/images/08-page-setup-scaling-options.png create mode 100644 vendor/phpoffice/phpspreadsheet/docs/topics/images/08-styling-border-options.png create mode 100644 vendor/phpoffice/phpspreadsheet/docs/topics/images/09-command-line-calculation.png create mode 100644 vendor/phpoffice/phpspreadsheet/docs/topics/images/09-formula-in-cell-1.png create mode 100644 vendor/phpoffice/phpspreadsheet/docs/topics/images/09-formula-in-cell-2.png create mode 100644 vendor/phpoffice/phpspreadsheet/docs/topics/memory_saving.md create mode 100644 vendor/phpoffice/phpspreadsheet/docs/topics/migration-from-PHPExcel.md create mode 100644 vendor/phpoffice/phpspreadsheet/docs/topics/reading-and-writing-to-file.md create mode 100644 vendor/phpoffice/phpspreadsheet/docs/topics/reading-files.md create mode 100644 vendor/phpoffice/phpspreadsheet/docs/topics/recipes.md create mode 100644 vendor/phpoffice/phpspreadsheet/docs/topics/settings.md create mode 100644 vendor/phpoffice/phpspreadsheet/docs/topics/worksheets.md create mode 100644 vendor/phpoffice/phpspreadsheet/mkdocs.yml create mode 100644 vendor/phpoffice/phpspreadsheet/phpunit.xml.dist create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Autofilter/10_Autofilter.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Autofilter/10_Autofilter_selection_1.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Autofilter/10_Autofilter_selection_2.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Autofilter/10_Autofilter_selection_display.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/01_Simple.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/01_Simple_download_ods.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/01_Simple_download_pdf.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/01_Simple_download_xls.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/01_Simple_download_xlsx.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/02_Types.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/03_Formulas.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/04_Printing.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/05_Feature_demo.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/06_Largescale.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/07_Reader.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/08_Conditional_formatting.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/08_Conditional_formatting_2.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/09_Pagebreaks.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/11_Documentsecurity.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/12_CellProtection.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/13_Calculation.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/13_CalculationCyclicFormulae.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/14_Xls.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/15_Datavalidation.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/16_Csv.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/17_Html.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/18_Extendedcalculation.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/19_Namedrange.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/20_Read_Excel2003XML.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/20_Read_Gnumeric.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/20_Read_Ods.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/20_Read_Sylk.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/20_Read_Xls.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/22_Heavily_formatted.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/23_Sharedstyles.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/24_Readfilter.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/25_In_memory_image.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/26_Utf8.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/27_Images_Xls.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/28_Iterator.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/29_Advanced_value_binder.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/30_Template.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/31_Document_properties_write.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/31_Document_properties_write_xls.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/37_Page_layout_view.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/38_Clone_worksheet.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/39_Dropdown.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/40_Duplicate_style.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/41_Password.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/42_RichText.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/43_Merge_workbooks.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/44_Worksheet_info.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/45_Quadratic_equation_solver.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/46_ReadHtml.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/data/continents/Africa.txt create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/data/continents/Asia.txt create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/data/continents/Europe.txt create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/data/continents/North America.txt create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/data/continents/Oceania.txt create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Basic/data/continents/South America.txt create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Calculations/Database/DAVERAGE.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Calculations/Database/DCOUNT.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Calculations/Database/DGET.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Calculations/Database/DMAX.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Calculations/Database/DMIN.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Calculations/Database/DPRODUCT.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Calculations/Database/DSTDEV.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Calculations/Database/DSTDEVP.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Calculations/Database/DVAR.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Calculations/Database/DVARP.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Calculations/DateTime/DATE.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Calculations/DateTime/DATEVALUE.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Calculations/DateTime/TIME.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Calculations/DateTime/TIMEVALUE.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Chart/32_Chart_read_write.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Chart/32_Chart_read_write_HTML.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Chart/32_Chart_read_write_PDF.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_area.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_bar.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_bar_stacked.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_column.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_column_2.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_composite.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_line.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_multiple_charts.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_pie.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_pie_custom_colors.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_radar.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_scatter.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_stock.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Chart/34_Chart_update.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Chart/35_Chart_render.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Header.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Pdf/21_Pdf_Domdf.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Pdf/21_Pdf_TCPDF.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Pdf/21_Pdf_mPDF.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Reader/01_Simple_file_reader_using_IOFactory.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Reader/02_Simple_file_reader_using_a_specified_reader.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Reader/03_Simple_file_reader_using_the_IOFactory_to_return_a_reader.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Reader/04_Simple_file_reader_using_the_IOFactory_to_identify_a_reader_to_use.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Reader/05_Simple_file_reader_using_the_read_data_only_option.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Reader/06_Simple_file_reader_loading_all_worksheets.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Reader/07_Simple_file_reader_loading_a_single_named_worksheet.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Reader/08_Simple_file_reader_loading_several_named_worksheets.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Reader/09_Simple_file_reader_using_a_read_filter.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Reader/10_Simple_file_reader_using_a_configurable_read_filter.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Reader/11_Reading_a_workbook_in_chunks_using_a_configurable_read_filter_(version_1).php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Reader/12_Reading_a_workbook_in_chunks_using_a_configurable_read_filter_(version_2).php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Reader/13_Simple_file_reader_for_multiple_CSV_files.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Reader/14_Reading_a_large_CSV_file_in_chunks_to_split_across_multiple_worksheets.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Reader/15_Simple_file_reader_for_tab_separated_value_file_using_the_Advanced_Value_Binder.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Reader/16_Handling_loader_exceptions_using_TryCatch.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Reader/17_Simple_file_reader_loading_several_named_worksheets.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Reader/18_Reading_list_of_worksheets_without_loading_entire_file.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Reader/19_Reading_worksheet_information_without_loading_entire_file.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Reader/20_Reader_worksheet_hyperlink_image.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Reader/21_Reader_CSV_Long_Integers_with_String_Value_Binder.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Reader/sampleData/example1.csv create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Reader/sampleData/example1.tsv create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Reader/sampleData/example1.xls create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Reader/sampleData/example2.csv create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Reader/sampleData/example2.xls create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Reader/sampleData/longIntegers.csv create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Reading_workbook_data/Custom_properties.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Reading_workbook_data/Custom_property_names.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Reading_workbook_data/Properties.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Reading_workbook_data/Worksheet_count_and_names.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Reading_workbook_data/sampleData/example1.xls create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Reading_workbook_data/sampleData/example1.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/Reading_workbook_data/sampleData/example2.xls create mode 100644 vendor/phpoffice/phpspreadsheet/samples/bootstrap/css/bootstrap.min.css create mode 100644 vendor/phpoffice/phpspreadsheet/samples/bootstrap/css/font-awesome.min.css create mode 100644 vendor/phpoffice/phpspreadsheet/samples/bootstrap/css/phpspreadsheet.css create mode 100644 vendor/phpoffice/phpspreadsheet/samples/bootstrap/fonts/FontAwesome.otf create mode 100644 vendor/phpoffice/phpspreadsheet/samples/bootstrap/fonts/fontawesome-webfont.eot create mode 100644 vendor/phpoffice/phpspreadsheet/samples/bootstrap/fonts/fontawesome-webfont.svg create mode 100644 vendor/phpoffice/phpspreadsheet/samples/bootstrap/fonts/fontawesome-webfont.ttf create mode 100644 vendor/phpoffice/phpspreadsheet/samples/bootstrap/fonts/fontawesome-webfont.woff create mode 100644 vendor/phpoffice/phpspreadsheet/samples/bootstrap/fonts/fontawesome-webfont.woff2 create mode 100644 vendor/phpoffice/phpspreadsheet/samples/bootstrap/js/bootstrap.min.js create mode 100644 vendor/phpoffice/phpspreadsheet/samples/bootstrap/js/jquery.min.js create mode 100644 vendor/phpoffice/phpspreadsheet/samples/images/PhpSpreadsheet_logo.png create mode 100644 vendor/phpoffice/phpspreadsheet/samples/images/officelogo.jpg create mode 100644 vendor/phpoffice/phpspreadsheet/samples/images/paid.png create mode 100644 vendor/phpoffice/phpspreadsheet/samples/images/termsconditions.jpg create mode 100644 vendor/phpoffice/phpspreadsheet/samples/index.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/26template.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/27template.xls create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/28iterators.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/30template.xls create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/31docproperties.xls create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/31docproperties.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32chartreadwrite.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32complexChartreadwrite.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteAreaChart1.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteAreaChart2.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteAreaChart3.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteAreaChart3D1.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteAreaPercentageChart1.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteAreaPercentageChart2.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteAreaPercentageChart3D1.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteAreaStackedChart1.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteAreaStackedChart2.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteAreaStackedChart3D1.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteBarChart1.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteBarChart2.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteBarChart3.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteBarChart3D1.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteBarPercentageChart1.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteBarPercentageChart2.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteBarPercentageChart3D1.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteBarStackedChart1.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteBarStackedChart2.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteBarStackedChart3D1.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteBubbleChart1.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteBubbleChart3D1.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteChartWithImages1.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteColumnChart1.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteColumnChart2.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteColumnChart3.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteColumnChart3D1.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteColumnChart4.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteColumnPercentageChart1.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteColumnPercentageChart2.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteColumnPercentageChart3D1.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteColumnStackedChart1.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteColumnStackedChart2.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteColumnStackedChart3D1.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteDonutChart1.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteDonutChart2.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteDonutChart3.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteDonutChart4.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteDonutChartExploded1.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteDonutChartMultiseries1.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteLineChart1.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteLineChart2.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteLineChart3.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteLineChart3D1.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteLineChartNoPointMarkers1.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteLinePercentageChart1.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteLinePercentageChart2.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteLineStackedChart1.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteLineStackedChart2.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwritePieChart1.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwritePieChart2.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwritePieChart3.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwritePieChart3D1.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwritePieChart4.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwritePieChartExploded1.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwritePieChartExploded3D1.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteRadarChart1.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteRadarChart2.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteRadarChart3.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteScatterChart1.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteScatterChart2.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteScatterChart3.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteScatterChart4.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteScatterChart5.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteStockChart1.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteStockChart2.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteStockChart3.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteStockChart4.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteSurfaceChart1.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteSurfaceChart2.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteSurfaceChart3.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/32readwriteSurfaceChart4.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/36writeLineChart1.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/43mergeBook1.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/43mergeBook2.xlsx create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/46readHtml.html create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/Excel2003XMLTest.xml create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/GnumericTest.gnumeric create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/OOCalcTest.ods create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/SylkTest.slk create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/chartSpreadsheet.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/largeSpreadsheet.php create mode 100644 vendor/phpoffice/phpspreadsheet/samples/templates/sampleSpreadsheet.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/Bootstrap.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Calculation.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Category.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTime.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/CyclicReferenceStack.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/Logger.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Exception.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/ExceptionHandler.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/FormulaParser.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/FormulaToken.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Functions.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Logical.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Token/Stack.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/functionlist.txt create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/bg/config create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/bg/functions create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/cs/config create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/cs/functions create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/da/config create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/da/functions create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/de/config create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/de/functions create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/en/uk/config create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/es/config create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/es/functions create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fi/config create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fi/functions create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fr/config create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fr/functions create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/hu/config create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/hu/functions create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/it/config create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/it/functions create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nl/config create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nl/functions create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/no/config create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/no/functions create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pl/config create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pl/functions create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/br/config create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/br/functions create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/config create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/functions create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/ru/config create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/ru/functions create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/sv/config create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/sv/functions create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/tr/config create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/tr/functions create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/AdvancedValueBinder.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/Cell.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/Coordinate.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DataType.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DataValidation.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DataValidator.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DefaultValueBinder.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/Hyperlink.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/IValueBinder.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/StringValueBinder.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Axis.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Chart.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/DataSeries.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/DataSeriesValues.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Exception.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/GridLines.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Layout.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Legend.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/PlotArea.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Properties.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/IRenderer.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/JpGraph.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/PHP Charting Libraries.txt create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/Polyfill.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Title.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Collection/Cells.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Collection/CellsFactory.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Collection/Memory.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Comment.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Document/Properties.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Document/Security.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/DocumentGenerator.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Exception.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/HashTable.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Html.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Migrator.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Sample.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/IComparable.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/IOFactory.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/NamedRange.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/BaseReader.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Csv.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/DefaultReadFilter.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Exception.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Html.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/IReadFilter.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/IReader.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/Properties.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Security/XmlScanner.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Slk.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color/BIFF5.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color/BIFF8.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color/BuiltIn.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/ErrorCode.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Escher.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/MD5.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/RC4.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Style/Border.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Style/FillPattern.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/BaseParserClass.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Chart.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/DataValidations.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/PageSetup.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Properties.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SheetViewOptions.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SheetViews.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Styles.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Theme.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/ReferenceHelper.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/RichText/ITextElement.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/RichText/RichText.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/RichText/Run.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/RichText/TextElement.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Settings.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/CodePage.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Date.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Drawing.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DgContainer.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DgContainer/SpgrContainer.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DgContainer/SpgrContainer/SpContainer.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer/BSE.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer/BSE/Blip.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/File.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Font.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/CHANGELOG.TXT create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/CholeskyDecomposition.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/EigenvalueDecomposition.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/LUDecomposition.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/Matrix.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/QRDecomposition.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/SingularValueDecomposition.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/utils/Maths.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/ChainedBlockStream.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS/File.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS/Root.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLERead.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/PasswordHasher.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/StringHelper.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/TimeZone.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/BestFit.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/ExponentialBestFit.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/LinearBestFit.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/LogarithmicBestFit.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/PowerBestFit.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/Trend.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/XMLWriter.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Xls.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Spreadsheet.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Alignment.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Border.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Borders.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Color.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Conditional.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Fill.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Font.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Protection.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Style.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Supervisor.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter/Column.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter/Column/Rule.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/BaseDrawing.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/CellIterator.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Column.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnCellIterator.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnDimension.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnIterator.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Dimension.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Drawing.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Drawing/Shadow.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/HeaderFooter.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/HeaderFooterDrawing.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Iterator.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/MemoryDrawing.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageMargins.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageSetup.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Protection.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Row.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowCellIterator.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowDimension.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowIterator.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/SheetView.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Worksheet.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/BaseWriter.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Csv.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Exception.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Html.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/IWriter.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Cell/Comment.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Content.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Meta.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/MetaInf.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Mimetype.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Settings.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Styles.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Thumbnails.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/WriterPart.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Dompdf.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Mpdf.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Tcpdf.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/BIFFwriter.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Escher.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Font.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Parser.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Workbook.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Worksheet.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Xf.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Chart.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Comments.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/DocProps.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Rels.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/RelsRibbon.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/RelsVBA.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/StringTable.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Style.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Theme.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Workbook.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php create mode 100644 vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/WriterPart.php create mode 100644 vendor/pimple/pimple/.gitignore create mode 100644 vendor/pimple/pimple/.travis.yml create mode 100644 vendor/pimple/pimple/CHANGELOG create mode 100644 vendor/pimple/pimple/LICENSE create mode 100644 vendor/pimple/pimple/README.rst create mode 100644 vendor/pimple/pimple/composer.json create mode 100644 vendor/pimple/pimple/ext/pimple/.gitignore create mode 100644 vendor/pimple/pimple/ext/pimple/README.md create mode 100644 vendor/pimple/pimple/ext/pimple/config.m4 create mode 100644 vendor/pimple/pimple/ext/pimple/config.w32 create mode 100644 vendor/pimple/pimple/ext/pimple/php_pimple.h create mode 100644 vendor/pimple/pimple/ext/pimple/pimple.c create mode 100644 vendor/pimple/pimple/ext/pimple/pimple_compat.h create mode 100644 vendor/pimple/pimple/ext/pimple/tests/001.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/002.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/003.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/004.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/005.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/006.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/007.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/008.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/009.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/010.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/011.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/012.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/013.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/014.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/015.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/016.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/017.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/017_1.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/018.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/019.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/bench.phpb create mode 100644 vendor/pimple/pimple/ext/pimple/tests/bench_shared.phpb create mode 100644 vendor/pimple/pimple/phpunit.xml.dist create mode 100644 vendor/pimple/pimple/src/Pimple/Container.php create mode 100644 vendor/pimple/pimple/src/Pimple/Exception/ExpectedInvokableException.php create mode 100644 vendor/pimple/pimple/src/Pimple/Exception/FrozenServiceException.php create mode 100644 vendor/pimple/pimple/src/Pimple/Exception/InvalidServiceIdentifierException.php create mode 100644 vendor/pimple/pimple/src/Pimple/Exception/UnknownIdentifierException.php create mode 100644 vendor/pimple/pimple/src/Pimple/Psr11/Container.php create mode 100644 vendor/pimple/pimple/src/Pimple/Psr11/ServiceLocator.php create mode 100644 vendor/pimple/pimple/src/Pimple/ServiceIterator.php create mode 100644 vendor/pimple/pimple/src/Pimple/ServiceProviderInterface.php create mode 100644 vendor/pimple/pimple/src/Pimple/Tests/Fixtures/Invokable.php create mode 100644 vendor/pimple/pimple/src/Pimple/Tests/Fixtures/NonInvokable.php create mode 100644 vendor/pimple/pimple/src/Pimple/Tests/Fixtures/PimpleServiceProvider.php create mode 100644 vendor/pimple/pimple/src/Pimple/Tests/Fixtures/Service.php create mode 100644 vendor/pimple/pimple/src/Pimple/Tests/PimpleServiceProviderInterfaceTest.php create mode 100644 vendor/pimple/pimple/src/Pimple/Tests/PimpleTest.php create mode 100644 vendor/pimple/pimple/src/Pimple/Tests/Psr11/ContainerTest.php create mode 100644 vendor/pimple/pimple/src/Pimple/Tests/Psr11/ServiceLocatorTest.php create mode 100644 vendor/pimple/pimple/src/Pimple/Tests/ServiceIteratorTest.php create mode 100644 vendor/psr/cache/CHANGELOG.md create mode 100644 vendor/psr/cache/LICENSE.txt create mode 100644 vendor/psr/cache/README.md create mode 100644 vendor/psr/cache/composer.json create mode 100644 vendor/psr/cache/src/CacheException.php create mode 100644 vendor/psr/cache/src/CacheItemInterface.php create mode 100644 vendor/psr/cache/src/CacheItemPoolInterface.php create mode 100644 vendor/psr/cache/src/InvalidArgumentException.php create mode 100644 vendor/psr/container/.gitignore create mode 100644 vendor/psr/container/LICENSE create mode 100644 vendor/psr/container/README.md create mode 100644 vendor/psr/container/composer.json create mode 100644 vendor/psr/container/src/ContainerExceptionInterface.php create mode 100644 vendor/psr/container/src/ContainerInterface.php create mode 100644 vendor/psr/container/src/NotFoundExceptionInterface.php create mode 100644 vendor/psr/http-message/CHANGELOG.md create mode 100644 vendor/psr/http-message/LICENSE create mode 100644 vendor/psr/http-message/README.md create mode 100644 vendor/psr/http-message/composer.json create mode 100644 vendor/psr/http-message/src/MessageInterface.php create mode 100644 vendor/psr/http-message/src/RequestInterface.php create mode 100644 vendor/psr/http-message/src/ResponseInterface.php create mode 100644 vendor/psr/http-message/src/ServerRequestInterface.php create mode 100644 vendor/psr/http-message/src/StreamInterface.php create mode 100644 vendor/psr/http-message/src/UploadedFileInterface.php create mode 100644 vendor/psr/http-message/src/UriInterface.php create mode 100644 vendor/psr/log/LICENSE create mode 100644 vendor/psr/log/Psr/Log/AbstractLogger.php create mode 100644 vendor/psr/log/Psr/Log/InvalidArgumentException.php create mode 100644 vendor/psr/log/Psr/Log/LogLevel.php create mode 100644 vendor/psr/log/Psr/Log/LoggerAwareInterface.php create mode 100644 vendor/psr/log/Psr/Log/LoggerAwareTrait.php create mode 100644 vendor/psr/log/Psr/Log/LoggerInterface.php create mode 100644 vendor/psr/log/Psr/Log/LoggerTrait.php create mode 100644 vendor/psr/log/Psr/Log/NullLogger.php create mode 100644 vendor/psr/log/Psr/Log/Test/DummyTest.php create mode 100644 vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php create mode 100644 vendor/psr/log/Psr/Log/Test/TestLogger.php create mode 100644 vendor/psr/log/README.md create mode 100644 vendor/psr/log/composer.json create mode 100644 vendor/psr/simple-cache/.editorconfig create mode 100644 vendor/psr/simple-cache/LICENSE.md create mode 100644 vendor/psr/simple-cache/README.md create mode 100644 vendor/psr/simple-cache/composer.json create mode 100644 vendor/psr/simple-cache/src/CacheException.php create mode 100644 vendor/psr/simple-cache/src/CacheInterface.php create mode 100644 vendor/psr/simple-cache/src/InvalidArgumentException.php create mode 100644 vendor/ralouphie/getallheaders/LICENSE create mode 100644 vendor/ralouphie/getallheaders/README.md create mode 100644 vendor/ralouphie/getallheaders/composer.json create mode 100644 vendor/ralouphie/getallheaders/src/getallheaders.php create mode 100644 vendor/symfony/cache-contracts/.gitignore create mode 100644 vendor/symfony/cache-contracts/CacheInterface.php create mode 100644 vendor/symfony/cache-contracts/CacheTrait.php create mode 100644 vendor/symfony/cache-contracts/CallbackInterface.php create mode 100644 vendor/symfony/cache-contracts/ItemInterface.php create mode 100644 vendor/symfony/cache-contracts/LICENSE create mode 100644 vendor/symfony/cache-contracts/README.md create mode 100644 vendor/symfony/cache-contracts/TagAwareCacheInterface.php create mode 100644 vendor/symfony/cache-contracts/composer.json create mode 100644 vendor/symfony/cache/Adapter/AbstractAdapter.php create mode 100644 vendor/symfony/cache/Adapter/AbstractTagAwareAdapter.php create mode 100644 vendor/symfony/cache/Adapter/AdapterInterface.php create mode 100644 vendor/symfony/cache/Adapter/ApcuAdapter.php create mode 100644 vendor/symfony/cache/Adapter/ArrayAdapter.php create mode 100644 vendor/symfony/cache/Adapter/ChainAdapter.php create mode 100644 vendor/symfony/cache/Adapter/DoctrineAdapter.php create mode 100644 vendor/symfony/cache/Adapter/FilesystemAdapter.php create mode 100644 vendor/symfony/cache/Adapter/FilesystemTagAwareAdapter.php create mode 100644 vendor/symfony/cache/Adapter/MemcachedAdapter.php create mode 100644 vendor/symfony/cache/Adapter/NullAdapter.php create mode 100644 vendor/symfony/cache/Adapter/PdoAdapter.php create mode 100644 vendor/symfony/cache/Adapter/PhpArrayAdapter.php create mode 100644 vendor/symfony/cache/Adapter/PhpFilesAdapter.php create mode 100644 vendor/symfony/cache/Adapter/ProxyAdapter.php create mode 100644 vendor/symfony/cache/Adapter/Psr16Adapter.php create mode 100644 vendor/symfony/cache/Adapter/RedisAdapter.php create mode 100644 vendor/symfony/cache/Adapter/RedisTagAwareAdapter.php create mode 100644 vendor/symfony/cache/Adapter/SimpleCacheAdapter.php create mode 100644 vendor/symfony/cache/Adapter/TagAwareAdapter.php create mode 100644 vendor/symfony/cache/Adapter/TagAwareAdapterInterface.php create mode 100644 vendor/symfony/cache/Adapter/TraceableAdapter.php create mode 100644 vendor/symfony/cache/Adapter/TraceableTagAwareAdapter.php create mode 100644 vendor/symfony/cache/CHANGELOG.md create mode 100644 vendor/symfony/cache/CacheItem.php create mode 100644 vendor/symfony/cache/DataCollector/CacheDataCollector.php create mode 100644 vendor/symfony/cache/DependencyInjection/CacheCollectorPass.php create mode 100644 vendor/symfony/cache/DependencyInjection/CachePoolClearerPass.php create mode 100644 vendor/symfony/cache/DependencyInjection/CachePoolPass.php create mode 100644 vendor/symfony/cache/DependencyInjection/CachePoolPrunerPass.php create mode 100644 vendor/symfony/cache/DoctrineProvider.php create mode 100644 vendor/symfony/cache/Exception/CacheException.php create mode 100644 vendor/symfony/cache/Exception/InvalidArgumentException.php create mode 100644 vendor/symfony/cache/Exception/LogicException.php create mode 100644 vendor/symfony/cache/LICENSE create mode 100644 vendor/symfony/cache/LockRegistry.php create mode 100644 vendor/symfony/cache/Marshaller/DefaultMarshaller.php create mode 100644 vendor/symfony/cache/Marshaller/DeflateMarshaller.php create mode 100644 vendor/symfony/cache/Marshaller/MarshallerInterface.php create mode 100644 vendor/symfony/cache/Marshaller/TagAwareMarshaller.php create mode 100644 vendor/symfony/cache/PruneableInterface.php create mode 100644 vendor/symfony/cache/Psr16Cache.php create mode 100644 vendor/symfony/cache/README.md create mode 100644 vendor/symfony/cache/ResettableInterface.php create mode 100644 vendor/symfony/cache/Simple/AbstractCache.php create mode 100644 vendor/symfony/cache/Simple/ApcuCache.php create mode 100644 vendor/symfony/cache/Simple/ArrayCache.php create mode 100644 vendor/symfony/cache/Simple/ChainCache.php create mode 100644 vendor/symfony/cache/Simple/DoctrineCache.php create mode 100644 vendor/symfony/cache/Simple/FilesystemCache.php create mode 100644 vendor/symfony/cache/Simple/MemcachedCache.php create mode 100644 vendor/symfony/cache/Simple/NullCache.php create mode 100644 vendor/symfony/cache/Simple/PdoCache.php create mode 100644 vendor/symfony/cache/Simple/PhpArrayCache.php create mode 100644 vendor/symfony/cache/Simple/PhpFilesCache.php create mode 100644 vendor/symfony/cache/Simple/Psr6Cache.php create mode 100644 vendor/symfony/cache/Simple/RedisCache.php create mode 100644 vendor/symfony/cache/Simple/TraceableCache.php create mode 100644 vendor/symfony/cache/Traits/AbstractAdapterTrait.php create mode 100644 vendor/symfony/cache/Traits/AbstractTrait.php create mode 100644 vendor/symfony/cache/Traits/ApcuTrait.php create mode 100644 vendor/symfony/cache/Traits/ArrayTrait.php create mode 100644 vendor/symfony/cache/Traits/ContractsTrait.php create mode 100644 vendor/symfony/cache/Traits/DoctrineTrait.php create mode 100644 vendor/symfony/cache/Traits/FilesystemCommonTrait.php create mode 100644 vendor/symfony/cache/Traits/FilesystemTrait.php create mode 100644 vendor/symfony/cache/Traits/MemcachedTrait.php create mode 100644 vendor/symfony/cache/Traits/PdoTrait.php create mode 100644 vendor/symfony/cache/Traits/PhpArrayTrait.php create mode 100644 vendor/symfony/cache/Traits/PhpFilesTrait.php create mode 100644 vendor/symfony/cache/Traits/ProxyTrait.php create mode 100644 vendor/symfony/cache/Traits/RedisClusterNodeProxy.php create mode 100644 vendor/symfony/cache/Traits/RedisClusterProxy.php create mode 100644 vendor/symfony/cache/Traits/RedisProxy.php create mode 100644 vendor/symfony/cache/Traits/RedisTrait.php create mode 100644 vendor/symfony/cache/composer.json create mode 100644 vendor/symfony/event-dispatcher-contracts/.gitignore create mode 100644 vendor/symfony/event-dispatcher-contracts/Event.php create mode 100644 vendor/symfony/event-dispatcher-contracts/EventDispatcherInterface.php create mode 100644 vendor/symfony/event-dispatcher-contracts/LICENSE create mode 100644 vendor/symfony/event-dispatcher-contracts/README.md create mode 100644 vendor/symfony/event-dispatcher-contracts/composer.json create mode 100644 vendor/symfony/event-dispatcher/CHANGELOG.md create mode 100644 vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php create mode 100644 vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php create mode 100644 vendor/symfony/event-dispatcher/Debug/WrappedListener.php create mode 100644 vendor/symfony/event-dispatcher/DependencyInjection/AddEventAliasesPass.php create mode 100644 vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php create mode 100644 vendor/symfony/event-dispatcher/Event.php create mode 100644 vendor/symfony/event-dispatcher/EventDispatcher.php create mode 100644 vendor/symfony/event-dispatcher/EventDispatcherInterface.php create mode 100644 vendor/symfony/event-dispatcher/EventSubscriberInterface.php create mode 100644 vendor/symfony/event-dispatcher/GenericEvent.php create mode 100644 vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php create mode 100644 vendor/symfony/event-dispatcher/LICENSE create mode 100644 vendor/symfony/event-dispatcher/LegacyEventDispatcherProxy.php create mode 100644 vendor/symfony/event-dispatcher/LegacyEventProxy.php create mode 100644 vendor/symfony/event-dispatcher/README.md create mode 100644 vendor/symfony/event-dispatcher/composer.json create mode 100644 vendor/symfony/finder/CHANGELOG.md create mode 100644 vendor/symfony/finder/Comparator/Comparator.php create mode 100644 vendor/symfony/finder/Comparator/DateComparator.php create mode 100644 vendor/symfony/finder/Comparator/NumberComparator.php create mode 100644 vendor/symfony/finder/Exception/AccessDeniedException.php create mode 100644 vendor/symfony/finder/Exception/DirectoryNotFoundException.php create mode 100644 vendor/symfony/finder/Finder.php create mode 100644 vendor/symfony/finder/Gitignore.php create mode 100644 vendor/symfony/finder/Glob.php create mode 100644 vendor/symfony/finder/Iterator/CustomFilterIterator.php create mode 100644 vendor/symfony/finder/Iterator/DateRangeFilterIterator.php create mode 100644 vendor/symfony/finder/Iterator/DepthRangeFilterIterator.php create mode 100644 vendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php create mode 100644 vendor/symfony/finder/Iterator/FileTypeFilterIterator.php create mode 100644 vendor/symfony/finder/Iterator/FilecontentFilterIterator.php create mode 100644 vendor/symfony/finder/Iterator/FilenameFilterIterator.php create mode 100644 vendor/symfony/finder/Iterator/LazyIterator.php create mode 100644 vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php create mode 100644 vendor/symfony/finder/Iterator/PathFilterIterator.php create mode 100644 vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php create mode 100644 vendor/symfony/finder/Iterator/SizeRangeFilterIterator.php create mode 100644 vendor/symfony/finder/Iterator/SortableIterator.php create mode 100644 vendor/symfony/finder/LICENSE create mode 100644 vendor/symfony/finder/README.md create mode 100644 vendor/symfony/finder/SplFileInfo.php create mode 100644 vendor/symfony/finder/composer.json create mode 100644 vendor/symfony/http-foundation/AcceptHeader.php create mode 100644 vendor/symfony/http-foundation/AcceptHeaderItem.php create mode 100644 vendor/symfony/http-foundation/ApacheRequest.php create mode 100644 vendor/symfony/http-foundation/BinaryFileResponse.php create mode 100644 vendor/symfony/http-foundation/CHANGELOG.md create mode 100644 vendor/symfony/http-foundation/Cookie.php create mode 100644 vendor/symfony/http-foundation/Exception/ConflictingHeadersException.php create mode 100644 vendor/symfony/http-foundation/Exception/RequestExceptionInterface.php create mode 100644 vendor/symfony/http-foundation/Exception/SuspiciousOperationException.php create mode 100644 vendor/symfony/http-foundation/ExpressionRequestMatcher.php create mode 100644 vendor/symfony/http-foundation/File/Exception/AccessDeniedException.php create mode 100644 vendor/symfony/http-foundation/File/Exception/CannotWriteFileException.php create mode 100644 vendor/symfony/http-foundation/File/Exception/ExtensionFileException.php create mode 100644 vendor/symfony/http-foundation/File/Exception/FileException.php create mode 100644 vendor/symfony/http-foundation/File/Exception/FileNotFoundException.php create mode 100644 vendor/symfony/http-foundation/File/Exception/FormSizeFileException.php create mode 100644 vendor/symfony/http-foundation/File/Exception/IniSizeFileException.php create mode 100644 vendor/symfony/http-foundation/File/Exception/NoFileException.php create mode 100644 vendor/symfony/http-foundation/File/Exception/NoTmpDirFileException.php create mode 100644 vendor/symfony/http-foundation/File/Exception/PartialFileException.php create mode 100644 vendor/symfony/http-foundation/File/Exception/UnexpectedTypeException.php create mode 100644 vendor/symfony/http-foundation/File/Exception/UploadException.php create mode 100644 vendor/symfony/http-foundation/File/File.php create mode 100644 vendor/symfony/http-foundation/File/MimeType/ExtensionGuesser.php create mode 100644 vendor/symfony/http-foundation/File/MimeType/ExtensionGuesserInterface.php create mode 100644 vendor/symfony/http-foundation/File/MimeType/FileBinaryMimeTypeGuesser.php create mode 100644 vendor/symfony/http-foundation/File/MimeType/FileinfoMimeTypeGuesser.php create mode 100644 vendor/symfony/http-foundation/File/MimeType/MimeTypeExtensionGuesser.php create mode 100644 vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesser.php create mode 100644 vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesserInterface.php create mode 100644 vendor/symfony/http-foundation/File/Stream.php create mode 100644 vendor/symfony/http-foundation/File/UploadedFile.php create mode 100644 vendor/symfony/http-foundation/FileBag.php create mode 100644 vendor/symfony/http-foundation/HeaderBag.php create mode 100644 vendor/symfony/http-foundation/HeaderUtils.php create mode 100644 vendor/symfony/http-foundation/IpUtils.php create mode 100644 vendor/symfony/http-foundation/JsonResponse.php create mode 100644 vendor/symfony/http-foundation/LICENSE create mode 100644 vendor/symfony/http-foundation/ParameterBag.php create mode 100644 vendor/symfony/http-foundation/README.md create mode 100644 vendor/symfony/http-foundation/RedirectResponse.php create mode 100644 vendor/symfony/http-foundation/Request.php create mode 100644 vendor/symfony/http-foundation/RequestMatcher.php create mode 100644 vendor/symfony/http-foundation/RequestMatcherInterface.php create mode 100644 vendor/symfony/http-foundation/RequestStack.php create mode 100644 vendor/symfony/http-foundation/Response.php create mode 100644 vendor/symfony/http-foundation/ResponseHeaderBag.php create mode 100644 vendor/symfony/http-foundation/ServerBag.php create mode 100644 vendor/symfony/http-foundation/Session/Attribute/AttributeBag.php create mode 100644 vendor/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php create mode 100644 vendor/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php create mode 100644 vendor/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php create mode 100644 vendor/symfony/http-foundation/Session/Flash/FlashBag.php create mode 100644 vendor/symfony/http-foundation/Session/Flash/FlashBagInterface.php create mode 100644 vendor/symfony/http-foundation/Session/Session.php create mode 100644 vendor/symfony/http-foundation/Session/SessionBagInterface.php create mode 100644 vendor/symfony/http-foundation/Session/SessionBagProxy.php create mode 100644 vendor/symfony/http-foundation/Session/SessionInterface.php create mode 100644 vendor/symfony/http-foundation/Session/SessionUtils.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/Handler/MigratingSessionHandler.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/Handler/RedisSessionHandler.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/Handler/SessionHandlerFactory.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/MetadataBag.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/SessionStorageInterface.php create mode 100644 vendor/symfony/http-foundation/StreamedResponse.php create mode 100644 vendor/symfony/http-foundation/Test/Constraint/RequestAttributeValueSame.php create mode 100644 vendor/symfony/http-foundation/Test/Constraint/ResponseCookieValueSame.php create mode 100644 vendor/symfony/http-foundation/Test/Constraint/ResponseHasCookie.php create mode 100644 vendor/symfony/http-foundation/Test/Constraint/ResponseHasHeader.php create mode 100644 vendor/symfony/http-foundation/Test/Constraint/ResponseHeaderSame.php create mode 100644 vendor/symfony/http-foundation/Test/Constraint/ResponseIsRedirected.php create mode 100644 vendor/symfony/http-foundation/Test/Constraint/ResponseIsSuccessful.php create mode 100644 vendor/symfony/http-foundation/Test/Constraint/ResponseStatusCodeSame.php create mode 100644 vendor/symfony/http-foundation/UrlHelper.php create mode 100644 vendor/symfony/http-foundation/composer.json create mode 100644 vendor/symfony/mime/Address.php create mode 100644 vendor/symfony/mime/BodyRendererInterface.php create mode 100644 vendor/symfony/mime/CHANGELOG.md create mode 100644 vendor/symfony/mime/CharacterStream.php create mode 100644 vendor/symfony/mime/Crypto/SMime.php create mode 100644 vendor/symfony/mime/Crypto/SMimeEncrypter.php create mode 100644 vendor/symfony/mime/Crypto/SMimeSigner.php create mode 100644 vendor/symfony/mime/DependencyInjection/AddMimeTypeGuesserPass.php create mode 100644 vendor/symfony/mime/Email.php create mode 100644 vendor/symfony/mime/Encoder/AddressEncoderInterface.php create mode 100644 vendor/symfony/mime/Encoder/Base64ContentEncoder.php create mode 100644 vendor/symfony/mime/Encoder/Base64Encoder.php create mode 100644 vendor/symfony/mime/Encoder/Base64MimeHeaderEncoder.php create mode 100644 vendor/symfony/mime/Encoder/ContentEncoderInterface.php create mode 100644 vendor/symfony/mime/Encoder/EightBitContentEncoder.php create mode 100644 vendor/symfony/mime/Encoder/EncoderInterface.php create mode 100644 vendor/symfony/mime/Encoder/IdnAddressEncoder.php create mode 100644 vendor/symfony/mime/Encoder/MimeHeaderEncoderInterface.php create mode 100644 vendor/symfony/mime/Encoder/QpContentEncoder.php create mode 100644 vendor/symfony/mime/Encoder/QpEncoder.php create mode 100644 vendor/symfony/mime/Encoder/QpMimeHeaderEncoder.php create mode 100644 vendor/symfony/mime/Encoder/Rfc2231Encoder.php create mode 100644 vendor/symfony/mime/Exception/AddressEncoderException.php create mode 100644 vendor/symfony/mime/Exception/ExceptionInterface.php create mode 100644 vendor/symfony/mime/Exception/InvalidArgumentException.php create mode 100644 vendor/symfony/mime/Exception/LogicException.php create mode 100644 vendor/symfony/mime/Exception/RfcComplianceException.php create mode 100644 vendor/symfony/mime/Exception/RuntimeException.php create mode 100644 vendor/symfony/mime/FileBinaryMimeTypeGuesser.php create mode 100644 vendor/symfony/mime/FileinfoMimeTypeGuesser.php create mode 100644 vendor/symfony/mime/Header/AbstractHeader.php create mode 100644 vendor/symfony/mime/Header/DateHeader.php create mode 100644 vendor/symfony/mime/Header/HeaderInterface.php create mode 100644 vendor/symfony/mime/Header/Headers.php create mode 100644 vendor/symfony/mime/Header/IdentificationHeader.php create mode 100644 vendor/symfony/mime/Header/MailboxHeader.php create mode 100644 vendor/symfony/mime/Header/MailboxListHeader.php create mode 100644 vendor/symfony/mime/Header/ParameterizedHeader.php create mode 100644 vendor/symfony/mime/Header/PathHeader.php create mode 100644 vendor/symfony/mime/Header/UnstructuredHeader.php create mode 100644 vendor/symfony/mime/LICENSE create mode 100644 vendor/symfony/mime/Message.php create mode 100644 vendor/symfony/mime/MessageConverter.php create mode 100644 vendor/symfony/mime/MimeTypeGuesserInterface.php create mode 100644 vendor/symfony/mime/MimeTypes.php create mode 100644 vendor/symfony/mime/MimeTypesInterface.php create mode 100644 vendor/symfony/mime/Part/AbstractMultipartPart.php create mode 100644 vendor/symfony/mime/Part/AbstractPart.php create mode 100644 vendor/symfony/mime/Part/DataPart.php create mode 100644 vendor/symfony/mime/Part/MessagePart.php create mode 100644 vendor/symfony/mime/Part/Multipart/AlternativePart.php create mode 100644 vendor/symfony/mime/Part/Multipart/DigestPart.php create mode 100644 vendor/symfony/mime/Part/Multipart/FormDataPart.php create mode 100644 vendor/symfony/mime/Part/Multipart/MixedPart.php create mode 100644 vendor/symfony/mime/Part/Multipart/RelatedPart.php create mode 100644 vendor/symfony/mime/Part/SMimePart.php create mode 100644 vendor/symfony/mime/Part/TextPart.php create mode 100644 vendor/symfony/mime/README.md create mode 100644 vendor/symfony/mime/RawMessage.php create mode 100644 vendor/symfony/mime/Resources/bin/update_mime_types.php create mode 100644 vendor/symfony/mime/Test/Constraint/EmailAddressContains.php create mode 100644 vendor/symfony/mime/Test/Constraint/EmailAttachmentCount.php create mode 100644 vendor/symfony/mime/Test/Constraint/EmailHasHeader.php create mode 100644 vendor/symfony/mime/Test/Constraint/EmailHeaderSame.php create mode 100644 vendor/symfony/mime/Test/Constraint/EmailHtmlBodyContains.php create mode 100644 vendor/symfony/mime/Test/Constraint/EmailTextBodyContains.php create mode 100644 vendor/symfony/mime/composer.json create mode 100644 vendor/symfony/polyfill-intl-idn/Idn.php create mode 100644 vendor/symfony/polyfill-intl-idn/Info.php create mode 100644 vendor/symfony/polyfill-intl-idn/LICENSE create mode 100644 vendor/symfony/polyfill-intl-idn/README.md create mode 100644 vendor/symfony/polyfill-intl-idn/Resources/unidata/DisallowedRanges.php create mode 100644 vendor/symfony/polyfill-intl-idn/Resources/unidata/Regex.php create mode 100644 vendor/symfony/polyfill-intl-idn/Resources/unidata/deviation.php create mode 100644 vendor/symfony/polyfill-intl-idn/Resources/unidata/disallowed.php create mode 100644 vendor/symfony/polyfill-intl-idn/Resources/unidata/disallowed_STD3_mapped.php create mode 100644 vendor/symfony/polyfill-intl-idn/Resources/unidata/disallowed_STD3_valid.php create mode 100644 vendor/symfony/polyfill-intl-idn/Resources/unidata/ignored.php create mode 100644 vendor/symfony/polyfill-intl-idn/Resources/unidata/mapped.php create mode 100644 vendor/symfony/polyfill-intl-idn/Resources/unidata/virama.php create mode 100644 vendor/symfony/polyfill-intl-idn/bootstrap.php create mode 100644 vendor/symfony/polyfill-intl-idn/bootstrap80.php create mode 100644 vendor/symfony/polyfill-intl-idn/composer.json create mode 100644 vendor/symfony/polyfill-intl-normalizer/LICENSE create mode 100644 vendor/symfony/polyfill-intl-normalizer/Normalizer.php create mode 100644 vendor/symfony/polyfill-intl-normalizer/README.md create mode 100644 vendor/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php create mode 100644 vendor/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalComposition.php create mode 100644 vendor/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalDecomposition.php create mode 100644 vendor/symfony/polyfill-intl-normalizer/Resources/unidata/combiningClass.php create mode 100644 vendor/symfony/polyfill-intl-normalizer/Resources/unidata/compatibilityDecomposition.php create mode 100644 vendor/symfony/polyfill-intl-normalizer/bootstrap.php create mode 100644 vendor/symfony/polyfill-intl-normalizer/bootstrap80.php create mode 100644 vendor/symfony/polyfill-intl-normalizer/composer.json create mode 100644 vendor/symfony/polyfill-mbstring/LICENSE create mode 100644 vendor/symfony/polyfill-mbstring/Mbstring.php create mode 100644 vendor/symfony/polyfill-mbstring/README.md create mode 100644 vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php create mode 100644 vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php create mode 100644 vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php create mode 100644 vendor/symfony/polyfill-mbstring/bootstrap.php create mode 100644 vendor/symfony/polyfill-mbstring/bootstrap80.php create mode 100644 vendor/symfony/polyfill-mbstring/composer.json create mode 100644 vendor/symfony/polyfill-php72/LICENSE create mode 100644 vendor/symfony/polyfill-php72/Php72.php create mode 100644 vendor/symfony/polyfill-php72/README.md create mode 100644 vendor/symfony/polyfill-php72/bootstrap.php create mode 100644 vendor/symfony/polyfill-php72/composer.json create mode 100644 vendor/symfony/polyfill-php73/LICENSE create mode 100644 vendor/symfony/polyfill-php73/Php73.php create mode 100644 vendor/symfony/polyfill-php73/README.md create mode 100644 vendor/symfony/polyfill-php73/Resources/stubs/JsonException.php create mode 100644 vendor/symfony/polyfill-php73/bootstrap.php create mode 100644 vendor/symfony/polyfill-php73/composer.json create mode 100644 vendor/symfony/polyfill-php80/LICENSE create mode 100644 vendor/symfony/polyfill-php80/Php80.php create mode 100644 vendor/symfony/polyfill-php80/PhpToken.php create mode 100644 vendor/symfony/polyfill-php80/README.md create mode 100644 vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php create mode 100644 vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php create mode 100644 vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php create mode 100644 vendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php create mode 100644 vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php create mode 100644 vendor/symfony/polyfill-php80/bootstrap.php create mode 100644 vendor/symfony/polyfill-php80/composer.json create mode 100644 vendor/symfony/psr-http-message-bridge/.gitignore create mode 100644 vendor/symfony/psr-http-message-bridge/.php_cs.dist create mode 100644 vendor/symfony/psr-http-message-bridge/.travis.yml create mode 100644 vendor/symfony/psr-http-message-bridge/CHANGELOG.md create mode 100644 vendor/symfony/psr-http-message-bridge/Factory/DiactorosFactory.php create mode 100644 vendor/symfony/psr-http-message-bridge/Factory/HttpFoundationFactory.php create mode 100644 vendor/symfony/psr-http-message-bridge/Factory/PsrHttpFactory.php create mode 100644 vendor/symfony/psr-http-message-bridge/Factory/UploadedFile.php create mode 100644 vendor/symfony/psr-http-message-bridge/HttpFoundationFactoryInterface.php create mode 100644 vendor/symfony/psr-http-message-bridge/HttpMessageFactoryInterface.php create mode 100644 vendor/symfony/psr-http-message-bridge/LICENSE create mode 100644 vendor/symfony/psr-http-message-bridge/README.md create mode 100644 vendor/symfony/psr-http-message-bridge/Tests/Factory/AbstractHttpMessageFactoryTest.php create mode 100644 vendor/symfony/psr-http-message-bridge/Tests/Factory/DiactorosFactoryTest.php create mode 100644 vendor/symfony/psr-http-message-bridge/Tests/Factory/HttpFoundationFactoryTest.php create mode 100644 vendor/symfony/psr-http-message-bridge/Tests/Factory/PsrHttpFactoryTest.php create mode 100644 vendor/symfony/psr-http-message-bridge/Tests/Fixtures/Message.php create mode 100644 vendor/symfony/psr-http-message-bridge/Tests/Fixtures/Response.php create mode 100644 vendor/symfony/psr-http-message-bridge/Tests/Fixtures/ServerRequest.php create mode 100644 vendor/symfony/psr-http-message-bridge/Tests/Fixtures/Stream.php create mode 100644 vendor/symfony/psr-http-message-bridge/Tests/Fixtures/UploadedFile.php create mode 100644 vendor/symfony/psr-http-message-bridge/Tests/Fixtures/Uri.php create mode 100644 vendor/symfony/psr-http-message-bridge/Tests/Functional/CovertTest.php create mode 100644 vendor/symfony/psr-http-message-bridge/composer.json create mode 100644 vendor/symfony/psr-http-message-bridge/phpunit.xml.dist create mode 100644 vendor/symfony/service-contracts/.gitignore create mode 100644 vendor/symfony/service-contracts/LICENSE create mode 100644 vendor/symfony/service-contracts/README.md create mode 100644 vendor/symfony/service-contracts/ResetInterface.php create mode 100644 vendor/symfony/service-contracts/ServiceLocatorTrait.php create mode 100644 vendor/symfony/service-contracts/ServiceProviderInterface.php create mode 100644 vendor/symfony/service-contracts/ServiceSubscriberInterface.php create mode 100644 vendor/symfony/service-contracts/ServiceSubscriberTrait.php create mode 100644 vendor/symfony/service-contracts/Test/ServiceLocatorTest.php create mode 100644 vendor/symfony/service-contracts/composer.json create mode 100644 vendor/symfony/var-exporter/CHANGELOG.md create mode 100644 vendor/symfony/var-exporter/Exception/ClassNotFoundException.php create mode 100644 vendor/symfony/var-exporter/Exception/ExceptionInterface.php create mode 100644 vendor/symfony/var-exporter/Exception/NotInstantiableTypeException.php create mode 100644 vendor/symfony/var-exporter/Instantiator.php create mode 100644 vendor/symfony/var-exporter/Internal/Exporter.php create mode 100644 vendor/symfony/var-exporter/Internal/Hydrator.php create mode 100644 vendor/symfony/var-exporter/Internal/Reference.php create mode 100644 vendor/symfony/var-exporter/Internal/Registry.php create mode 100644 vendor/symfony/var-exporter/Internal/Values.php create mode 100644 vendor/symfony/var-exporter/LICENSE create mode 100644 vendor/symfony/var-exporter/README.md create mode 100644 vendor/symfony/var-exporter/VarExporter.php create mode 100644 vendor/symfony/var-exporter/composer.json create mode 100644 vendor/topthink/think-captcha/.gitignore create mode 100644 vendor/topthink/think-captcha/LICENSE create mode 100644 vendor/topthink/think-captcha/README.md create mode 100644 vendor/topthink/think-captcha/assets/bgs/1.jpg create mode 100644 vendor/topthink/think-captcha/assets/bgs/2.jpg create mode 100644 vendor/topthink/think-captcha/assets/bgs/3.jpg create mode 100644 vendor/topthink/think-captcha/assets/bgs/4.jpg create mode 100644 vendor/topthink/think-captcha/assets/bgs/5.jpg create mode 100644 vendor/topthink/think-captcha/assets/bgs/6.jpg create mode 100644 vendor/topthink/think-captcha/assets/bgs/7.jpg create mode 100644 vendor/topthink/think-captcha/assets/bgs/8.jpg create mode 100644 vendor/topthink/think-captcha/assets/ttfs/1.ttf create mode 100644 vendor/topthink/think-captcha/assets/ttfs/2.ttf create mode 100644 vendor/topthink/think-captcha/assets/ttfs/3.ttf create mode 100644 vendor/topthink/think-captcha/assets/ttfs/4.ttf create mode 100644 vendor/topthink/think-captcha/assets/ttfs/5.ttf create mode 100644 vendor/topthink/think-captcha/assets/ttfs/6.ttf create mode 100644 vendor/topthink/think-captcha/assets/zhttfs/1.ttf create mode 100644 vendor/topthink/think-captcha/composer.json create mode 100644 vendor/topthink/think-captcha/src/Captcha.php create mode 100644 vendor/topthink/think-captcha/src/CaptchaController.php create mode 100644 vendor/topthink/think-captcha/src/helper.php create mode 100644 vendor/topthink/think-helper/.gitignore create mode 100644 vendor/topthink/think-helper/LICENSE create mode 100644 vendor/topthink/think-helper/README.md create mode 100644 vendor/topthink/think-helper/composer.json create mode 100644 vendor/topthink/think-helper/src/Arr.php create mode 100644 vendor/topthink/think-helper/src/Hash.php create mode 100644 vendor/topthink/think-helper/src/Str.php create mode 100644 vendor/topthink/think-helper/src/Time.php create mode 100644 vendor/topthink/think-helper/src/hash/Bcrypt.php create mode 100644 vendor/topthink/think-helper/src/hash/Md5.php create mode 100644 vendor/topthink/think-helper/src/helper.php create mode 100644 vendor/topthink/think-installer/.gitignore create mode 100644 vendor/topthink/think-installer/composer.json create mode 100644 vendor/topthink/think-installer/src/LibraryInstaller.php create mode 100644 vendor/topthink/think-installer/src/Plugin.php create mode 100644 vendor/topthink/think-installer/src/Promise.php create mode 100644 vendor/topthink/think-installer/src/ThinkExtend.php create mode 100644 vendor/topthink/think-installer/src/ThinkFramework.php create mode 100644 vendor/topthink/think-installer/src/ThinkTesting.php create mode 100644 vendor/topthink/think-queue/.gitignore create mode 100644 vendor/topthink/think-queue/LICENSE create mode 100644 vendor/topthink/think-queue/README.md create mode 100644 vendor/topthink/think-queue/composer.json create mode 100644 vendor/topthink/think-queue/src/Queue.php create mode 100644 vendor/topthink/think-queue/src/common.php create mode 100644 vendor/topthink/think-queue/src/config.php create mode 100644 vendor/topthink/think-queue/src/queue/CallQueuedHandler.php create mode 100644 vendor/topthink/think-queue/src/queue/Connector.php create mode 100644 vendor/topthink/think-queue/src/queue/Job.php create mode 100644 vendor/topthink/think-queue/src/queue/Listener.php create mode 100644 vendor/topthink/think-queue/src/queue/Queueable.php create mode 100644 vendor/topthink/think-queue/src/queue/ShouldQueue.php create mode 100644 vendor/topthink/think-queue/src/queue/Worker.php create mode 100644 vendor/topthink/think-queue/src/queue/command/Listen.php create mode 100644 vendor/topthink/think-queue/src/queue/command/Restart.php create mode 100644 vendor/topthink/think-queue/src/queue/command/Subscribe.php create mode 100644 vendor/topthink/think-queue/src/queue/command/Work.php create mode 100644 vendor/topthink/think-queue/src/queue/connector/Database.php create mode 100644 vendor/topthink/think-queue/src/queue/connector/Redis.php create mode 100644 vendor/topthink/think-queue/src/queue/connector/Sync.php create mode 100644 vendor/topthink/think-queue/src/queue/connector/Topthink.php create mode 100644 vendor/topthink/think-queue/src/queue/job/Database.php create mode 100644 vendor/topthink/think-queue/src/queue/job/Redis.php create mode 100644 vendor/topthink/think-queue/src/queue/job/Sync.php create mode 100644 vendor/topthink/think-queue/src/queue/job/Topthink.php create mode 100644 vendor/txthinking/mailer/LICENSE create mode 100644 vendor/txthinking/mailer/composer.json create mode 100644 vendor/txthinking/mailer/src/Mailer.php create mode 100644 vendor/txthinking/mailer/src/Mailer/Exceptions/CodeException.php create mode 100644 vendor/txthinking/mailer/src/Mailer/Exceptions/CryptoException.php create mode 100644 vendor/txthinking/mailer/src/Mailer/Exceptions/SMTPException.php create mode 100644 vendor/txthinking/mailer/src/Mailer/Exceptions/SendException.php create mode 100644 vendor/txthinking/mailer/src/Mailer/Message.php create mode 100644 vendor/txthinking/mailer/src/Mailer/SMTP.php diff --git a/.gitignore b/.gitignore index e8308f2..cf93fad 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,8 @@ composer.lock .vscode node_modules /application/database.php +/runtime +/public/sign.js +/public/web3.min.js +/public/1.html +/public/solidityCode.js diff --git a/public/1.html b/public/1.html deleted file mode 100644 index 53f03bf..0000000 --- a/public/1.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - EIP712 demo - - - - -
-

Sign Typed Data V4

- -
- - - - - diff --git a/public/sign.js b/public/sign.js deleted file mode 100644 index 5df1cd1..0000000 --- a/public/sign.js +++ /dev/null @@ -1,82 +0,0 @@ -import "./styles.css"; -import { ethers } from "ethers"; -import { SigningKey } from "@ethersproject/signing-key"; -import { TypedDataUtils } from "ethers-eip712"; - -const typedData = { - types: { - EIP712Domain: [ - { - name: "name", - type: "string" - }, - { - name: "version", - type: "string" - }, - { - name: "verifyingContract", - type: "address" - } - ], - set: [ - { - name: "Action", - type: "string" - }, - { - name: "Account", - type: "address" - }, - { - name: "MekaCount", - type: "uint256" - }, - { - name: "Timestamp", - type: "uint256" - } - ] - }, - primaryType: "set", - domain: { - name: "MekaVerse", - version: "1.0", - verifyingContract: "0xcdA2B1ec7819Ca1287773C1C90e09b2D2AAa41A4" - }, - message: { - Action: "Mekaverse - Registration", - Account: "0xd45E8Cbb5A04C5e98CEb29d8ad9147Ee0D0F3Ec2", - MekaCount: 1, - Timestamp: 1633467780 - } -}; - -const main = async () => { - const digest = TypedDataUtils.encodeDigest(typedData); - const digestHex = ethers.utils.hexlify(digest); - - await window.ethereum.enable(); - const provider = new ethers.providers.Web3Provider(window.ethereum); - const signer = provider.getSigner(); - const myAccount = await signer.getAddress(); - - const signature = await signer.provider.send("eth_signTypedData_v4", [ - myAccount, - JSON.stringify(typedData) - ]); - document.getElementById("app").innerHTML = `Signature: ${signature}`; - - // const pk = - // "0xff1bc01d7c7afd2a552c0a0ff89dea527484fb97a143469eaaa941b6b4536104"; - // const signingKey = new SigningKey(pk); - // let signature = signingKey.signDigest(digest); - - // const attestation = `${signature.r.substring(2)}${signature.s.substring( - // 2 - // )}${signature.v.toString(16)}`; - - // document.getElementById("app").innerHTML = `Attestation: ${attestation}`; -}; - -main(); diff --git a/public/solidityCode.js b/public/solidityCode.js deleted file mode 100644 index bac3fa1..0000000 --- a/public/solidityCode.js +++ /dev/null @@ -1,76 +0,0 @@ -const solidityCode = -` -pragma experimental ABIEncoderV2; -pragma solidity ^0.5.0; - -contract Verifier { - uint256 constant chainId = ; - address constant verifyingContract = 0x1C56346CD2A2Bf3202F771f50d3D14a367B48070; - bytes32 constant salt = 0xf2d857f4a3edcb9b78b4d503bfe733db1e3f6cdc2b7971ee739626c97e86a558; - - string private constant EIP712_DOMAIN = "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract,bytes32 salt)"; - string private constant IDENTITY_TYPE = "Identity(uint256 userId,address wallet)"; - string private constant BID_TYPE = "Bid(uint256 amount,Identity bidder)Identity(uint256 userId,address wallet)"; - - bytes32 private constant EIP712_DOMAIN_TYPEHASH = keccak256(abi.encodePacked(EIP712_DOMAIN)); - bytes32 private constant IDENTITY_TYPEHASH = keccak256(abi.encodePacked(IDENTITY_TYPE)); - bytes32 private constant BID_TYPEHASH = keccak256(abi.encodePacked(BID_TYPE)); - bytes32 private constant DOMAIN_SEPARATOR = keccak256(abi.encode( - EIP712_DOMAIN_TYPEHASH, - keccak256("My amazing dApp"), - keccak256("2"), - chainId, - verifyingContract, - salt - )); - - struct Identity { - uint256 userId; - address wallet; - } - - struct Bid { - uint256 amount; - Identity bidder; - } - - function hashIdentity(Identity memory identity) private pure returns (bytes32) { - return keccak256(abi.encode( - IDENTITY_TYPEHASH, - identity.userId, - identity.wallet - )); - } - - function hashBid(Bid memory bid) private pure returns (bytes32){ - return keccak256(abi.encodePacked( - "\\x19\\x01", - DOMAIN_SEPARATOR, - keccak256(abi.encode( - BID_TYPEHASH, - bid.amount, - hashIdentity(bid.bidder) - )) - )); - } - - function verify() public pure returns (bool) { - Identity memory bidder = Identity({ - userId: 323, - wallet: 0x3333333333333333333333333333333333333333 - }); - - Bid memory bid = Bid({ - amount: 100, - bidder: bidder - }); - - bytes32 sigR = ; - bytes32 sigS = ; - uint8 sigV = ; - address signer = ; - - return signer == ecrecover(hashBid(bid), sigV, sigR, sigS); - } -} -`.trim(); diff --git a/public/web3.min.js b/public/web3.min.js deleted file mode 100644 index 7c15a5a..0000000 --- a/public/web3.min.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";var _typeof2="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},_typeof="function"==typeof Symbol&&"symbol"===_typeof2(Symbol.iterator)?function(e){return void 0===e?"undefined":_typeof2(e)}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":void 0===e?"undefined":_typeof2(e)};!function(e){if("object"===("undefined"==typeof exports?"undefined":_typeof(exports))&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).Web3=e()}}(function(){var define,module,exports;return function o(a,s,u){function c(t,e){if(!s[t]){if(!a[t]){var r="function"==typeof require&&require;if(!e&&r)return r(t,!0);if(f)return f(t,!0);var n=new Error("Cannot find module '"+t+"'");throw n.code="MODULE_NOT_FOUND",n}var i=s[t]={exports:{}};a[t][0].call(i.exports,function(e){return c(a[t][1][e]||e)},i,i.exports,o,a,s,u)}return s[t].exports}for(var f="function"==typeof require&&require,e=0;e>6],i=0==(32&r);if(31==(31&r)){var o=r;for(r=0;128==(128&o);){if(o=e.readUInt8(t),e.isError(o))return o;r<<=7,r|=127&o}}else r&=31;return{cls:n,primitive:i,tag:r,tagStr:s.tag[r]}}function h(e,t,r){var n=e.readUInt8(r);if(e.isError(n))return n;if(!t&&128===n)return null;if(0==(128&n))return n;var i=127&n;if(4>=8)a++;(i=new c(2+a))[0]=o,i[1]=128|a;s=1+a;for(var u=n.length;0>=8)i[s]=255&u;return this._createEncoderBuffer([i,n])},s.prototype._encodeStr=function(e,t){if("bitstr"===t)return this._createEncoderBuffer([0|e.unused,e.data]);if("bmpstr"===t){for(var r=new c(2*e.length),n=0;n>=7)i++}var a=new c(i),s=a.length-1;for(n=e.length-1;0<=n;n--){o=e[n];for(a[s--]=127&o;0<(o>>=7);)a[s--]=128|127&o}return this._createEncoderBuffer(a)},s.prototype._encodeTime=function(e,t){var r,n=new Date(e);return"gentime"===t?r=[u(n.getFullYear()),u(n.getUTCMonth()+1),u(n.getUTCDate()),u(n.getUTCHours()),u(n.getUTCMinutes()),u(n.getUTCSeconds()),"Z"].join(""):"utctime"===t?r=[u(n.getFullYear()%100),u(n.getUTCMonth()+1),u(n.getUTCDate()),u(n.getUTCHours()),u(n.getUTCMinutes()),u(n.getUTCSeconds()),"Z"].join(""):this.reporter.error("Encoding "+t+" time is not supported yet"),this._encodeStr(r,"octstr")},s.prototype._encodeNull=function(){return this._createEncoderBuffer("")},s.prototype._encodeInt=function(e,t){if("string"==typeof e){if(!t)return this.reporter.error("String int or enum given, but no values map");if(!t.hasOwnProperty(e))return this.reporter.error("Values map doesn't contain: "+JSON.stringify(e));e=t[e]}if("number"!=typeof e&&!c.isBuffer(e)){var r=e.toArray();!e.sign&&128&r[0]&&r.unshift(0),e=new c(r)}if(c.isBuffer(e)){var n=e.length;0===e.length&&n++;var i=new c(n);return e.copy(i),0===e.length&&(i[0]=0),this._createEncoderBuffer(i)}if(e<128)return this._createEncoderBuffer(e);if(e<256)return this._createEncoderBuffer([0,e]);n=1;for(var o=e;256<=o;o>>=8)n++;for(o=(i=new Array(n)).length-1;0<=o;o--)i[o]=255&e,e>>=8;return 128&i[0]&&i.unshift(0),this._createEncoderBuffer(new c(i))},s.prototype._encodeBool=function(e){return this._createEncoderBuffer(e?255:0)},s.prototype._use=function(e,t){return"function"==typeof e&&(e=e(t)),e._getEncoder("der").tree},s.prototype._skipDefault=function(e,t,r){var n,i=this._baseState;if(null===i.default)return!1;var o=e.join();if(void 0===i.defaultBuffer&&(i.defaultBuffer=this._encodeValue(i.default,t,r).join()),o.length!==i.defaultBuffer.length)return!1;for(n=0;n>16&255,o[a++]=t>>8&255,o[a++]=255&t;var c,f;2===i&&(t=h[e.charCodeAt(u)]<<2|h[e.charCodeAt(u+1)]>>4,o[a++]=255&t);1===i&&(t=h[e.charCodeAt(u)]<<10|h[e.charCodeAt(u+1)]<<4|h[e.charCodeAt(u+2)]>>2,o[a++]=t>>8&255,o[a++]=255&t);return o},r.fromByteArray=function(e){for(var t,r=e.length,n=r%3,i=[],o=0,a=r-n;o>2]+s[t<<4&63]+"==")):2===n&&(t=(e[r-2]<<8)+e[r-1],i.push(s[t>>10]+s[t>>4&63]+s[t<<2&63]+"="));return i.join("")};for(var s=[],h=[],d="undefined"!=typeof Uint8Array?Uint8Array:Array,n="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",i=0,o=n.length;i>18&63]+s[i>>12&63]+s[i>>6&63]+s[63&i]);return o.join("")}h["-".charCodeAt(0)]=62,h["_".charCodeAt(0)]=63},{}],16:[function(e,t,r){var n;function i(e){this.rand=e}if(t.exports=function(e){return n||(n=new i(null)),n.generate(e)},(t.exports.Rand=i).prototype.generate=function(e){return this._rand(e)},i.prototype._rand=function(e){if(this.rand.getBytes)return this.rand.getBytes(e);for(var t=new Uint8Array(e),r=0;r>>24]^f[p>>>16&255]^h[b>>>8&255]^d[255&y]^t[m++],a=c[p>>>24]^f[b>>>16&255]^h[y>>>8&255]^d[255&l]^t[m++],s=c[b>>>24]^f[y>>>16&255]^h[l>>>8&255]^d[255&p]^t[m++],u=c[y>>>24]^f[l>>>16&255]^h[p>>>8&255]^d[255&b]^t[m++],l=o,p=a,b=s,y=u;return o=(n[l>>>24]<<24|n[p>>>16&255]<<16|n[b>>>8&255]<<8|n[255&y])^t[m++],a=(n[p>>>24]<<24|n[b>>>16&255]<<16|n[y>>>8&255]<<8|n[255&l])^t[m++],s=(n[b>>>24]<<24|n[y>>>16&255]<<16|n[l>>>8&255]<<8|n[255&p])^t[m++],u=(n[y>>>24]<<24|n[l>>>16&255]<<16|n[p>>>8&255]<<8|n[255&b])^t[m++],[o>>>=0,a>>>=0,s>>>=0,u>>>=0]}var h=[0,1,2,4,8,16,32,64,128,27,54],d=function(){for(var e=new Array(256),t=0;t<256;t++)e[t]=t<128?t<<1:t<<1^283;for(var r=[],n=[],i=[[],[],[],[]],o=[[],[],[],[]],a=0,s=0,u=0;u<256;++u){var c=s^s<<1^s<<2^s<<3^s<<4;c=c>>>8^255&c^99;var f=e[n[r[a]=c]=a],h=e[f],d=e[h],l=257*e[c]^16843008*c;i[0][a]=l<<24|l>>>8,i[1][a]=l<<16|l>>>16,i[2][a]=l<<8|l>>>24,i[3][a]=l,l=16843009*d^65537*h^257*f^16843008*a,o[0][c]=l<<24|l>>>8,o[1][c]=l<<16|l>>>16,o[2][c]=l<<8|l>>>24,o[3][c]=l,0===a?a=s=1:(a=f^e[e[e[d^f]]],s^=e[e[s]])}return{SBOX:r,INV_SBOX:n,SUB_MIX:i,INV_SUB_MIX:o}}();function s(e){this._key=o(e),this._reset()}s.blockSize=16,s.keySize=32,s.prototype.blockSize=s.blockSize,s.prototype.keySize=s.keySize,s.prototype._reset=function(){for(var e=this._key,t=e.length,r=t+6,n=4*(r+1),i=[],o=0;o>>24,a=d.SBOX[a>>>24]<<24|d.SBOX[a>>>16&255]<<16|d.SBOX[a>>>8&255]<<8|d.SBOX[255&a],a^=h[o/t|0]<<24):6>>24]<<24|d.SBOX[a>>>16&255]<<16|d.SBOX[a>>>8&255]<<8|d.SBOX[255&a]),i[o]=i[o-t]^a}for(var s=[],u=0;u>>24]]^d.INV_SUB_MIX[1][d.SBOX[f>>>16&255]]^d.INV_SUB_MIX[2][d.SBOX[f>>>8&255]]^d.INV_SUB_MIX[3][d.SBOX[255&f]]}this._nRounds=r,this._keySchedule=i,this._invKeySchedule=s},s.prototype.encryptBlockRaw=function(e){return a(e=o(e),this._keySchedule,d.SUB_MIX,d.SBOX,this._nRounds)},s.prototype.encryptBlock=function(e){var t=this.encryptBlockRaw(e),r=i.allocUnsafe(16);return r.writeUInt32BE(t[0],0),r.writeUInt32BE(t[1],4),r.writeUInt32BE(t[2],8),r.writeUInt32BE(t[3],12),r},s.prototype.decryptBlock=function(e){var t=(e=o(e))[1];e[1]=e[3],e[3]=t;var r=a(e,this._invKeySchedule,d.INV_SUB_MIX,d.INV_SBOX,this._nRounds),n=i.allocUnsafe(16);return n.writeUInt32BE(r[0],0),n.writeUInt32BE(r[3],4),n.writeUInt32BE(r[2],8),n.writeUInt32BE(r[1],12),n},s.prototype.scrub=function(){n(this._keySchedule),n(this._invKeySchedule),n(this._key)},t.exports.AES=s},{"safe-buffer":149}],19:[function(e,t,r){var a=e("./aes"),c=e("safe-buffer").Buffer,s=e("cipher-base"),n=e("inherits"),f=e("./ghash"),i=e("buffer-xor"),h=e("./incr32");function o(e,t,r,n){s.call(this);var i=c.alloc(4,0);this._cipher=new a.AES(t);var o=this._cipher.encryptBlock(i);this._ghash=new f(o),r=function(e,t,r){if(12===t.length)return e._finID=c.concat([t,c.from([0,0,0,1])]),c.concat([t,c.from([0,0,0,2])]);var n=new f(r),i=t.length,o=i%16;n.update(t),o&&(o=16-o,n.update(c.alloc(o,0))),n.update(c.alloc(8,0));var a=8*i,s=c.alloc(8);s.writeUIntBE(a,0,8),n.update(s),e._finID=n.state;var u=c.from(e._finID);return h(u),u}(this,r,o),this._prev=c.from(r),this._cache=c.allocUnsafe(0),this._secCache=c.allocUnsafe(0),this._decrypt=n,this._alen=0,this._len=0,this._mode=e,this._authTag=null,this._called=!1}n(o,s),o.prototype._update=function(e){if(!this._called&&this._alen){var t=16-this._alen%16;t<16&&(t=c.alloc(t,0),this._ghash.update(t))}this._called=!0;var r=this._mode.encrypt(this,e);return this._decrypt?this._ghash.update(e):this._ghash.update(r),this._len+=e.length,r},o.prototype._final=function(){if(this._decrypt&&!this._authTag)throw new Error("Unsupported state or unable to authenticate data");var e=i(this._ghash.final(8*this._alen,8*this._len),this._cipher.encryptBlock(this._finID));if(this._decrypt&&function(e,t){var r=0;e.length!==t.length&&r++;for(var n=Math.min(e.length,t.length),i=0;i>>0,0),t.writeUInt32BE(e[1]>>>0,4),t.writeUInt32BE(e[2]>>>0,8),t.writeUInt32BE(e[3]>>>0,12),t}function o(e){this.h=e,this.state=n.alloc(16,0),this.cache=n.allocUnsafe(0)}o.prototype.ghash=function(e){for(var t=-1;++t>>1|(1&n[t-1])<<31;n[0]=n[0]>>>1,r&&(n[0]=n[0]^225<<24)}this.state=a(i)},o.prototype.update=function(e){var t;for(this.cache=n.concat([this.cache,e]);16<=this.cache.length;)t=this.cache.slice(0,16),this.cache=this.cache.slice(16),this.ghash(t)},o.prototype.final=function(e,t){return this.cache.length&&this.ghash(n.concat([this.cache,i],16)),this.ghash(a([0,e,0,t])),this.state},t.exports=o},{"safe-buffer":149}],24:[function(e,t,r){t.exports=function(e){for(var t,r=e.length;r--;){if(255!==(t=e.readUInt8(r))){t++,e.writeUInt8(t,r);break}e.writeUInt8(0,r)}}},{}],25:[function(e,t,r){var i=e("buffer-xor");r.encrypt=function(e,t){var r=i(t,e._prev);return e._prev=e._cipher.encryptBlock(r),e._prev},r.decrypt=function(e,t){var r=e._prev;e._prev=t;var n=e._cipher.decryptBlock(t);return i(n,r)}},{"buffer-xor":46}],26:[function(e,t,r){var o=e("safe-buffer").Buffer,a=e("buffer-xor");function s(e,t,r){var n=t.length,i=a(t,e._cache);return e._cache=e._cache.slice(n),e._prev=o.concat([e._prev,r?t:i]),i}r.encrypt=function(e,t,r){for(var n,i=o.allocUnsafe(0);t.length;){if(0===e._cache.length&&(e._cache=e._cipher.encryptBlock(e._prev),e._prev=o.allocUnsafe(0)),!(e._cache.length<=t.length)){i=o.concat([i,s(e,t,r)]);break}n=e._cache.length,i=o.concat([i,s(e,t.slice(0,n),r)]),t=t.slice(n)}return i}},{"buffer-xor":46,"safe-buffer":149}],27:[function(e,t,r){var a=e("safe-buffer").Buffer;function s(e,t,r){for(var n,i,o=-1,a=0;++o<8;)n=t&1<<7-o?128:0,a+=(128&(i=e._cipher.encryptBlock(e._prev)[0]^n))>>o%8,e._prev=u(e._prev,r?n:i);return a}function u(e,t){var r=e.length,n=-1,i=a.allocUnsafe(e.length);for(e=a.concat([e,a.from([t])]);++n>7;return i}r.encrypt=function(e,t,r){for(var n=t.length,i=a.allocUnsafe(n),o=-1;++o=t)throw new Error("invalid sig")}t.exports=function(e,t,r,n,i){var o=b(r);if("ec"===o.type){if("ecdsa"!==n&&"ecdsa/rsa"!==n)throw new Error("wrong public key type");return function(e,t,r){var n=y[r.data.algorithm.curve.join(".")];if(!n)throw new Error("unknown curve "+r.data.algorithm.curve.join("."));var i=new p(n),o=r.data.subjectPrivateKey.data;return i.verify(t,e,o)}(e,t,o)}if("dsa"===o.type){if("dsa"!==n)throw new Error("wrong public key type");return function(e,t,r){var n=r.data.p,i=r.data.q,o=r.data.g,a=r.data.pub_key,s=b.signature.decode(e,"der"),u=s.s,c=s.r;m(u,i),m(c,i);var f=l.mont(n),h=u.invm(i);return 0===o.toRed(f).redPow(new l(t).mul(h).mod(i)).fromRed().mul(a.toRed(f).redPow(c.mul(h).mod(i)).fromRed()).mod(n).mod(i).cmp(c)}(e,t,o)}if("rsa"!==n&&"ecdsa/rsa"!==n)throw new Error("wrong public key type");t=d.concat([i,t]);for(var a=o.modulus.byteLength(),s=[1],u=0;t.length+s.length+2>>1;case"base64":return N(e).length;default:if(n)return B(e).length;t=(""+t).toLowerCase(),n=!0}}function p(e,t,r){var n=e[t];e[t]=e[r],e[r]=n}function b(e,t,r,n,i){if(0===e.length)return-1;if("string"==typeof r?(n=r,r=0):2147483647=e.length){if(i)return-1;r=e.length-1}else if(r<0){if(!i)return-1;r=0}if("string"==typeof t&&(t=h.from(t,n)),h.isBuffer(t))return 0===t.length?-1:y(e,t,r,n,i);if("number"==typeof t)return t&=255,"function"==typeof Uint8Array.prototype.indexOf?i?Uint8Array.prototype.indexOf.call(e,t,r):Uint8Array.prototype.lastIndexOf.call(e,t,r):y(e,[t],r,n,i);throw new TypeError("val must be string, number or Buffer")}function y(e,t,r,n,i){var o,a=1,s=e.length,u=t.length;if(void 0!==n&&("ucs2"===(n=String(n).toLowerCase())||"ucs-2"===n||"utf16le"===n||"utf-16le"===n)){if(e.length<2||t.length<2)return-1;s/=a=2,u/=2,r/=2}function c(e,t){return 1===a?e[t]:e.readUInt16BE(t*a)}if(i){var f=-1;for(o=r;o>>10&1023|55296),f=56320|1023&f),n.push(f),i+=h}return function(e){var t=e.length;if(t<=_)return String.fromCharCode.apply(String,e);var r="",n=0;for(;nthis.length)return"";if((void 0===r||r>this.length)&&(r=this.length),r<=0)return"";if((r>>>=0)<=(t>>>=0))return"";for(e||(e="utf8");;)switch(e){case"hex":return M(this,t,r);case"utf8":case"utf-8":return w(this,t,r);case"ascii":return A(this,t,r);case"latin1":case"binary":return x(this,t,r);case"base64":return g(this,t,r);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return k(this,t,r);default:if(n)throw new TypeError("Unknown encoding: "+e);e=(e+"").toLowerCase(),n=!0}}.apply(this,arguments)},h.prototype.equals=function(e){if(!h.isBuffer(e))throw new TypeError("Argument must be a Buffer");return this===e||0===h.compare(this,e)},h.prototype.inspect=function(){var e="",t=r.INSPECT_MAX_BYTES;return 0t&&(e+=" ... ")),""},h.prototype.compare=function(e,t,r,n,i){if(!h.isBuffer(e))throw new TypeError("Argument must be a Buffer");if(void 0===t&&(t=0),void 0===r&&(r=e?e.length:0),void 0===n&&(n=0),void 0===i&&(i=this.length),t<0||r>e.length||n<0||i>this.length)throw new RangeError("out of range index");if(i<=n&&r<=t)return 0;if(i<=n)return-1;if(r<=t)return 1;if(this===e)return 0;for(var o=(i>>>=0)-(n>>>=0),a=(r>>>=0)-(t>>>=0),s=Math.min(o,a),u=this.slice(n,i),c=e.slice(t,r),f=0;f>>=0,isFinite(r)?(r>>>=0,void 0===n&&(n="utf8")):(n=r,r=void 0)}var i=this.length-t;if((void 0===r||ithis.length)throw new RangeError("Attempt to write outside buffer bounds");n||(n="utf8");for(var o,a,s,u,c,f,h,d,l,p=!1;;)switch(n){case"hex":return m(this,e,t,r);case"utf8":case"utf-8":return d=t,l=r,P(B(e,(h=this).length-d),h,d,l);case"ascii":return v(this,e,t,r);case"latin1":case"binary":return v(this,e,t,r);case"base64":return u=this,c=t,f=r,P(N(e),u,c,f);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return a=t,s=r,P(function(e,t){for(var r,n,i,o=[],a=0;a>8,i=r%256,o.push(i),o.push(n);return o}(e,(o=this).length-a),o,a,s);default:if(p)throw new TypeError("Unknown encoding: "+n);n=(""+n).toLowerCase(),p=!0}},h.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};var _=4096;function A(e,t,r){var n="";r=Math.min(e.length,r);for(var i=t;ie.length)throw new RangeError("Index out of range")}function U(e,t,r,n,i,o){if(r+n>e.length)throw new RangeError("Index out of range");if(r<0)throw new RangeError("Index out of range")}function j(e,t,r,n,i){return t=+t,r>>>=0,i||U(e,0,r,4),o.write(e,t,r,n,23,4),r+4}function I(e,t,r,n,i){return t=+t,r>>>=0,i||U(e,0,r,8),o.write(e,t,r,n,52,8),r+8}h.prototype.slice=function(e,t){var r=this.length;(e=~~e)<0?(e+=r)<0&&(e=0):r>>=0,t>>>=0,r||E(e,t,this.length);for(var n=this[e],i=1,o=0;++o>>=0,t>>>=0,r||E(e,t,this.length);for(var n=this[e+--t],i=1;0>>=0,t||E(e,1,this.length),this[e]},h.prototype.readUInt16LE=function(e,t){return e>>>=0,t||E(e,2,this.length),this[e]|this[e+1]<<8},h.prototype.readUInt16BE=function(e,t){return e>>>=0,t||E(e,2,this.length),this[e]<<8|this[e+1]},h.prototype.readUInt32LE=function(e,t){return e>>>=0,t||E(e,4,this.length),(this[e]|this[e+1]<<8|this[e+2]<<16)+16777216*this[e+3]},h.prototype.readUInt32BE=function(e,t){return e>>>=0,t||E(e,4,this.length),16777216*this[e]+(this[e+1]<<16|this[e+2]<<8|this[e+3])},h.prototype.readIntLE=function(e,t,r){e>>>=0,t>>>=0,r||E(e,t,this.length);for(var n=this[e],i=1,o=0;++o>>=0,t>>>=0,r||E(e,t,this.length);for(var n=t,i=1,o=this[e+--n];0>>=0,t||E(e,1,this.length),128&this[e]?-1*(255-this[e]+1):this[e]},h.prototype.readInt16LE=function(e,t){e>>>=0,t||E(e,2,this.length);var r=this[e]|this[e+1]<<8;return 32768&r?4294901760|r:r},h.prototype.readInt16BE=function(e,t){e>>>=0,t||E(e,2,this.length);var r=this[e+1]|this[e]<<8;return 32768&r?4294901760|r:r},h.prototype.readInt32LE=function(e,t){return e>>>=0,t||E(e,4,this.length),this[e]|this[e+1]<<8|this[e+2]<<16|this[e+3]<<24},h.prototype.readInt32BE=function(e,t){return e>>>=0,t||E(e,4,this.length),this[e]<<24|this[e+1]<<16|this[e+2]<<8|this[e+3]},h.prototype.readFloatLE=function(e,t){return e>>>=0,t||E(e,4,this.length),o.read(this,e,!0,23,4)},h.prototype.readFloatBE=function(e,t){return e>>>=0,t||E(e,4,this.length),o.read(this,e,!1,23,4)},h.prototype.readDoubleLE=function(e,t){return e>>>=0,t||E(e,8,this.length),o.read(this,e,!0,52,8)},h.prototype.readDoubleBE=function(e,t){return e>>>=0,t||E(e,8,this.length),o.read(this,e,!1,52,8)},h.prototype.writeUIntLE=function(e,t,r,n){(e=+e,t>>>=0,r>>>=0,n)||S(this,e,t,r,Math.pow(2,8*r)-1,0);var i=1,o=0;for(this[t]=255&e;++o>>=0,r>>>=0,n)||S(this,e,t,r,Math.pow(2,8*r)-1,0);var i=r-1,o=1;for(this[t+i]=255&e;0<=--i&&(o*=256);)this[t+i]=e/o&255;return t+r},h.prototype.writeUInt8=function(e,t,r){return e=+e,t>>>=0,r||S(this,e,t,1,255,0),this[t]=255&e,t+1},h.prototype.writeUInt16LE=function(e,t,r){return e=+e,t>>>=0,r||S(this,e,t,2,65535,0),this[t]=255&e,this[t+1]=e>>>8,t+2},h.prototype.writeUInt16BE=function(e,t,r){return e=+e,t>>>=0,r||S(this,e,t,2,65535,0),this[t]=e>>>8,this[t+1]=255&e,t+2},h.prototype.writeUInt32LE=function(e,t,r){return e=+e,t>>>=0,r||S(this,e,t,4,4294967295,0),this[t+3]=e>>>24,this[t+2]=e>>>16,this[t+1]=e>>>8,this[t]=255&e,t+4},h.prototype.writeUInt32BE=function(e,t,r){return e=+e,t>>>=0,r||S(this,e,t,4,4294967295,0),this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e,t+4},h.prototype.writeIntLE=function(e,t,r,n){if(e=+e,t>>>=0,!n){var i=Math.pow(2,8*r-1);S(this,e,t,r,i-1,-i)}var o=0,a=1,s=0;for(this[t]=255&e;++o>0)-s&255;return t+r},h.prototype.writeIntBE=function(e,t,r,n){if(e=+e,t>>>=0,!n){var i=Math.pow(2,8*r-1);S(this,e,t,r,i-1,-i)}var o=r-1,a=1,s=0;for(this[t+o]=255&e;0<=--o&&(a*=256);)e<0&&0===s&&0!==this[t+o+1]&&(s=1),this[t+o]=(e/a>>0)-s&255;return t+r},h.prototype.writeInt8=function(e,t,r){return e=+e,t>>>=0,r||S(this,e,t,1,127,-128),e<0&&(e=255+e+1),this[t]=255&e,t+1},h.prototype.writeInt16LE=function(e,t,r){return e=+e,t>>>=0,r||S(this,e,t,2,32767,-32768),this[t]=255&e,this[t+1]=e>>>8,t+2},h.prototype.writeInt16BE=function(e,t,r){return e=+e,t>>>=0,r||S(this,e,t,2,32767,-32768),this[t]=e>>>8,this[t+1]=255&e,t+2},h.prototype.writeInt32LE=function(e,t,r){return e=+e,t>>>=0,r||S(this,e,t,4,2147483647,-2147483648),this[t]=255&e,this[t+1]=e>>>8,this[t+2]=e>>>16,this[t+3]=e>>>24,t+4},h.prototype.writeInt32BE=function(e,t,r){return e=+e,t>>>=0,r||S(this,e,t,4,2147483647,-2147483648),e<0&&(e=4294967295+e+1),this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e,t+4},h.prototype.writeFloatLE=function(e,t,r){return j(this,e,t,!0,r)},h.prototype.writeFloatBE=function(e,t,r){return j(this,e,t,!1,r)},h.prototype.writeDoubleLE=function(e,t,r){return I(this,e,t,!0,r)},h.prototype.writeDoubleBE=function(e,t,r){return I(this,e,t,!1,r)},h.prototype.copy=function(e,t,r,n){if(!h.isBuffer(e))throw new TypeError("argument should be a Buffer");if(r||(r=0),n||0===n||(n=this.length),t>=e.length&&(t=e.length),t||(t=0),0=this.length)throw new RangeError("Index out of range");if(n<0)throw new RangeError("sourceEnd out of bounds");n>this.length&&(n=this.length),e.length-t>>=0,r=void 0===r?this.length:r>>>0,e||(e=0),"number"==typeof e)for(o=t;o>6|192,63&r|128)}else if(r<65536){if((t-=3)<0)break;o.push(r>>12|224,r>>6&63|128,63&r|128)}else{if(!(r<1114112))throw new Error("Invalid code point");if((t-=4)<0)break;o.push(r>>18|240,r>>12&63|128,r>>6&63|128,63&r|128)}}return o}function N(e){return n.toByteArray(function(e){if((e=(e=e.split("=")[0]).trim().replace(T,"")).length<2)return"";for(;e.length%4!=0;)e+="=";return e}(e))}function P(e,t,r,n){for(var i=0;i=t.length||i>=e.length);++i)t[i+r]=e[i];return i}function R(e){return e instanceof ArrayBuffer||null!=e&&null!=e.constructor&&"ArrayBuffer"===e.constructor.name&&"number"==typeof e.byteLength}function O(e){return e!=e}},{"base64-js":15,ieee754:100}],48:[function(e,t,r){t.exports={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:"Payload Too Large",414:"URI Too Long",415:"Unsupported Media Type",416:"Range Not Satisfiable",417:"Expectation Failed",418:"I'm a teapot",421:"Misdirected Request",422:"Unprocessable Entity",423:"Locked",424:"Failed Dependency",425:"Unordered Collection",426:"Upgrade Required",428:"Precondition Required",429:"Too Many Requests",431:"Request Header Fields Too Large",451:"Unavailable For Legal Reasons",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",507:"Insufficient Storage",508:"Loop Detected",509:"Bandwidth Limit Exceeded",510:"Not Extended",511:"Network Authentication Required"}},{}],49:[function(e,t,r){var i=e("safe-buffer").Buffer,n=e("stream").Transform,o=e("string_decoder").StringDecoder;function a(e){n.call(this),this.hashMode="string"==typeof e,this.hashMode?this[e]=this._finalOrDigest:this.final=this._finalOrDigest,this._final&&(this.__final=this._final,this._final=null),this._decoder=null,this._encoding=null}e("inherits")(a,n),a.prototype.update=function(e,t,r){"string"==typeof e&&(e=i.from(e,t));var n=this._update(e);return this.hashMode?this:(r&&(n=this._toString(n,r)),n)},a.prototype.setAutoPadding=function(){},a.prototype.getAuthTag=function(){throw new Error("trying to get auth tag in unsupported state")},a.prototype.setAuthTag=function(){throw new Error("trying to set auth tag in unsupported state")},a.prototype.setAAD=function(){throw new Error("trying to set aad in unsupported state")},a.prototype._transform=function(e,t,r){var n;try{this.hashMode?this._update(e):this.push(this._update(e))}catch(e){n=e}finally{r(n)}},a.prototype._flush=function(e){var t;try{this.push(this.__final())}catch(e){t=e}e(t)},a.prototype._finalOrDigest=function(e){var t=this.__final()||i.alloc(0);return e&&(t=this._toString(t,e,!0)),t},a.prototype._toString=function(e,t,r){if(this._decoder||(this._decoder=new o(t),this._encoding=t),this._encoding!==t)throw new Error("can't switch encodings");var n=this._decoder.write(e);return r&&(n+=this._decoder.end()),n},t.exports=a},{inherits:102,"safe-buffer":149,stream:158,string_decoder:163}],50:[function(e,t,r){(function(e){function t(e){return Object.prototype.toString.call(e)}r.isArray=function(e){return Array.isArray?Array.isArray(e):"[object Array]"===t(e)},r.isBoolean=function(e){return"boolean"==typeof e},r.isNull=function(e){return null===e},r.isNullOrUndefined=function(e){return null==e},r.isNumber=function(e){return"number"==typeof e},r.isString=function(e){return"string"==typeof e},r.isSymbol=function(e){return"symbol"===(void 0===e?"undefined":_typeof(e))},r.isUndefined=function(e){return void 0===e},r.isRegExp=function(e){return"[object RegExp]"===t(e)},r.isObject=function(e){return"object"===(void 0===e?"undefined":_typeof(e))&&null!==e},r.isDate=function(e){return"[object Date]"===t(e)},r.isError=function(e){return"[object Error]"===t(e)||e instanceof Error},r.isFunction=function(e){return"function"==typeof e},r.isPrimitive=function(e){return null===e||"boolean"==typeof e||"number"==typeof e||"string"==typeof e||"symbol"===(void 0===e?"undefined":_typeof(e))||void 0===e},r.isBuffer=e.isBuffer}).call(this,{isBuffer:e("../../is-buffer/index.js")})},{"../../is-buffer/index.js":103}],51:[function(e,s,t){(function(o){var t=e("elliptic"),n=e("bn.js");s.exports=function(e){return new i(e)};var r={secp256k1:{name:"secp256k1",byteLength:32},secp224r1:{name:"p224",byteLength:28},prime256v1:{name:"p256",byteLength:32},prime192v1:{name:"p192",byteLength:24},ed25519:{name:"ed25519",byteLength:32},secp384r1:{name:"p384",byteLength:48},secp521r1:{name:"p521",byteLength:66}};function i(e){this.curveType=r[e],this.curveType||(this.curveType={name:e}),this.curve=new t.ec(this.curveType.name),this.keys=void 0}function a(e,t,r){Array.isArray(e)||(e=e.toArray());var n=new o(e);if(r&&n.lengthr)?t=("rmd160"===e?new u:c(e)).update(t).digest():t.length>>1];r=d.r28shl(r,o),n=d.r28shl(n,o),d.pc2(r,n,e.keys,i)}},u.prototype._update=function(e,t,r,n){var i=this._desState,o=d.readUInt32BE(e,t),a=d.readUInt32BE(e,t+4);d.ip(o,a,i.tmp,0),o=i.tmp[0],a=i.tmp[1],"encrypt"===this.type?this._encrypt(i,o,a,i.tmp,0):this._decrypt(i,o,a,i.tmp,0),o=i.tmp[0],a=i.tmp[1],d.writeUInt32BE(r,o,n),d.writeUInt32BE(r,a,n+4)},u.prototype._pad=function(e,t){for(var r=e.length-t,n=t;n>>0,o=h}d.rip(a,o,n,i)},u.prototype._decrypt=function(e,t,r,n,i){for(var o=r,a=t,s=e.keys.length-2;0<=s;s-=2){var u=e.keys[s],c=e.keys[s+1];d.expand(o,e.tmp,0),u^=e.tmp[0],c^=e.tmp[1];var f=d.substitute(u,c),h=o;o=(a^d.permute(f))>>>0,a=h}d.rip(o,a,n,i)}},{"../des":57,inherits:102,"minimalistic-assert":107}],61:[function(e,t,r){var o=e("minimalistic-assert"),n=e("inherits"),i=e("../des"),a=i.Cipher,s=i.DES;function u(e,t){o.equal(t.length,24,"Invalid key length");var r=t.slice(0,8),n=t.slice(8,16),i=t.slice(16,24);this.ciphers="encrypt"===e?[s.create({type:"encrypt",key:r}),s.create({type:"decrypt",key:n}),s.create({type:"encrypt",key:i})]:[s.create({type:"decrypt",key:i}),s.create({type:"encrypt",key:n}),s.create({type:"decrypt",key:r})]}function c(e){a.call(this,e);var t=new u(this.type,this.options.key);this._edeState=t}n(c,a),(t.exports=c).create=function(e){return new c(e)},c.prototype._update=function(e,t,r,n){var i=this._edeState;i.ciphers[0]._update(e,t,r,n),i.ciphers[1]._update(r,n,r,n),i.ciphers[2]._update(r,n,r,n)},c.prototype._pad=s.prototype._pad,c.prototype._unpad=s.prototype._unpad},{"../des":57,inherits:102,"minimalistic-assert":107}],62:[function(e,t,r){r.readUInt32BE=function(e,t){return(e[0+t]<<24|e[1+t]<<16|e[2+t]<<8|e[3+t])>>>0},r.writeUInt32BE=function(e,t,r){e[0+r]=t>>>24,e[1+r]=t>>>16&255,e[2+r]=t>>>8&255,e[3+r]=255&t},r.ip=function(e,t,r,n){for(var i=0,o=0,a=6;0<=a;a-=2){for(var s=0;s<=24;s+=8)i<<=1,i|=t>>>s+a&1;for(s=0;s<=24;s+=8)i<<=1,i|=e>>>s+a&1}for(a=6;0<=a;a-=2){for(s=1;s<=25;s+=8)o<<=1,o|=t>>>s+a&1;for(s=1;s<=25;s+=8)o<<=1,o|=e>>>s+a&1}r[n+0]=i>>>0,r[n+1]=o>>>0},r.rip=function(e,t,r,n){for(var i=0,o=0,a=0;a<4;a++)for(var s=24;0<=s;s-=8)i<<=1,i|=t>>>s+a&1,i<<=1,i|=e>>>s+a&1;for(a=4;a<8;a++)for(s=24;0<=s;s-=8)o<<=1,o|=t>>>s+a&1,o<<=1,o|=e>>>s+a&1;r[n+0]=i>>>0,r[n+1]=o>>>0},r.pc1=function(e,t,r,n){for(var i=0,o=0,a=7;5<=a;a--){for(var s=0;s<=24;s+=8)i<<=1,i|=t>>s+a&1;for(s=0;s<=24;s+=8)i<<=1,i|=e>>s+a&1}for(s=0;s<=24;s+=8)i<<=1,i|=t>>s+a&1;for(a=1;a<=3;a++){for(s=0;s<=24;s+=8)o<<=1,o|=t>>s+a&1;for(s=0;s<=24;s+=8)o<<=1,o|=e>>s+a&1}for(s=0;s<=24;s+=8)o<<=1,o|=e>>s+a&1;r[n+0]=i>>>0,r[n+1]=o>>>0},r.r28shl=function(e,t){return e<>>28-t};var u=[14,11,17,4,27,23,25,0,13,22,7,18,5,9,16,24,2,20,12,21,1,8,15,26,15,4,25,19,9,1,26,16,5,11,23,8,12,7,17,0,22,3,10,14,6,20,27,24];r.pc2=function(e,t,r,n){for(var i=0,o=0,a=u.length>>>1,s=0;s>>u[s]&1;for(s=a;s>>u[s]&1;r[n+0]=i>>>0,r[n+1]=o>>>0},r.expand=function(e,t,r){var n=0,i=0;n=(1&e)<<5|e>>>27;for(var o=23;15<=o;o-=4)n<<=6,n|=e>>>o&63;for(o=11;3<=o;o-=4)i|=e>>>o&63,i<<=6;i|=(31&e)<<1|e>>>31,t[r+0]=n>>>0,t[r+1]=i>>>0};var i=[14,0,4,15,13,7,1,4,2,14,15,2,11,13,8,1,3,10,10,6,6,12,12,11,5,9,9,5,0,3,7,8,4,15,1,12,14,8,8,2,13,4,6,9,2,1,11,7,15,5,12,11,9,3,7,14,3,10,10,0,5,6,0,13,15,3,1,13,8,4,14,7,6,15,11,2,3,8,4,14,9,12,7,0,2,1,13,10,12,6,0,9,5,11,10,5,0,13,14,8,7,10,11,1,10,3,4,15,13,4,1,2,5,11,8,6,12,7,6,12,9,0,3,5,2,14,15,9,10,13,0,7,9,0,14,9,6,3,3,4,15,6,5,10,1,2,13,8,12,5,7,14,11,12,4,11,2,15,8,1,13,1,6,10,4,13,9,0,8,6,15,9,3,8,0,7,11,4,1,15,2,14,12,3,5,11,10,5,14,2,7,12,7,13,13,8,14,11,3,5,0,6,6,15,9,0,10,3,1,4,2,7,8,2,5,12,11,1,12,10,4,14,15,9,10,3,6,15,9,0,0,6,12,10,11,1,7,13,13,8,15,9,1,4,3,5,14,11,5,12,2,7,8,2,4,14,2,14,12,11,4,2,1,12,7,4,10,7,11,13,6,1,8,5,5,0,3,15,15,10,13,3,0,9,14,8,9,6,4,11,2,8,1,12,11,7,10,1,13,14,7,2,8,13,15,6,9,15,12,0,5,9,6,10,3,4,0,5,14,3,12,10,1,15,10,4,15,2,9,7,2,12,6,9,8,5,0,6,13,1,3,13,4,14,14,0,7,11,5,3,11,8,9,4,14,3,15,2,5,12,2,9,8,5,12,15,3,10,7,11,0,14,4,1,10,7,1,6,13,0,11,8,6,13,4,13,11,0,2,11,14,7,15,4,0,9,8,1,13,10,3,14,12,3,9,5,7,12,5,2,10,15,6,8,1,6,1,6,4,11,11,13,13,8,12,1,3,4,7,10,14,7,10,9,15,5,6,0,8,15,0,14,5,2,9,3,2,12,13,1,2,15,8,13,4,8,6,10,15,3,11,7,1,4,10,12,9,5,3,6,14,11,5,0,0,14,12,9,7,2,7,2,11,1,4,14,1,7,9,4,12,10,14,8,2,13,0,15,6,12,10,9,13,0,15,3,3,5,5,6,8,11];r.substitute=function(e,t){for(var r=0,n=0;n<4;n++){r<<=4,r|=i[64*n+(e>>>18-6*n&63)]}for(n=0;n<4;n++){r<<=4,r|=i[256+64*n+(t>>>18-6*n&63)]}return r>>>0};var n=[16,25,12,11,3,20,4,15,31,17,9,6,27,14,1,22,30,24,8,18,0,5,29,23,13,19,2,26,10,21,28,7];r.permute=function(e){for(var t=0,r=0;r>>n[r]&1;return t>>>0},r.padSplit=function(e,t,r){for(var n=e.toString(2);n.lengthe;)r.ishrn(1);if(r.isEven()&&r.iadd(u),r.testn(1)||r.iadd(c),t.cmp(c)){if(!t.cmp(f))for(;r.mod(h).cmp(d);)r.iadd(p)}else for(;r.mod(a).cmp(l);)r.iadd(p);if(y(n=r.shrn(1))&&y(r)&&m(n)&&m(r)&&s.test(n)&&s.test(r))return r}}},{"bn.js":"BN","miller-rabin":106,randombytes:132}],66:[function(e,t,r){t.exports={modp1:{gen:"02",prime:"ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a63a3620ffffffffffffffff"},modp2:{gen:"02",prime:"ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece65381ffffffffffffffff"},modp5:{gen:"02",prime:"ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca237327ffffffffffffffff"},modp14:{gen:"02",prime:"ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3be39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf6955817183995497cea956ae515d2261898fa051015728e5a8aacaa68ffffffffffffffff"},modp15:{gen:"02",prime:"ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3be39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf6955817183995497cea956ae515d2261898fa051015728e5a8aaac42dad33170d04507a33a85521abdf1cba64ecfb850458dbef0a8aea71575d060c7db3970f85a6e1e4c7abf5ae8cdb0933d71e8c94e04a25619dcee3d2261ad2ee6bf12ffa06d98a0864d87602733ec86a64521f2b18177b200cbbe117577a615d6c770988c0bad946e208e24fa074e5ab3143db5bfce0fd108e4b82d120a93ad2caffffffffffffffff"},modp16:{gen:"02",prime:"ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3be39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf6955817183995497cea956ae515d2261898fa051015728e5a8aaac42dad33170d04507a33a85521abdf1cba64ecfb850458dbef0a8aea71575d060c7db3970f85a6e1e4c7abf5ae8cdb0933d71e8c94e04a25619dcee3d2261ad2ee6bf12ffa06d98a0864d87602733ec86a64521f2b18177b200cbbe117577a615d6c770988c0bad946e208e24fa074e5ab3143db5bfce0fd108e4b82d120a92108011a723c12a787e6d788719a10bdba5b2699c327186af4e23c1a946834b6150bda2583e9ca2ad44ce8dbbbc2db04de8ef92e8efc141fbecaa6287c59474e6bc05d99b2964fa090c3a2233ba186515be7ed1f612970cee2d7afb81bdd762170481cd0069127d5b05aa993b4ea988d8fddc186ffb7dc90a6c08f4df435c934063199ffffffffffffffff"},modp17:{gen:"02",prime:"ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3be39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf6955817183995497cea956ae515d2261898fa051015728e5a8aaac42dad33170d04507a33a85521abdf1cba64ecfb850458dbef0a8aea71575d060c7db3970f85a6e1e4c7abf5ae8cdb0933d71e8c94e04a25619dcee3d2261ad2ee6bf12ffa06d98a0864d87602733ec86a64521f2b18177b200cbbe117577a615d6c770988c0bad946e208e24fa074e5ab3143db5bfce0fd108e4b82d120a92108011a723c12a787e6d788719a10bdba5b2699c327186af4e23c1a946834b6150bda2583e9ca2ad44ce8dbbbc2db04de8ef92e8efc141fbecaa6287c59474e6bc05d99b2964fa090c3a2233ba186515be7ed1f612970cee2d7afb81bdd762170481cd0069127d5b05aa993b4ea988d8fddc186ffb7dc90a6c08f4df435c93402849236c3fab4d27c7026c1d4dcb2602646dec9751e763dba37bdf8ff9406ad9e530ee5db382f413001aeb06a53ed9027d831179727b0865a8918da3edbebcf9b14ed44ce6cbaced4bb1bdb7f1447e6cc254b332051512bd7af426fb8f401378cd2bf5983ca01c64b92ecf032ea15d1721d03f482d7ce6e74fef6d55e702f46980c82b5a84031900b1c9e59e7c97fbec7e8f323a97a7e36cc88be0f1d45b7ff585ac54bd407b22b4154aacc8f6d7ebf48e1d814cc5ed20f8037e0a79715eef29be32806a1d58bb7c5da76f550aa3d8a1fbff0eb19ccb1a313d55cda56c9ec2ef29632387fe8d76e3c0468043e8f663f4860ee12bf2d5b0b7474d6e694f91e6dcc4024ffffffffffffffff"},modp18:{gen:"02",prime:"ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3be39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf6955817183995497cea956ae515d2261898fa051015728e5a8aaac42dad33170d04507a33a85521abdf1cba64ecfb850458dbef0a8aea71575d060c7db3970f85a6e1e4c7abf5ae8cdb0933d71e8c94e04a25619dcee3d2261ad2ee6bf12ffa06d98a0864d87602733ec86a64521f2b18177b200cbbe117577a615d6c770988c0bad946e208e24fa074e5ab3143db5bfce0fd108e4b82d120a92108011a723c12a787e6d788719a10bdba5b2699c327186af4e23c1a946834b6150bda2583e9ca2ad44ce8dbbbc2db04de8ef92e8efc141fbecaa6287c59474e6bc05d99b2964fa090c3a2233ba186515be7ed1f612970cee2d7afb81bdd762170481cd0069127d5b05aa993b4ea988d8fddc186ffb7dc90a6c08f4df435c93402849236c3fab4d27c7026c1d4dcb2602646dec9751e763dba37bdf8ff9406ad9e530ee5db382f413001aeb06a53ed9027d831179727b0865a8918da3edbebcf9b14ed44ce6cbaced4bb1bdb7f1447e6cc254b332051512bd7af426fb8f401378cd2bf5983ca01c64b92ecf032ea15d1721d03f482d7ce6e74fef6d55e702f46980c82b5a84031900b1c9e59e7c97fbec7e8f323a97a7e36cc88be0f1d45b7ff585ac54bd407b22b4154aacc8f6d7ebf48e1d814cc5ed20f8037e0a79715eef29be32806a1d58bb7c5da76f550aa3d8a1fbff0eb19ccb1a313d55cda56c9ec2ef29632387fe8d76e3c0468043e8f663f4860ee12bf2d5b0b7474d6e694f91e6dbe115974a3926f12fee5e438777cb6a932df8cd8bec4d073b931ba3bc832b68d9dd300741fa7bf8afc47ed2576f6936ba424663aab639c5ae4f5683423b4742bf1c978238f16cbe39d652de3fdb8befc848ad922222e04a4037c0713eb57a81a23f0c73473fc646cea306b4bcbc8862f8385ddfa9d4b7fa2c087e879683303ed5bdd3a062b3cf5b3a278a66d2a13f83f44f82ddf310ee074ab6a364597e899a0255dc164f31cc50846851df9ab48195ded7ea1b1d510bd7ee74d73faf36bc31ecfa268359046f4eb879f924009438b481c6cd7889a002ed5ee382bc9190da6fc026e479558e4475677e9aa9e3050e2765694dfc81f56e880b96e7160c980dd98edd3dfffffffffffffffff"}}},{}],67:[function(e,t,r){var n=r;n.version=e("../package.json").version,n.utils=e("./elliptic/utils"),n.rand=e("brorand"),n.curve=e("./elliptic/curve"),n.curves=e("./elliptic/curves"),n.ec=e("./elliptic/ec"),n.eddsa=e("./elliptic/eddsa")},{"../package.json":82,"./elliptic/curve":70,"./elliptic/curves":73,"./elliptic/ec":74,"./elliptic/eddsa":77,"./elliptic/utils":81,brorand:16}],68:[function(e,t,r){var n=e("bn.js"),i=e("../../elliptic").utils,k=i.getNAF,E=i.getJSF,h=i.assert;function o(e,t){this.type=e,this.p=new n(t.p,16),this.red=t.prime?n.red(t.prime):n.mont(this.p),this.zero=new n(0).toRed(this.red),this.one=new n(1).toRed(this.red),this.two=new n(2).toRed(this.red),this.n=t.n&&new n(t.n,16),this.g=t.g&&this.pointFromJSON(t.g,t.gRed),this._wnafT1=new Array(4),this._wnafT2=new Array(4),this._wnafT3=new Array(4),this._wnafT4=new Array(4);var r=this.n&&this.p.div(this.n);!r||0>1]):a.mixedAdd(i[-u-1>>1].neg()):0>1]):a.add(i[-u-1>>1].neg())}return"affine"===e.type?a.toP():a},o.prototype._wnafMulAdd=function(e,t,r,n,i){for(var o=this._wnafT1,a=this._wnafT2,s=this._wnafT3,u=0,c=0;c>1]:M<0&&(x=a[y][-M-1>>1].neg()),g="affine"===x.type?g.mixedAdd(x):g.add(x))}}for(c=0;c=Math.ceil((e.bitLength()+1)/t.step)},a.prototype._getDoubles=function(e,t){if(this.precomputed&&this.precomputed.doubles)return this.precomputed.doubles;for(var r=[this],n=this,i=0;i":""},f.prototype.isInfinity=function(){return 0===this.x.cmpn(0)&&0===this.y.cmp(this.z)},f.prototype._extDbl=function(){var e=this.x.redSqr(),t=this.y.redSqr(),r=this.z.redSqr();r=r.redIAdd(r);var n=this.curve._mulA(e),i=this.x.redAdd(this.y).redSqr().redISub(e).redISub(t),o=n.redAdd(t),a=o.redSub(r),s=n.redSub(t),u=i.redMul(a),c=o.redMul(s),f=i.redMul(s),h=a.redMul(o);return this.curve.point(u,c,h,f)},f.prototype._projDbl=function(){var e,t,r,n=this.x.redAdd(this.y).redSqr(),i=this.x.redSqr(),o=this.y.redSqr();if(this.curve.twisted){var a=(c=this.curve._mulA(i)).redAdd(o);if(this.zOne)e=n.redSub(i).redSub(o).redMul(a.redSub(this.curve.two)),t=a.redMul(c.redSub(o)),r=a.redSqr().redSub(a).redSub(a);else{var s=this.z.redSqr(),u=a.redSub(s).redISub(s);e=n.redSub(i).redISub(o).redMul(u),t=a.redMul(c.redSub(o)),r=a.redMul(u)}}else{var c=i.redAdd(o);s=this.curve._mulC(this.c.redMul(this.z)).redSqr(),u=c.redSub(s).redSub(s);e=this.curve._mulC(n.redISub(c)).redMul(u),t=this.curve._mulC(c).redMul(i.redISub(o)),r=c.redMul(u)}return this.curve.point(e,t,r)},f.prototype.dbl=function(){return this.isInfinity()?this:this.curve.extended?this._extDbl():this._projDbl()},f.prototype._extAdd=function(e){var t=this.y.redSub(this.x).redMul(e.y.redSub(e.x)),r=this.y.redAdd(this.x).redMul(e.y.redAdd(e.x)),n=this.t.redMul(this.curve.dd).redMul(e.t),i=this.z.redMul(e.z.redAdd(e.z)),o=r.redSub(t),a=i.redSub(n),s=i.redAdd(n),u=r.redAdd(t),c=o.redMul(a),f=s.redMul(u),h=o.redMul(u),d=a.redMul(s);return this.curve.point(c,f,d,h)},f.prototype._projAdd=function(e){var t,r,n=this.z.redMul(e.z),i=n.redSqr(),o=this.x.redMul(e.x),a=this.y.redMul(e.y),s=this.curve.d.redMul(o).redMul(a),u=i.redSub(s),c=i.redAdd(s),f=this.x.redAdd(this.y).redMul(e.x.redAdd(e.y)).redISub(o).redISub(a),h=n.redMul(u).redMul(f);return this.curve.twisted?(t=n.redMul(c).redMul(a.redSub(this.curve._mulA(o))),r=u.redMul(c)):(t=n.redMul(c).redMul(a.redSub(o)),r=this.curve._mulC(u).redMul(c)),this.curve.point(h,t,r)},f.prototype.add=function(e){return this.isInfinity()?e:e.isInfinity()?this:this.curve.extended?this._extAdd(e):this._projAdd(e)},f.prototype.mul=function(e){return this._hasDoubles(e)?this.curve._fixedNafMul(this,e):this.curve._wnafMul(this,e)},f.prototype.mulAdd=function(e,t,r){return this.curve._wnafMulAdd(1,[this,t],[e,r],2,!1)},f.prototype.jmulAdd=function(e,t,r){return this.curve._wnafMulAdd(1,[this,t],[e,r],2,!0)},f.prototype.normalize=function(){if(this.zOne)return this;var e=this.z.redInvm();return this.x=this.x.redMul(e),this.y=this.y.redMul(e),this.t&&(this.t=this.t.redMul(e)),this.z=this.curve.one,this.zOne=!0,this},f.prototype.neg=function(){return this.curve.point(this.x.redNeg(),this.y,this.z,this.t&&this.t.redNeg())},f.prototype.getX=function(){return this.normalize(),this.x.fromRed()},f.prototype.getY=function(){return this.normalize(),this.y.fromRed()},f.prototype.eq=function(e){return this===e||0===this.getX().cmp(e.getX())&&0===this.getY().cmp(e.getY())},f.prototype.eqXToP=function(e){var t=e.toRed(this.curve.red).redMul(this.z);if(0===this.x.cmp(t))return!0;for(var r=e.clone(),n=this.curve.redN.redMul(this.z);;){if(r.iadd(this.curve.n),0<=r.cmp(this.curve.p))return!1;if(t.redIAdd(n),0===this.x.cmp(t))return!0}return!1},f.prototype.toP=f.prototype.normalize,f.prototype.mixedAdd=f.prototype.add},{"../../elliptic":67,"../curve":70,"bn.js":"BN",inherits:102}],70:[function(e,t,r){var n=r;n.base=e("./base"),n.short=e("./short"),n.mont=e("./mont"),n.edwards=e("./edwards")},{"./base":68,"./edwards":69,"./mont":71,"./short":72}],71:[function(e,t,r){var n=e("../curve"),i=e("bn.js"),o=e("inherits"),a=n.base,s=e("../../elliptic").utils;function u(e){a.call(this,"mont",e),this.a=new i(e.a,16).toRed(this.red),this.b=new i(e.b,16).toRed(this.red),this.i4=new i(4).toRed(this.red).redInvm(),this.two=new i(2).toRed(this.red),this.a24=this.i4.redMul(this.a.redAdd(this.two))}function c(e,t,r){a.BasePoint.call(this,e,"projective"),null===t&&null===r?(this.x=this.curve.one,this.z=this.curve.zero):(this.x=new i(t,16),this.z=new i(r,16),this.x.red||(this.x=this.x.toRed(this.curve.red)),this.z.red||(this.z=this.z.toRed(this.curve.red)))}o(u,a),(t.exports=u).prototype.validate=function(e){var t=e.normalize().x,r=t.redSqr(),n=r.redMul(t).redAdd(r.redMul(this.a)).redAdd(t);return 0===n.redSqrt().redSqr().cmp(n)},o(c,a.BasePoint),u.prototype.decodePoint=function(e,t){return this.point(s.toArray(e,t),1)},u.prototype.point=function(e,t){return new c(this,e,t)},u.prototype.pointFromJSON=function(e){return c.fromJSON(this,e)},c.prototype.precompute=function(){},c.prototype._encode=function(){return this.getX().toArray("be",this.curve.p.byteLength())},c.fromJSON=function(e,t){return new c(e,t[0],t[1]||e.one)},c.prototype.inspect=function(){return this.isInfinity()?"":""},c.prototype.isInfinity=function(){return 0===this.z.cmpn(0)},c.prototype.dbl=function(){var e=this.x.redAdd(this.z).redSqr(),t=this.x.redSub(this.z).redSqr(),r=e.redSub(t),n=e.redMul(t),i=r.redMul(t.redAdd(this.curve.a24.redMul(r)));return this.curve.point(n,i)},c.prototype.add=function(){throw new Error("Not supported on Montgomery curve")},c.prototype.diffAdd=function(e,t){var r=this.x.redAdd(this.z),n=this.x.redSub(this.z),i=e.x.redAdd(e.z),o=e.x.redSub(e.z).redMul(r),a=i.redMul(n),s=t.z.redMul(o.redAdd(a).redSqr()),u=t.x.redMul(o.redISub(a).redSqr());return this.curve.point(s,u)},c.prototype.mul=function(e){for(var t=e.clone(),r=this,n=this.curve.point(null,null),i=[];0!==t.cmpn(0);t.iushrn(1))i.push(t.andln(1));for(var o=i.length-1;0<=o;o--)0===i[o]?(r=r.diffAdd(n,this),n=n.dbl()):(n=r.diffAdd(n,this),r=r.dbl());return n},c.prototype.mulAdd=function(){throw new Error("Not supported on Montgomery curve")},c.prototype.jumlAdd=function(){throw new Error("Not supported on Montgomery curve")},c.prototype.eq=function(e){return 0===this.getX().cmp(e.getX())},c.prototype.normalize=function(){return this.x=this.x.redMul(this.z.redInvm()),this.z=this.curve.one,this},c.prototype.getX=function(){return this.normalize(),this.x.fromRed()}},{"../../elliptic":67,"../curve":70,"bn.js":"BN",inherits:102}],72:[function(e,t,r){var n=e("../curve"),i=e("../../elliptic"),_=e("bn.js"),o=e("inherits"),a=n.base,s=i.utils.assert;function u(e){a.call(this,"short",e),this.a=new _(e.a,16).toRed(this.red),this.b=new _(e.b,16).toRed(this.red),this.tinv=this.two.redInvm(),this.zeroA=0===this.a.fromRed().cmpn(0),this.threeA=0===this.a.fromRed().sub(this.p).cmpn(-3),this.endo=this._getEndomorphism(e),this._endoWnafT1=new Array(4),this._endoWnafT2=new Array(4)}function c(e,t,r,n){a.BasePoint.call(this,e,"affine"),null===t&&null===r?(this.x=null,this.y=null,this.inf=!0):(this.x=new _(t,16),this.y=new _(r,16),n&&(this.x.forceRed(this.curve.red),this.y.forceRed(this.curve.red)),this.x.red||(this.x=this.x.toRed(this.curve.red)),this.y.red||(this.y=this.y.toRed(this.curve.red)),this.inf=!1)}function f(e,t,r,n){a.BasePoint.call(this,e,"jacobian"),null===t&&null===r&&null===n?(this.x=this.curve.one,this.y=this.curve.one,this.z=new _(0)):(this.x=new _(t,16),this.y=new _(r,16),this.z=new _(n,16)),this.x.red||(this.x=this.x.toRed(this.curve.red)),this.y.red||(this.y=this.y.toRed(this.curve.red)),this.z.red||(this.z=this.z.toRed(this.curve.red)),this.zOne=this.z===this.curve.one}o(u,a),(t.exports=u).prototype._getEndomorphism=function(e){if(this.zeroA&&this.g&&this.n&&1===this.p.modn(3)){var t,r;if(e.beta)t=new _(e.beta,16).toRed(this.red);else{var n=this._getEndoRoots(this.p);t=(t=n[0].cmp(n[1])<0?n[0]:n[1]).toRed(this.red)}if(e.lambda)r=new _(e.lambda,16);else{var i=this._getEndoRoots(this.n);0===this.g.mul(i[0]).x.cmp(this.g.x.redMul(t))?r=i[0]:(r=i[1],s(0===this.g.mul(r).x.cmp(this.g.x.redMul(t))))}return{beta:t,lambda:r,basis:e.basis?e.basis.map(function(e){return{a:new _(e.a,16),b:new _(e.b,16)}}):this._getEndoBasis(r)}}},u.prototype._getEndoRoots=function(e){var t=e===this.p?this.red:_.mont(e),r=new _(2).toRed(t).redInvm(),n=r.redNeg(),i=new _(3).toRed(t).redNeg().redSqrt().redMul(r);return[n.redAdd(i).fromRed(),n.redSub(i).fromRed()]},u.prototype._getEndoBasis=function(e){for(var t,r,n,i,o,a,s,u,c,f=this.n.ushrn(Math.floor(this.n.bitLength()/2)),h=e,d=this.n.clone(),l=new _(1),p=new _(0),b=new _(0),y=new _(1),m=0;0!==h.cmpn(0);){var v=d.div(h);u=d.sub(v.mul(h)),c=b.sub(v.mul(l));var g=y.sub(v.mul(p));if(!n&&u.cmp(f)<0)t=s.neg(),r=l,n=u.neg(),i=c;else if(n&&2==++m)break;d=h,h=s=u,b=l,l=c,y=p,p=g}o=u.neg(),a=c;var w=n.sqr().add(i.sqr());return 0<=o.sqr().add(a.sqr()).cmp(w)&&(o=t,a=r),n.negative&&(n=n.neg(),i=i.neg()),o.negative&&(o=o.neg(),a=a.neg()),[{a:n,b:i},{a:o,b:a}]},u.prototype._endoSplit=function(e){var t=this.endo.basis,r=t[0],n=t[1],i=n.b.mul(e).divRound(this.n),o=r.b.neg().mul(e).divRound(this.n),a=i.mul(r.a),s=o.mul(n.a),u=i.mul(r.b),c=o.mul(n.b);return{k1:e.sub(a).sub(s),k2:u.add(c).neg()}},u.prototype.pointFromX=function(e,t){(e=new _(e,16)).red||(e=e.toRed(this.red));var r=e.redSqr().redMul(e).redIAdd(e.redMul(this.a)).redIAdd(this.b),n=r.redSqrt();if(0!==n.redSqr().redSub(r).cmp(this.zero))throw new Error("invalid point");var i=n.fromRed().isOdd();return(t&&!i||!t&&i)&&(n=n.redNeg()),this.point(e,n)},u.prototype.validate=function(e){if(e.inf)return!0;var t=e.x,r=e.y,n=this.a.redMul(t),i=t.redSqr().redMul(t).redIAdd(n).redIAdd(this.b);return 0===r.redSqr().redISub(i).cmpn(0)},u.prototype._endoWnafMulAdd=function(e,t,r){for(var n=this._endoWnafT1,i=this._endoWnafT2,o=0;o":""},c.prototype.isInfinity=function(){return this.inf},c.prototype.add=function(e){if(this.inf)return e;if(e.inf)return this;if(this.eq(e))return this.dbl();if(this.neg().eq(e))return this.curve.point(null,null);if(0===this.x.cmp(e.x))return this.curve.point(null,null);var t=this.y.redSub(e.y);0!==t.cmpn(0)&&(t=t.redMul(this.x.redSub(e.x).redInvm()));var r=t.redSqr().redISub(this.x).redISub(e.x),n=t.redMul(this.x.redSub(r)).redISub(this.y);return this.curve.point(r,n)},c.prototype.dbl=function(){if(this.inf)return this;var e=this.y.redAdd(this.y);if(0===e.cmpn(0))return this.curve.point(null,null);var t=this.curve.a,r=this.x.redSqr(),n=e.redInvm(),i=r.redAdd(r).redIAdd(r).redIAdd(t).redMul(n),o=i.redSqr().redISub(this.x.redAdd(this.x)),a=i.redMul(this.x.redSub(o)).redISub(this.y);return this.curve.point(o,a)},c.prototype.getX=function(){return this.x.fromRed()},c.prototype.getY=function(){return this.y.fromRed()},c.prototype.mul=function(e){return e=new _(e,16),this._hasDoubles(e)?this.curve._fixedNafMul(this,e):this.curve.endo?this.curve._endoWnafMulAdd([this],[e]):this.curve._wnafMul(this,e)},c.prototype.mulAdd=function(e,t,r){var n=[this,t],i=[e,r];return this.curve.endo?this.curve._endoWnafMulAdd(n,i):this.curve._wnafMulAdd(1,n,i,2)},c.prototype.jmulAdd=function(e,t,r){var n=[this,t],i=[e,r];return this.curve.endo?this.curve._endoWnafMulAdd(n,i,!0):this.curve._wnafMulAdd(1,n,i,2,!0)},c.prototype.eq=function(e){return this===e||this.inf===e.inf&&(this.inf||0===this.x.cmp(e.x)&&0===this.y.cmp(e.y))},c.prototype.neg=function(e){if(this.inf)return this;var t=this.curve.point(this.x,this.y.redNeg());if(e&&this.precomputed){var r=this.precomputed,n=function(e){return e.neg()};t.precomputed={naf:r.naf&&{wnd:r.naf.wnd,points:r.naf.points.map(n)},doubles:r.doubles&&{step:r.doubles.step,points:r.doubles.points.map(n)}}}return t},c.prototype.toJ=function(){return this.inf?this.curve.jpoint(null,null,null):this.curve.jpoint(this.x,this.y,this.curve.one)},o(f,a.BasePoint),u.prototype.jpoint=function(e,t,r){return new f(this,e,t,r)},f.prototype.toP=function(){if(this.isInfinity())return this.curve.point(null,null);var e=this.z.redInvm(),t=e.redSqr(),r=this.x.redMul(t),n=this.y.redMul(t).redMul(e);return this.curve.point(r,n)},f.prototype.neg=function(){return this.curve.jpoint(this.x,this.y.redNeg(),this.z)},f.prototype.add=function(e){if(this.isInfinity())return e;if(e.isInfinity())return this;var t=e.z.redSqr(),r=this.z.redSqr(),n=this.x.redMul(t),i=e.x.redMul(r),o=this.y.redMul(t.redMul(e.z)),a=e.y.redMul(r.redMul(this.z)),s=n.redSub(i),u=o.redSub(a);if(0===s.cmpn(0))return 0!==u.cmpn(0)?this.curve.jpoint(null,null,null):this.dbl();var c=s.redSqr(),f=c.redMul(s),h=n.redMul(c),d=u.redSqr().redIAdd(f).redISub(h).redISub(h),l=u.redMul(h.redISub(d)).redISub(o.redMul(f)),p=this.z.redMul(e.z).redMul(s);return this.curve.jpoint(d,l,p)},f.prototype.mixedAdd=function(e){if(this.isInfinity())return e.toJ();if(e.isInfinity())return this;var t=this.z.redSqr(),r=this.x,n=e.x.redMul(t),i=this.y,o=e.y.redMul(t).redMul(this.z),a=r.redSub(n),s=i.redSub(o);if(0===a.cmpn(0))return 0!==s.cmpn(0)?this.curve.jpoint(null,null,null):this.dbl();var u=a.redSqr(),c=u.redMul(a),f=r.redMul(u),h=s.redSqr().redIAdd(c).redISub(f).redISub(f),d=s.redMul(f.redISub(h)).redISub(i.redMul(c)),l=this.z.redMul(a);return this.curve.jpoint(h,d,l)},f.prototype.dblp=function(e){if(0===e)return this;if(this.isInfinity())return this;if(!e)return this.dbl();if(this.curve.zeroA||this.curve.threeA){for(var t=this,r=0;r":""},f.prototype.isInfinity=function(){return 0===this.z.cmpn(0)}},{"../../elliptic":67,"../curve":70,"bn.js":"BN",inherits:102}],73:[function(e,t,r){var n,i=r,o=e("hash.js"),a=e("../elliptic"),s=a.utils.assert;function u(e){"short"===e.type?this.curve=new a.curve.short(e):"edwards"===e.type?this.curve=new a.curve.edwards(e):this.curve=new a.curve.mont(e),this.g=this.curve.g,this.n=this.curve.n,this.hash=e.hash,s(this.g.validate(),"Invalid curve"),s(this.g.mul(this.n).isInfinity(),"Invalid curve, G*N != O")}function c(t,r){Object.defineProperty(i,t,{configurable:!0,enumerable:!0,get:function(){var e=new u(r);return Object.defineProperty(i,t,{configurable:!0,enumerable:!0,value:e}),e}})}i.PresetCurve=u,c("p192",{type:"short",prime:"p192",p:"ffffffff ffffffff ffffffff fffffffe ffffffff ffffffff",a:"ffffffff ffffffff ffffffff fffffffe ffffffff fffffffc",b:"64210519 e59c80e7 0fa7e9ab 72243049 feb8deec c146b9b1",n:"ffffffff ffffffff ffffffff 99def836 146bc9b1 b4d22831",hash:o.sha256,gRed:!1,g:["188da80e b03090f6 7cbf20eb 43a18800 f4ff0afd 82ff1012","07192b95 ffc8da78 631011ed 6b24cdd5 73f977a1 1e794811"]}),c("p224",{type:"short",prime:"p224",p:"ffffffff ffffffff ffffffff ffffffff 00000000 00000000 00000001",a:"ffffffff ffffffff ffffffff fffffffe ffffffff ffffffff fffffffe",b:"b4050a85 0c04b3ab f5413256 5044b0b7 d7bfd8ba 270b3943 2355ffb4",n:"ffffffff ffffffff ffffffff ffff16a2 e0b8f03e 13dd2945 5c5c2a3d",hash:o.sha256,gRed:!1,g:["b70e0cbd 6bb4bf7f 321390b9 4a03c1d3 56c21122 343280d6 115c1d21","bd376388 b5f723fb 4c22dfe6 cd4375a0 5a074764 44d58199 85007e34"]}),c("p256",{type:"short",prime:null,p:"ffffffff 00000001 00000000 00000000 00000000 ffffffff ffffffff ffffffff",a:"ffffffff 00000001 00000000 00000000 00000000 ffffffff ffffffff fffffffc",b:"5ac635d8 aa3a93e7 b3ebbd55 769886bc 651d06b0 cc53b0f6 3bce3c3e 27d2604b",n:"ffffffff 00000000 ffffffff ffffffff bce6faad a7179e84 f3b9cac2 fc632551",hash:o.sha256,gRed:!1,g:["6b17d1f2 e12c4247 f8bce6e5 63a440f2 77037d81 2deb33a0 f4a13945 d898c296","4fe342e2 fe1a7f9b 8ee7eb4a 7c0f9e16 2bce3357 6b315ece cbb64068 37bf51f5"]}),c("p384",{type:"short",prime:null,p:"ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffe ffffffff 00000000 00000000 ffffffff",a:"ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffe ffffffff 00000000 00000000 fffffffc",b:"b3312fa7 e23ee7e4 988e056b e3f82d19 181d9c6e fe814112 0314088f 5013875a c656398d 8a2ed19d 2a85c8ed d3ec2aef",n:"ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff c7634d81 f4372ddf 581a0db2 48b0a77a ecec196a ccc52973",hash:o.sha384,gRed:!1,g:["aa87ca22 be8b0537 8eb1c71e f320ad74 6e1d3b62 8ba79b98 59f741e0 82542a38 5502f25d bf55296c 3a545e38 72760ab7","3617de4a 96262c6f 5d9e98bf 9292dc29 f8f41dbd 289a147c e9da3113 b5f0b8c0 0a60b1ce 1d7e819d 7a431d7c 90ea0e5f"]}),c("p521",{type:"short",prime:null,p:"000001ff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff",a:"000001ff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffc",b:"00000051 953eb961 8e1c9a1f 929a21a0 b68540ee a2da725b 99b315f3 b8b48991 8ef109e1 56193951 ec7e937b 1652c0bd 3bb1bf07 3573df88 3d2c34f1 ef451fd4 6b503f00",n:"000001ff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffa 51868783 bf2f966b 7fcc0148 f709a5d0 3bb5c9b8 899c47ae bb6fb71e 91386409",hash:o.sha512,gRed:!1,g:["000000c6 858e06b7 0404e9cd 9e3ecb66 2395b442 9c648139 053fb521 f828af60 6b4d3dba a14b5e77 efe75928 fe1dc127 a2ffa8de 3348b3c1 856a429b f97e7e31 c2e5bd66","00000118 39296a78 9a3bc004 5c8a5fb4 2c7d1bd9 98f54449 579b4468 17afbd17 273e662c 97ee7299 5ef42640 c550b901 3fad0761 353c7086 a272c240 88be9476 9fd16650"]}),c("curve25519",{type:"mont",prime:"p25519",p:"7fffffffffffffff ffffffffffffffff ffffffffffffffff ffffffffffffffed",a:"76d06",b:"1",n:"1000000000000000 0000000000000000 14def9dea2f79cd6 5812631a5cf5d3ed",hash:o.sha256,gRed:!1,g:["9"]}),c("ed25519",{type:"edwards",prime:"p25519",p:"7fffffffffffffff ffffffffffffffff ffffffffffffffff ffffffffffffffed",a:"-1",c:"1",d:"52036cee2b6ffe73 8cc740797779e898 00700a4d4141d8ab 75eb4dca135978a3",n:"1000000000000000 0000000000000000 14def9dea2f79cd6 5812631a5cf5d3ed",hash:o.sha256,gRed:!1,g:["216936d3cd6e53fec0a4e231fdd6dc5c692cc7609525a7b2c9562d608f25d51a","6666666666666666666666666666666666666666666666666666666666666658"]});try{n=e("./precomputed/secp256k1")}catch(e){n=void 0}c("secp256k1",{type:"short",prime:"k256",p:"ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffe fffffc2f",a:"0",b:"7",n:"ffffffff ffffffff ffffffff fffffffe baaedce6 af48a03b bfd25e8c d0364141",h:"1",hash:o.sha256,beta:"7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee",lambda:"5363ad4cc05c30e0a5261c028812645a122e22ea20816678df02967c1b23bd72",basis:[{a:"3086d221a7d46bcde86c90e49284eb15",b:"-e4437ed6010e88286f547fa90abfe4c3"},{a:"114ca50f7a8e2f3f657c1108d9d44cfd8",b:"3086d221a7d46bcde86c90e49284eb15"}],gRed:!1,g:["79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8",n]})},{"../elliptic":67,"./precomputed/secp256k1":80,"hash.js":86}],74:[function(e,t,r){var y=e("bn.js"),m=e("hmac-drbg"),o=e("../../elliptic"),l=o.utils.assert,n=e("./key"),v=e("./signature");function i(e){if(!(this instanceof i))return new i(e);"string"==typeof e&&(l(o.curves.hasOwnProperty(e),"Unknown curve "+e),e=o.curves[e]),e instanceof o.curves.PresetCurve&&(e={curve:e}),this.curve=e.curve.curve,this.n=this.curve.n,this.nh=this.n.ushrn(1),this.g=this.curve.g,this.g=e.curve.g,this.g.precompute(e.curve.n.bitLength()+1),this.hash=e.hash||e.curve.hash}(t.exports=i).prototype.keyPair=function(e){return new n(this,e)},i.prototype.keyFromPrivate=function(e,t){return n.fromPrivate(this,e,t)},i.prototype.keyFromPublic=function(e,t){return n.fromPublic(this,e,t)},i.prototype.genKeyPair=function(e){e||(e={});for(var t=new m({hash:this.hash,pers:e.pers,persEnc:e.persEnc||"utf8",entropy:e.entropy||o.rand(this.hash.hmacStrength),entropyEnc:e.entropy&&e.entropyEnc||"utf8",nonce:this.n.toArray()}),r=this.n.byteLength(),n=this.n.sub(new y(2));;){var i=new y(t.generate(r));if(!(0>1;if(0<=a.cmp(this.curve.p.umod(this.curve.n))&&c)throw new Error("Unable to find sencond key candinate");a=c?this.curve.pointFromX(a.add(this.curve.n),u):this.curve.pointFromX(a,u);var f=t.r.invm(i),h=i.sub(o).mul(f).umod(i),d=s.mul(f).umod(i);return this.g.mulAdd(h,a,d)},i.prototype.getKeyRecoveryParam=function(e,t,r,n){if(null!==(t=new v(t,n)).recoveryParam)return t.recoveryParam;for(var i=0;i<4;i++){var o;try{o=this.recoverPubKey(e,t,i)}catch(e){continue}if(o.eq(r))return i}throw new Error("Unable to find valid recovery factor")}},{"../../elliptic":67,"./key":75,"./signature":76,"bn.js":"BN","hmac-drbg":98}],75:[function(e,t,r){var n=e("bn.js"),i=e("../../elliptic").utils.assert;function o(e,t){this.ec=e,this.priv=null,this.pub=null,t.priv&&this._importPrivate(t.priv,t.privEnc),t.pub&&this._importPublic(t.pub,t.pubEnc)}(t.exports=o).fromPublic=function(e,t,r){return t instanceof o?t:new o(e,{pub:t,pubEnc:r})},o.fromPrivate=function(e,t,r){return t instanceof o?t:new o(e,{priv:t,privEnc:r})},o.prototype.validate=function(){var e=this.getPublic();return e.isInfinity()?{result:!1,reason:"Invalid public key"}:e.validate()?e.mul(this.ec.curve.n).isInfinity()?{result:!0,reason:null}:{result:!1,reason:"Public key * N != O"}:{result:!1,reason:"Public key is not a point"}},o.prototype.getPublic=function(e,t){return"string"==typeof e&&(t=e,e=null),this.pub||(this.pub=this.ec.g.mul(this.priv)),t?this.pub.encode(t,e):this.pub},o.prototype.getPrivate=function(e){return"hex"===e?this.priv.toString(16,2):this.priv},o.prototype._importPrivate=function(e,t){this.priv=new n(e,t||16),this.priv=this.priv.umod(this.ec.curve.n)},o.prototype._importPublic=function(e,t){if(e.x||e.y)return"mont"===this.ec.curve.type?i(e.x,"Need x coordinate"):"short"!==this.ec.curve.type&&"edwards"!==this.ec.curve.type||i(e.x&&e.y,"Need both x and y coordinate"),void(this.pub=this.ec.curve.point(e.x,e.y));this.pub=this.ec.curve.decodePoint(e,t)},o.prototype.derive=function(e){return e.mul(this.priv).getX()},o.prototype.sign=function(e,t,r){return this.ec.sign(e,this,t,r)},o.prototype.verify=function(e,t){return this.ec.verify(e,t,this)},o.prototype.inspect=function(){return""}},{"../../elliptic":67,"bn.js":"BN"}],76:[function(e,t,r){var s=e("bn.js"),u=e("../../elliptic").utils,n=u.assert;function i(e,t){if(e instanceof i)return e;this._importDER(e,t)||(n(e.r&&e.s,"Signature without r or s"),this.r=new s(e.r,16),this.s=new s(e.s,16),void 0===e.recoveryParam?this.recoveryParam=null:this.recoveryParam=e.recoveryParam)}function c(){this.place=0}function f(e,t){var r=e[t.place++];if(!(128&r))return r;for(var n=15&r,i=0,o=0,a=t.place;o>>3);for(e.push(128|r);--r;)e.push(t>>>(r<<3)&255);e.push(t)}}(t.exports=i).prototype._importDER=function(e,t){e=u.toArray(e,t);var r=new c;if(48!==e[r.place++])return!1;if(f(e,r)+r.place!==e.length)return!1;if(2!==e[r.place++])return!1;var n=f(e,r),i=e.slice(r.place,n+r.place);if(r.place+=n,2!==e[r.place++])return!1;var o=f(e,r);if(e.length!==o+r.place)return!1;var a=e.slice(r.place,o+r.place);return 0===i[0]&&128&i[1]&&(i=i.slice(1)),0===a[0]&&128&a[1]&&(a=a.slice(1)),this.r=new s(i),this.s=new s(a),!(this.recoveryParam=null)},i.prototype.toDER=function(e){var t=this.r.toArray(),r=this.s.toArray();for(128&t[0]&&(t=[0].concat(t)),128&r[0]&&(r=[0].concat(r)),t=a(t),r=a(r);!(r[0]||128&r[1]);)r=r.slice(1);var n=[2];h(n,t.length),(n=n.concat(t)).push(2),h(n,r.length);var i=n.concat(r),o=[48];return h(o,i.length),o=o.concat(i),u.encode(o,e)}},{"../../elliptic":67,"bn.js":"BN"}],77:[function(e,t,r){var n=e("hash.js"),i=e("../../elliptic"),o=i.utils,a=o.assert,u=o.parseBytes,s=e("./key"),c=e("./signature");function f(e){if(a("ed25519"===e,"only tested with ed25519 so far"),!(this instanceof f))return new f(e);e=i.curves[e].curve;this.curve=e,this.g=e.g,this.g.precompute(e.n.bitLength()+1),this.pointClass=e.point().constructor,this.encodingLength=Math.ceil(e.n.bitLength()/8),this.hash=n.sha512}(t.exports=f).prototype.sign=function(e,t){e=u(e);var r=this.keyFromSecret(t),n=this.hashInt(r.messagePrefix(),e),i=this.g.mul(n),o=this.encodePoint(i),a=this.hashInt(o,r.pubBytes(),e).mul(r.priv()),s=n.add(a).umod(this.curve.n);return this.makeSignature({R:i,S:s,Rencoded:o})},f.prototype.verify=function(e,t,r){e=u(e),t=this.makeSignature(t);var n=this.keyFromPublic(r),i=this.hashInt(t.Rencoded(),n.pubBytes(),e),o=this.g.mul(t.S());return t.R().add(n.pub().mul(i)).eq(o)},f.prototype.hashInt=function(){for(var e=this.hash(),t=0;t>1)-1>1)-a:a,i.isubn(o)}else o=0;r.push(o);for(var s=0!==i.cmpn(0)&&0===i.andln(n-1)?t+1:1,u=1;ur&&(this._events[e].warned=!0,console.error("(node) warning: possible EventEmitter memory leak detected. %d listeners added. Use emitter.setMaxListeners() to increase limit.",this._events[e].length),"function"==typeof console.trace&&console.trace()),this},n.prototype.once=function(e,t){if(!u(t))throw TypeError("listener must be a function");var r=!1;function n(){this.removeListener(e,n),r||(r=!0,t.apply(this,arguments))}return n.listener=t,this.on(e,n),this},n.prototype.removeListener=function(e,t){var r,n,i,o;if(!u(t))throw TypeError("listener must be a function");if(!this._events||!this._events[e])return this;if(i=(r=this._events[e]).length,n=-1,r===t||u(r.listener)&&r.listener===t)delete this._events[e],this._events.removeListener&&this.emit("removeListener",e,t);else if(c(r)){for(o=i;0=this._blockSize;){for(var i=this._blockOffset;i=this._delta8){var r=(e=this.pending).length%this._delta8;this.pending=e.slice(e.length-r,e.length),0===this.pending.length&&(this.pending=null),e=i.join32(e,0,e.length-r,this.endian);for(var n=0;n>>24&255,n[i++]=e>>>16&255,n[i++]=e>>>8&255,n[i++]=255&e}else for(n[i++]=255&e,n[i++]=e>>>8&255,n[i++]=e>>>16&255,n[i++]=e>>>24&255,n[i++]=0,n[i++]=0,n[i++]=0,n[i++]=0,o=8;othis.blockSize&&(e=(new this.Hash).update(e).digest()),i(e.length<=this.blockSize);for(var t=e.length;t>>3},r.g1_256=function(e){return n(e,17)^n(e,19)^e>>>10}},{"../utils":97}],97:[function(e,t,r){var c=e("minimalistic-assert"),n=e("inherits");function o(e){return(e>>>24|e>>>8&65280|e<<8&16711680|(255&e)<<24)>>>0}function i(e){return 1===e.length?"0"+e:e}function a(e){return 7===e.length?"0"+e:6===e.length?"00"+e:5===e.length?"000"+e:4===e.length?"0000"+e:3===e.length?"00000"+e:2===e.length?"000000"+e:1===e.length?"0000000"+e:e}r.inherits=n,r.toArray=function(e,t){if(Array.isArray(e))return e.slice();if(!e)return[];var r=[];if("string"==typeof e)if(t){if("hex"===t)for((e=e.replace(/[^a-z0-9]+/gi,"")).length%2!=0&&(e="0"+e),n=0;n>8,a=255&i;o?r.push(o,a):r.push(a)}else for(n=0;n>>0}return o},r.split32=function(e,t){for(var r=new Array(4*e.length),n=0,i=0;n>>24,r[i+1]=o>>>16&255,r[i+2]=o>>>8&255,r[i+3]=255&o):(r[i+3]=o>>>24,r[i+2]=o>>>16&255,r[i+1]=o>>>8&255,r[i]=255&o)}return r},r.rotr32=function(e,t){return e>>>t|e<<32-t},r.rotl32=function(e,t){return e<>>32-t},r.sum32=function(e,t){return e+t>>>0},r.sum32_3=function(e,t,r){return e+t+r>>>0},r.sum32_4=function(e,t,r,n){return e+t+r+n>>>0},r.sum32_5=function(e,t,r,n,i){return e+t+r+n+i>>>0},r.sum64=function(e,t,r,n){var i=e[t],o=n+e[t+1]>>>0,a=(o>>0,e[t+1]=o},r.sum64_hi=function(e,t,r,n){return(t+n>>>0>>0},r.sum64_lo=function(e,t,r,n){return t+n>>>0},r.sum64_4_hi=function(e,t,r,n,i,o,a,s){var u=0,c=t;return u+=(c=c+n>>>0)>>0)>>0)>>0},r.sum64_4_lo=function(e,t,r,n,i,o,a,s){return t+n+o+s>>>0},r.sum64_5_hi=function(e,t,r,n,i,o,a,s,u,c){var f=0,h=t;return f+=(h=h+n>>>0)>>0)>>0)>>0)>>0},r.sum64_5_lo=function(e,t,r,n,i,o,a,s,u,c){return t+n+o+s+c>>>0},r.rotr64_hi=function(e,t,r){return(t<<32-r|e>>>r)>>>0},r.rotr64_lo=function(e,t,r){return(e<<32-r|t>>>r)>>>0},r.shr64_hi=function(e,t,r){return e>>>r},r.shr64_lo=function(e,t,r){return(e<<32-r|t>>>r)>>>0}},{inherits:102,"minimalistic-assert":107}],98:[function(e,t,r){var n=e("hash.js"),a=e("minimalistic-crypto-utils"),i=e("minimalistic-assert");function o(e){if(!(this instanceof o))return new o(e);this.hash=e.hash,this.predResist=!!e.predResist,this.outLen=this.hash.outSize,this.minEntropy=e.minEntropy||this.hash.hmacStrength,this._reseed=null,this.reseedInterval=null,this.K=null,this.V=null;var t=a.toArray(e.entropy,e.entropyEnc||"hex"),r=a.toArray(e.nonce,e.nonceEnc||"hex"),n=a.toArray(e.pers,e.persEnc||"hex");i(t.length>=this.minEntropy/8,"Not enough entropy. Minimum is: "+this.minEntropy+" bits"),this._init(t,r,n)}(t.exports=o).prototype._init=function(e,t,r){var n=e.concat(t).concat(r);this.K=new Array(this.outLen/8),this.V=new Array(this.outLen/8);for(var i=0;i=this.minEntropy/8,"Not enough entropy. Minimum is: "+this.minEntropy+" bits"),this._update(e.concat(r||[])),this._reseed=1},o.prototype.generate=function(e,t,r,n){if(this._reseed>this.reseedInterval)throw new Error("Reseed is required");"string"!=typeof t&&(n=r,r=t,t=null),r&&(r=a.toArray(r,n||"hex"),this._update(r));for(var i=[];i.length>1,f=-7,h=r?i-1:0,d=r?-1:1,l=e[t+h];for(h+=d,o=l&(1<<-f)-1,l>>=-f,f+=s;0>=-f,f+=n;0>1,d=23===i?Math.pow(2,-24)-Math.pow(2,-77):0,l=n?0:o-1,p=n?1:-1,b=t<0||0===t&&1/t<0?1:0;for(t=Math.abs(t),isNaN(t)||t===1/0?(s=isNaN(t)?1:0,a=f):(a=Math.floor(Math.log(t)/Math.LN2),t*(u=Math.pow(2,-a))<1&&(a--,u*=2),2<=(t+=1<=a+h?d/u:d*Math.pow(2,1-h))*u&&(a++,u/=2),f<=a+h?(s=0,a=f):1<=a+h?(s=(t*u-1)*Math.pow(2,i),a+=h):(s=t*Math.pow(2,h-1)*Math.pow(2,i),a=0));8<=i;e[r+l]=255&s,l+=p,s/=256,i-=8);for(a=a<>>32-t}function u(e,t,r,n,i,o,a){return s(e+(t&r|~t&n)+i+o|0,a)+t|0}function c(e,t,r,n,i,o,a){return s(e+(t&n|r&~n)+i+o|0,a)+t|0}function f(e,t,r,n,i,o,a){return s(e+(t^r^n)+i+o|0,a)+t|0}function h(e,t,r,n,i,o,a){return s(e+(r^(t|~n))+i+o|0,a)+t|0}e(n,r),n.prototype._update=function(){for(var e=a,t=0;t<16;++t)e[t]=this._block.readInt32LE(4*t);var r=this._a,n=this._b,i=this._c,o=this._d;n=h(n=h(n=h(n=h(n=f(n=f(n=f(n=f(n=c(n=c(n=c(n=c(n=u(n=u(n=u(n=u(n,i=u(i,o=u(o,r=u(r,n,i,o,e[0],3614090360,7),n,i,e[1],3905402710,12),r,n,e[2],606105819,17),o,r,e[3],3250441966,22),i=u(i,o=u(o,r=u(r,n,i,o,e[4],4118548399,7),n,i,e[5],1200080426,12),r,n,e[6],2821735955,17),o,r,e[7],4249261313,22),i=u(i,o=u(o,r=u(r,n,i,o,e[8],1770035416,7),n,i,e[9],2336552879,12),r,n,e[10],4294925233,17),o,r,e[11],2304563134,22),i=u(i,o=u(o,r=u(r,n,i,o,e[12],1804603682,7),n,i,e[13],4254626195,12),r,n,e[14],2792965006,17),o,r,e[15],1236535329,22),i=c(i,o=c(o,r=c(r,n,i,o,e[1],4129170786,5),n,i,e[6],3225465664,9),r,n,e[11],643717713,14),o,r,e[0],3921069994,20),i=c(i,o=c(o,r=c(r,n,i,o,e[5],3593408605,5),n,i,e[10],38016083,9),r,n,e[15],3634488961,14),o,r,e[4],3889429448,20),i=c(i,o=c(o,r=c(r,n,i,o,e[9],568446438,5),n,i,e[14],3275163606,9),r,n,e[3],4107603335,14),o,r,e[8],1163531501,20),i=c(i,o=c(o,r=c(r,n,i,o,e[13],2850285829,5),n,i,e[2],4243563512,9),r,n,e[7],1735328473,14),o,r,e[12],2368359562,20),i=f(i,o=f(o,r=f(r,n,i,o,e[5],4294588738,4),n,i,e[8],2272392833,11),r,n,e[11],1839030562,16),o,r,e[14],4259657740,23),i=f(i,o=f(o,r=f(r,n,i,o,e[1],2763975236,4),n,i,e[4],1272893353,11),r,n,e[7],4139469664,16),o,r,e[10],3200236656,23),i=f(i,o=f(o,r=f(r,n,i,o,e[13],681279174,4),n,i,e[0],3936430074,11),r,n,e[3],3572445317,16),o,r,e[6],76029189,23),i=f(i,o=f(o,r=f(r,n,i,o,e[9],3654602809,4),n,i,e[12],3873151461,11),r,n,e[15],530742520,16),o,r,e[2],3299628645,23),i=h(i,o=h(o,r=h(r,n,i,o,e[0],4096336452,6),n,i,e[7],1126891415,10),r,n,e[14],2878612391,15),o,r,e[5],4237533241,21),i=h(i,o=h(o,r=h(r,n,i,o,e[12],1700485571,6),n,i,e[3],2399980690,10),r,n,e[10],4293915773,15),o,r,e[1],2240044497,21),i=h(i,o=h(o,r=h(r,n,i,o,e[8],1873313359,6),n,i,e[15],4264355552,10),r,n,e[6],2734768916,15),o,r,e[13],1309151649,21),i=h(i,o=h(o,r=h(r,n,i,o,e[4],4149444226,6),n,i,e[11],3174756917,10),r,n,e[2],718787259,15),o,r,e[9],3951481745,21),this._a=this._a+r|0,this._b=this._b+n|0,this._c=this._c+i|0,this._d=this._d+o|0},n.prototype._digest=function(){this._block[this._blockOffset++]=128,56>8,a=255&i;o?r.push(o,a):r.push(a)}return r},n.zero2=i,n.toHex=o,n.encode=function(e,t){return"hex"===t?o(e):e}},{}],109:[function(e,t,r){r.endianness=function(){return"LE"},r.hostname=function(){return"undefined"!=typeof location?location.hostname:""},r.loadavg=function(){return[]},r.uptime=function(){return 0},r.freemem=function(){return Number.MAX_VALUE},r.totalmem=function(){return Number.MAX_VALUE},r.cpus=function(){return[]},r.type=function(){return"Browser"},r.release=function(){return"undefined"!=typeof navigator?navigator.appVersion:""},r.networkInterfaces=r.getNetworkInterfaces=function(){return{}},r.arch=function(){return"javascript"},r.platform=function(){return"browser"},r.tmpdir=r.tmpDir=function(){return"/tmp"},r.EOL="\n",r.homedir=function(){return"/"}},{}],110:[function(e,t,r){t.exports={"2.16.840.1.101.3.4.1.1":"aes-128-ecb","2.16.840.1.101.3.4.1.2":"aes-128-cbc","2.16.840.1.101.3.4.1.3":"aes-128-ofb","2.16.840.1.101.3.4.1.4":"aes-128-cfb","2.16.840.1.101.3.4.1.21":"aes-192-ecb","2.16.840.1.101.3.4.1.22":"aes-192-cbc","2.16.840.1.101.3.4.1.23":"aes-192-ofb","2.16.840.1.101.3.4.1.24":"aes-192-cfb","2.16.840.1.101.3.4.1.41":"aes-256-ecb","2.16.840.1.101.3.4.1.42":"aes-256-cbc","2.16.840.1.101.3.4.1.43":"aes-256-ofb","2.16.840.1.101.3.4.1.44":"aes-256-cfb"}},{}],111:[function(e,t,r){var n=e("asn1.js");r.certificate=e("./certificate");var i=n.define("RSAPrivateKey",function(){this.seq().obj(this.key("version").int(),this.key("modulus").int(),this.key("publicExponent").int(),this.key("privateExponent").int(),this.key("prime1").int(),this.key("prime2").int(),this.key("exponent1").int(),this.key("exponent2").int(),this.key("coefficient").int())});r.RSAPrivateKey=i;var o=n.define("RSAPublicKey",function(){this.seq().obj(this.key("modulus").int(),this.key("publicExponent").int())});r.RSAPublicKey=o;var a=n.define("SubjectPublicKeyInfo",function(){this.seq().obj(this.key("algorithm").use(s),this.key("subjectPublicKey").bitstr())});r.PublicKey=a;var s=n.define("AlgorithmIdentifier",function(){this.seq().obj(this.key("algorithm").objid(),this.key("none").null_().optional(),this.key("curve").objid().optional(),this.key("params").seq().obj(this.key("p").int(),this.key("q").int(),this.key("g").int()).optional())}),u=n.define("PrivateKeyInfo",function(){this.seq().obj(this.key("version").int(),this.key("algorithm").use(s),this.key("subjectPrivateKey").octstr())});r.PrivateKey=u;var c=n.define("EncryptedPrivateKeyInfo",function(){this.seq().obj(this.key("algorithm").seq().obj(this.key("id").objid(),this.key("decrypt").seq().obj(this.key("kde").seq().obj(this.key("id").objid(),this.key("kdeparams").seq().obj(this.key("salt").octstr(),this.key("iters").int())),this.key("cipher").seq().obj(this.key("algo").objid(),this.key("iv").octstr()))),this.key("subjectPrivateKey").octstr())});r.EncryptedPrivateKey=c;var f=n.define("DSAPrivateKey",function(){this.seq().obj(this.key("version").int(),this.key("p").int(),this.key("q").int(),this.key("g").int(),this.key("pub_key").int(),this.key("priv_key").int())});r.DSAPrivateKey=f,r.DSAparam=n.define("DSAparam",function(){this.int()});var h=n.define("ECPrivateKey",function(){this.seq().obj(this.key("version").int(),this.key("privateKey").octstr(),this.key("parameters").optional().explicit(0).use(d),this.key("publicKey").optional().explicit(1).bitstr())});r.ECPrivateKey=h;var d=n.define("ECParameters",function(){this.choice({namedCurve:this.objid()})});r.signature=n.define("signature",function(){this.seq().obj(this.key("r").int(),this.key("s").int())})},{"./certificate":112,"asn1.js":1}],112:[function(e,t,r){var n=e("asn1.js"),i=n.define("Time",function(){this.choice({utcTime:this.utctime(),generalTime:this.gentime()})}),o=n.define("AttributeTypeValue",function(){this.seq().obj(this.key("type").objid(),this.key("value").any())}),a=n.define("AlgorithmIdentifier",function(){this.seq().obj(this.key("algorithm").objid(),this.key("parameters").optional())}),s=n.define("SubjectPublicKeyInfo",function(){this.seq().obj(this.key("algorithm").use(a),this.key("subjectPublicKey").bitstr())}),u=n.define("RelativeDistinguishedName",function(){this.setof(o)}),c=n.define("RDNSequence",function(){this.seqof(u)}),f=n.define("Name",function(){this.choice({rdnSequence:this.use(c)})}),h=n.define("Validity",function(){this.seq().obj(this.key("notBefore").use(i),this.key("notAfter").use(i))}),d=n.define("Extension",function(){this.seq().obj(this.key("extnID").objid(),this.key("critical").bool().def(!1),this.key("extnValue").octstr())}),l=n.define("TBSCertificate",function(){this.seq().obj(this.key("version").explicit(0).int(),this.key("serialNumber").int(),this.key("signature").use(a),this.key("issuer").use(f),this.key("validity").use(h),this.key("subject").use(f),this.key("subjectPublicKeyInfo").use(s),this.key("issuerUniqueID").implicit(1).bitstr().optional(),this.key("subjectUniqueID").implicit(2).bitstr().optional(),this.key("extensions").explicit(3).seqof(d).optional())}),p=n.define("X509Certificate",function(){this.seq().obj(this.key("tbsCertificate").use(l),this.key("signatureAlgorithm").use(a),this.key("signatureValue").bitstr())});t.exports=p},{"asn1.js":1}],113:[function(e,t,r){(function(d){var l=/Proc-Type: 4,ENCRYPTED[\n\r]+DEK-Info: AES-((?:128)|(?:192)|(?:256))-CBC,([0-9A-H]+)[\n\r]+([0-9A-z\n\r\+\/\=]+)[\n\r]+/m,p=/^-----BEGIN ((?:.* KEY)|CERTIFICATE)-----/m,b=/^-----BEGIN ((?:.* KEY)|CERTIFICATE)-----([0-9A-z\n\r\+\/\=]+)-----END \1-----$/m,y=e("evp_bytestokey"),m=e("browserify-aes");t.exports=function(e,t){var r,n=e.toString(),i=n.match(l);if(i){var o="aes"+i[1],a=new d(i[2],"hex"),s=new d(i[3].replace(/[\r\n]/g,""),"base64"),u=y(t,a.slice(0,8),parseInt(i[1],10)).key,c=[],f=m.createDecipheriv(o,u,a);c.push(f.update(s)),c.push(f.final()),r=d.concat(c)}else{var h=n.match(b);r=new d(h[2].replace(/[\r\n]/g,""),"base64")}return{tag:n.match(p)[1],data:r}}}).call(this,e("buffer").Buffer)},{"browserify-aes":20,buffer:47,evp_bytestokey:84}],114:[function(t,r,e){(function(v){var g=t("./asn1"),w=t("./aesid.json"),_=t("./fixProc"),A=t("browserify-aes"),x=t("pbkdf2");function e(e){var t;"object"!==(void 0===e?"undefined":_typeof(e))||v.isBuffer(e)||(t=e.passphrase,e=e.key),"string"==typeof e&&(e=new v(e));var r,n,i,o,a,s,u,c,f,h,d,l,p,b=_(e,t),y=b.tag,m=b.data;switch(y){case"CERTIFICATE":n=g.certificate.decode(m,"der").tbsCertificate.subjectPublicKeyInfo;case"PUBLIC KEY":switch(n||(n=g.PublicKey.decode(m,"der")),r=n.algorithm.algorithm.join(".")){case"1.2.840.113549.1.1.1":return g.RSAPublicKey.decode(n.subjectPublicKey.data,"der");case"1.2.840.10045.2.1":return n.subjectPrivateKey=n.subjectPublicKey,{type:"ec",data:n};case"1.2.840.10040.4.1":return n.algorithm.params.pub_key=g.DSAparam.decode(n.subjectPublicKey.data,"der"),{type:"dsa",data:n.algorithm.params};default:throw new Error("unknown key id "+r)}throw new Error("unknown key type "+y);case"ENCRYPTED PRIVATE KEY":m=g.EncryptedPrivateKey.decode(m,"der"),o=t,a=(i=m).algorithm.decrypt.kde.kdeparams.salt,s=parseInt(i.algorithm.decrypt.kde.kdeparams.iters.toString(),10),u=w[i.algorithm.decrypt.cipher.algo.join(".")],c=i.algorithm.decrypt.cipher.iv,f=i.subjectPrivateKey,h=parseInt(u.split("-")[1],10)/8,d=x.pbkdf2Sync(o,a,s,h),l=A.createDecipheriv(u,d,c),(p=[]).push(l.update(f)),p.push(l.final()),m=v.concat(p);case"PRIVATE KEY":switch(r=(n=g.PrivateKey.decode(m,"der")).algorithm.algorithm.join(".")){case"1.2.840.113549.1.1.1":return g.RSAPrivateKey.decode(n.subjectPrivateKey,"der");case"1.2.840.10045.2.1":return{curve:n.algorithm.curve,privateKey:g.ECPrivateKey.decode(n.subjectPrivateKey,"der").privateKey};case"1.2.840.10040.4.1":return n.algorithm.params.priv_key=g.DSAparam.decode(n.subjectPrivateKey,"der"),{type:"dsa",params:n.algorithm.params};default:throw new Error("unknown key id "+r)}throw new Error("unknown key type "+y);case"RSA PUBLIC KEY":return g.RSAPublicKey.decode(m,"der");case"RSA PRIVATE KEY":return g.RSAPrivateKey.decode(m,"der");case"DSA PRIVATE KEY":return{type:"dsa",params:g.DSAPrivateKey.decode(m,"der")};case"EC PRIVATE KEY":return{curve:(m=g.ECPrivateKey.decode(m,"der")).parameters.value,privateKey:m.privateKey};default:throw new Error("unknown key type "+y)}}(r.exports=e).signature=g.signature}).call(this,t("buffer").Buffer)},{"./aesid.json":110,"./asn1":111,"./fixProc":113,"browserify-aes":20,buffer:47,pbkdf2:115}],115:[function(e,t,r){r.pbkdf2=e("./lib/async"),r.pbkdf2Sync=e("./lib/sync")},{"./lib/async":116,"./lib/sync":119}],116:[function(e,t,r){(function(c,f){var h,d=e("./precondition"),l=e("./default-encoding"),p=e("./sync"),b=e("safe-buffer").Buffer,y=f.crypto&&f.crypto.subtle,m={sha:"SHA-1","sha-1":"SHA-1",sha1:"SHA-1",sha256:"SHA-256","sha-256":"SHA-256",sha384:"SHA-384","sha-384":"SHA-384","sha-512":"SHA-512",sha512:"SHA-512"},v=[];function g(e,t,r,n,i){return y.importKey("raw",e,{name:"PBKDF2"},!1,["deriveBits"]).then(function(e){return y.deriveBits({name:"PBKDF2",salt:t,iterations:r,hash:{name:i}},e,n<<3)}).then(function(e){return b.from(e)})}t.exports=function(t,r,n,i,o,a){"function"==typeof o&&(a=o,o=void 0);var e,s,u=m[(o=o||"sha1").toLowerCase()];if(!u||"function"!=typeof f.Promise)return c.nextTick(function(){var e;try{e=p(t,r,n,i,o)}catch(e){return a(e)}a(null,e)});if(d(t,r,n,i),"function"!=typeof a)throw new Error("No callback provided to pbkdf2");b.isBuffer(t)||(t=b.from(t,l)),b.isBuffer(r)||(r=b.from(r,l)),e=function(e){if(f.process&&!f.process.browser)return Promise.resolve(!1);if(!y||!y.importKey||!y.deriveBits)return Promise.resolve(!1);if(void 0!==v[e])return v[e];var t=g(h=h||b.alloc(8),h,10,128,e).then(function(){return!0}).catch(function(){return!1});return v[e]=t}(u).then(function(e){return e?g(t,r,n,i,u):p(t,r,n,i,o)}),s=a,e.then(function(e){c.nextTick(function(){s(null,e)})},function(e){c.nextTick(function(){s(e)})})}}).call(this,e("_process"),"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./default-encoding":117,"./precondition":118,"./sync":119,_process:121,"safe-buffer":149}],117:[function(e,r,t){(function(e){var t;e.browser?t="utf-8":t=6<=parseInt(e.version.split(".")[0].slice(1),10)?"utf-8":"binary";r.exports=t}).call(this,e("_process"))},{_process:121}],118:[function(e,t,r){(function(r){var i=Math.pow(2,30)-1;function o(e,t){if("string"!=typeof e&&!r.isBuffer(e))throw new TypeError(t+" must be a buffer or string")}t.exports=function(e,t,r,n){if(o(e,"Password"),o(t,"Salt"),"number"!=typeof r)throw new TypeError("Iterations not a number");if(r<0)throw new TypeError("Bad iterations");if("number"!=typeof n)throw new TypeError("Key length not a number");if(n<0||io?t=i(t):t.lengtha||0<=new c(t).cmp(o.modulus))throw new Error("decryption error");i=r?b(new c(t),o):l(t,o);var s=new f(a-i.length);if(s.fill(0),i=f.concat([s,i],a),4===n)return function(e,t){e.modulus;var r=e.modulus.byteLength(),n=(t.length,p("sha1").update(new f("")).digest()),i=n.length;if(0!==t[0])throw new Error("decryption error");var o=t.slice(1,i+1),a=t.slice(i+1),s=d(o,h(a,i)),u=d(a,h(s,r-i-1));if(function(e,t){e=new f(e),t=new f(t);var r=0,n=e.length;e.length!==t.length&&(r++,n=Math.min(e.length,t.length));var i=-1;for(;++i=t.length){o++;break}var a=t.slice(2,i-1);t.slice(i-1,i);("0002"!==n.toString("hex")&&!r||"0001"!==n.toString("hex")&&r)&&o++;a.length<8&&o++;if(o)throw new Error("decryption error");return t.slice(i)}(0,i,r);if(3===n)return i;throw new Error("unknown padding")}}).call(this,e("buffer").Buffer)},{"./mgf":123,"./withPublic":126,"./xor":127,"bn.js":"BN","browserify-rsa":38,buffer:47,"create-hash":52,"parse-asn1":114}],125:[function(e,t,r){(function(d){var a=e("parse-asn1"),l=e("randombytes"),p=e("create-hash"),b=e("./mgf"),y=e("./xor"),m=e("bn.js"),s=e("./withPublic"),u=e("browserify-rsa");t.exports=function(e,t,r){var n;n=e.padding?e.padding:r?1:4;var i,o=a(e);if(4===n)i=function(e,t){var r=e.modulus.byteLength(),n=t.length,i=p("sha1").update(new d("")).digest(),o=i.length,a=2*o;if(r-a-2= 0x80 (not a basic code point)","invalid-input":"Invalid input"},d=v-g,M=Math.floor,k=String.fromCharCode;function E(e){throw new RangeError(h[e])}function l(e,t){for(var r=e.length,n=[];r--;)n[r]=t(e[r]);return n}function p(e,t){var r=e.split("@"),n="";return 1>>10&1023|55296),e=56320|1023&e),t+=k(e)}).join("")}function j(e,t){return e+22+75*(e<26)-((0!=t)<<5)}function I(e,t,r){var n=0;for(e=r?M(e/s):e>>1,e+=M(e/t);d*w>>1M((m-p)/a))&&E("overflow"),p+=u*a,!(u<(c=s<=y?g:y+w<=s?w:s-y));s+=v)a>M(m/(f=v-c))&&E("overflow"),a*=f;y=I(p-o,t=d.length+1,0==o),M(p/t)>m-b&&E("overflow"),b+=M(p/t),p%=t,d.splice(p++,0,b)}return U(d)}function y(e){var t,r,n,i,o,a,s,u,c,f,h,d,l,p,b,y=[];for(d=(e=S(e)).length,t=A,o=_,a=r=0;aM((m-r)/(l=n+1))&&E("overflow"),r+=(s-t)*l,t=s,a=0;am&&E("overflow"),h==t){for(u=r,c=v;!(u<(f=c<=o?g:o+w<=c?w:c-o));c+=v)b=u-f,p=v-f,y.push(k(j(f+b%p,0))),u=M(b/p);y.push(k(j(u,0))),o=I(r,l,n==i),r=0,++n}++r,++t}return y.join("")}if(i={version:"1.4.1",ucs2:{decode:S,encode:U},decode:b,encode:y,toASCII:function(e){return p(e,function(e){return c.test(e)?"xn--"+y(e):e})},toUnicode:function(e){return p(e,function(e){return u.test(e)?b(e.slice(4).toLowerCase()):e})}},"function"==typeof define&&"object"==_typeof(define.amd)&&define.amd)define("punycode",function(){return i});else if(t&&r)if(C.exports==t)r.exports=i;else for(o in i)i.hasOwnProperty(o)&&(t[o]=i[o]);else e.punycode=i}(this)}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],129:[function(e,t,r){t.exports=function(e,t,r,n){t=t||"&",r=r||"=";var i={};if("string"!=typeof e||0===e.length)return i;var o=/\+/g;e=e.split(t);var a=1e3;n&&"number"==typeof n.maxKeys&&(a=n.maxKeys);var s,u,c=e.length;0t.highWaterMark&&(t.highWaterMark=(b<=(r=e)?r=b:(r--,r|=r>>>1,r|=r>>>2,r|=r>>>4,r|=r>>>8,r|=r>>>16,r++),r)),e<=t.length?e:t.ended?t.length:(t.needReadable=!0,0));var r}function A(e){var t=e._readableState;t.needReadable=!1,t.emittedReadable||(w("emitReadable",t.flowing),t.emittedReadable=!0,t.sync?m.nextTick(x,e):x(e))}function x(e){w("emit readable"),e.emit("readable"),U(e)}function M(e,t){t.readingMore||(t.readingMore=!0,m.nextTick(k,e,t))}function k(e,t){for(var r=t.length;!t.reading&&!t.flowing&&!t.ended&&t.length=t.length?(r=t.decoder?t.buffer.join(""):1===t.buffer.length?t.buffer.head.data:t.buffer.concat(t.length),t.buffer.clear()):r=function(e,t,r){var n;eo.length?o.length:e;if(a===o.length?i+=o:i+=o.slice(0,e),0===(e-=a)){a===o.length?(++n,r.next?t.head=r.next:t.head=t.tail=null):(t.head=r).data=o.slice(a);break}++n}return t.length-=n,i}(e,t):function(e,t){var r=c.allocUnsafe(e),n=t.head,i=1;n.data.copy(r),e-=n.data.length;for(;n=n.next;){var o=n.data,a=e>o.length?o.length:e;if(o.copy(r,r.length-e,0,a),0===(e-=a)){a===o.length?(++i,n.next?t.head=n.next:t.head=t.tail=null):(t.head=n).data=o.slice(a);break}++i}return t.length-=i,r}(e,t);return n}(e,t.buffer,t.decoder),r);var r}function I(e){var t=e._readableState;if(0=t.highWaterMark||t.ended))return w("read: emitReadable",t.length,t.ended),0===t.length&&t.ended?I(this):A(this),null;if(0===(e=_(e,t))&&t.ended)return 0===t.length&&I(this),null;var n,i=t.needReadable;return w("need readable",i),(0===t.length||t.length-e>>0),o=this.head,a=0;o;)t=o.data,r=i,n=a,t.copy(r,n),a+=o.data.length,o=o.next;return i},e}(),n&&n.inspect&&n.inspect.custom&&(t.exports.prototype[n.inspect.custom]=function(){var e=n.inspect({length:this.length});return this.constructor.name+" "+e})},{"safe-buffer":149,util:17}],141:[function(e,t,r){var o=e("process-nextick-args");function a(e,t){e.emit("error",t)}t.exports={destroy:function(e,t){var r=this,n=this._readableState&&this._readableState.destroyed,i=this._writableState&&this._writableState.destroyed;return n||i?t?t(e):!e||this._writableState&&this._writableState.errorEmitted||o.nextTick(a,this,e):(this._readableState&&(this._readableState.destroyed=!0),this._writableState&&(this._writableState.destroyed=!0),this._destroy(e||null,function(e){!t&&e?(o.nextTick(a,r,e),r._writableState&&(r._writableState.errorEmitted=!0)):t&&t(e)})),this},undestroy:function(){this._readableState&&(this._readableState.destroyed=!1,this._readableState.reading=!1,this._readableState.ended=!1,this._readableState.endEmitted=!1),this._writableState&&(this._writableState.destroyed=!1,this._writableState.ended=!1,this._writableState.ending=!1,this._writableState.finished=!1,this._writableState.errorEmitted=!1)}}},{"process-nextick-args":120}],142:[function(e,t,r){t.exports=e("events").EventEmitter},{events:83}],143:[function(e,t,r){var n=e("safe-buffer").Buffer,i=n.isEncoding||function(e){switch((e=""+e)&&e.toLowerCase()){case"hex":case"utf8":case"utf-8":case"ascii":case"binary":case"base64":case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":case"raw":return!0;default:return!1}};function o(e){var t;switch(this.encoding=function(e){var t=function(e){if(!e)return"utf8";for(var t;;)switch(e){case"utf8":case"utf-8":return"utf8";case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return"utf16le";case"latin1":case"binary":return"latin1";case"base64":case"ascii":case"hex":return e;default:if(t)return;e=(""+e).toLowerCase(),t=!0}}(e);if("string"!=typeof t&&(n.isEncoding===i||!i(e)))throw new Error("Unknown encoding: "+e);return t||e}(e),this.encoding){case"utf16le":this.text=u,this.end=c,t=4;break;case"utf8":this.fillLast=s,t=4;break;case"base64":this.text=f,this.end=h,t=3;break;default:return this.write=d,void(this.end=l)}this.lastNeed=0,this.lastTotal=0,this.lastChar=n.allocUnsafe(t)}function a(e){return e<=127?0:e>>5==6?2:e>>4==14?3:e>>3==30?4:e>>6==2?-1:-2}function s(e){var t=this.lastTotal-this.lastNeed,r=function(e,t,r){if(128!=(192&t[0]))return e.lastNeed=0,"�";if(1>>32-t}function M(e,t,r,n,i,o,a,s){return x(e+(t^r^n)+o+a|0,s)+i|0}function k(e,t,r,n,i,o,a,s){return x(e+(t&r|~t&n)+o+a|0,s)+i|0}function E(e,t,r,n,i,o,a,s){return x(e+((t|~r)^n)+o+a|0,s)+i|0}function S(e,t,r,n,i,o,a,s){return x(e+(t&n|r&~n)+o+a|0,s)+i|0}function U(e,t,r,n,i,o,a,s){return x(e+(t^(r|~n))+o+a|0,s)+i|0}i(a,o),a.prototype._update=function(){for(var e=y,t=0;t<16;++t)e[t]=this._block.readInt32LE(4*t);for(var r=0|this._a,n=0|this._b,i=0|this._c,o=0|this._d,a=0|this._e,s=0|this._a,u=0|this._b,c=0|this._c,f=0|this._d,h=0|this._e,d=0;d<80;d+=1){var l,p;d<16?(l=M(r,n,i,o,a,e[m[d]],_[0],g[d]),p=U(s,u,c,f,h,e[v[d]],A[0],w[d])):d<32?(l=k(r,n,i,o,a,e[m[d]],_[1],g[d]),p=S(s,u,c,f,h,e[v[d]],A[1],w[d])):d<48?(l=E(r,n,i,o,a,e[m[d]],_[2],g[d]),p=E(s,u,c,f,h,e[v[d]],A[2],w[d])):d<64?(l=S(r,n,i,o,a,e[m[d]],_[3],g[d]),p=k(s,u,c,f,h,e[v[d]],A[3],w[d])):(l=U(r,n,i,o,a,e[m[d]],_[4],g[d]),p=M(s,u,c,f,h,e[v[d]],A[4],w[d])),r=a,a=o,o=x(i,10),i=n,n=l,s=h,h=f,f=x(c,10),c=u,u=p}var b=this._b+i+f|0;this._b=this._c+o+h|0,this._c=this._d+a+s|0,this._d=this._e+r+u|0,this._e=this._a+n+c|0,this._a=b},a.prototype._digest=function(){this._block[this._blockOffset++]=128,56=this._finalSize&&(this._update(this._block),this._block.fill(0));var r=8*this._len;if(r<=4294967295)this._block.writeUInt32BE(r,this._blockSize-4);else{var n=(4294967295&r)>>>0,i=(r-n)/4294967296;this._block.writeUInt32BE(i,this._blockSize-8),this._block.writeUInt32BE(n,this._blockSize-4)}this._update(this._block);var o=this._hash();return e?o.toString(e):o},n.prototype._update=function(){throw new Error("_update must be implemented by subclass")},t.exports=n},{"safe-buffer":149}],151:[function(e,t,r){(r=t.exports=function(e){e=e.toLowerCase();var t=r[e];if(!t)throw new Error(e+" is not supported (we accept pull requests)");return new t}).sha=e("./sha"),r.sha1=e("./sha1"),r.sha224=e("./sha224"),r.sha256=e("./sha256"),r.sha384=e("./sha384"),r.sha512=e("./sha512")},{"./sha":152,"./sha1":153,"./sha224":154,"./sha256":155,"./sha384":156,"./sha512":157}],152:[function(e,t,r){var n=e("inherits"),i=e("./hash"),o=e("safe-buffer").Buffer,m=[1518500249,1859775393,-1894007588,-899497514],a=new Array(80);function s(){this.init(),this._w=a,i.call(this,64,56)}n(s,i),s.prototype.init=function(){return this._a=1732584193,this._b=4023233417,this._c=2562383102,this._d=271733878,this._e=3285377520,this},s.prototype._update=function(e){for(var t,r,n,i,o,a,s=this._w,u=0|this._a,c=0|this._b,f=0|this._c,h=0|this._d,d=0|this._e,l=0;l<16;++l)s[l]=e.readInt32BE(4*l);for(;l<80;++l)s[l]=s[l-3]^s[l-8]^s[l-14]^s[l-16];for(var p=0;p<80;++p){var b=~~(p/20),y=0|((a=u)<<5|a>>>27)+(n=c,i=f,o=h,0===(r=b)?n&i|~n&o:2===r?n&i|n&o|i&o:n^i^o)+d+s[p]+m[b];d=h,h=f,f=(t=c)<<30|t>>>2,c=u,u=y}this._a=u+this._a|0,this._b=c+this._b|0,this._c=f+this._c|0,this._d=h+this._d|0,this._e=d+this._e|0},s.prototype._hash=function(){var e=o.allocUnsafe(20);return e.writeInt32BE(0|this._a,0),e.writeInt32BE(0|this._b,4),e.writeInt32BE(0|this._c,8),e.writeInt32BE(0|this._d,12),e.writeInt32BE(0|this._e,16),e},t.exports=s},{"./hash":150,inherits:102,"safe-buffer":149}],153:[function(e,t,r){var n=e("inherits"),i=e("./hash"),o=e("safe-buffer").Buffer,v=[1518500249,1859775393,-1894007588,-899497514],a=new Array(80);function s(){this.init(),this._w=a,i.call(this,64,56)}n(s,i),s.prototype.init=function(){return this._a=1732584193,this._b=4023233417,this._c=2562383102,this._d=271733878,this._e=3285377520,this},s.prototype._update=function(e){for(var t,r,n,i,o,a,s,u=this._w,c=0|this._a,f=0|this._b,h=0|this._c,d=0|this._d,l=0|this._e,p=0;p<16;++p)u[p]=e.readInt32BE(4*p);for(;p<80;++p)u[p]=(t=u[p-3]^u[p-8]^u[p-14]^u[p-16])<<1|t>>>31;for(var b=0;b<80;++b){var y=~~(b/20),m=0|((s=c)<<5|s>>>27)+(i=f,o=h,a=d,0===(n=y)?i&o|~i&a:2===n?i&o|i&a|o&a:i^o^a)+l+u[b]+v[y];l=d,d=h,h=(r=f)<<30|r>>>2,f=c,c=m}this._a=c+this._a|0,this._b=f+this._b|0,this._c=h+this._c|0,this._d=d+this._d|0,this._e=l+this._e|0},s.prototype._hash=function(){var e=o.allocUnsafe(20);return e.writeInt32BE(0|this._a,0),e.writeInt32BE(0|this._b,4),e.writeInt32BE(0|this._c,8),e.writeInt32BE(0|this._d,12),e.writeInt32BE(0|this._e,16),e},t.exports=s},{"./hash":150,inherits:102,"safe-buffer":149}],154:[function(e,t,r){var n=e("inherits"),i=e("./sha256"),o=e("./hash"),a=e("safe-buffer").Buffer,s=new Array(64);function u(){this.init(),this._w=s,o.call(this,64,56)}n(u,i),u.prototype.init=function(){return this._a=3238371032,this._b=914150663,this._c=812702999,this._d=4144912697,this._e=4290775857,this._f=1750603025,this._g=1694076839,this._h=3204075428,this},u.prototype._hash=function(){var e=a.allocUnsafe(28);return e.writeInt32BE(this._a,0),e.writeInt32BE(this._b,4),e.writeInt32BE(this._c,8),e.writeInt32BE(this._d,12),e.writeInt32BE(this._e,16),e.writeInt32BE(this._f,20),e.writeInt32BE(this._g,24),e},t.exports=u},{"./hash":150,"./sha256":155,inherits:102,"safe-buffer":149}],155:[function(e,t,r){var n=e("inherits"),i=e("./hash"),o=e("safe-buffer").Buffer,_=[1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298],a=new Array(64);function s(){this.init(),this._w=a,i.call(this,64,56)}n(s,i),s.prototype.init=function(){return this._a=1779033703,this._b=3144134277,this._c=1013904242,this._d=2773480762,this._e=1359893119,this._f=2600822924,this._g=528734635,this._h=1541459225,this},s.prototype._update=function(e){for(var t,r,n,i,o,a,s,u=this._w,c=0|this._a,f=0|this._b,h=0|this._c,d=0|this._d,l=0|this._e,p=0|this._f,b=0|this._g,y=0|this._h,m=0;m<16;++m)u[m]=e.readInt32BE(4*m);for(;m<64;++m)u[m]=0|(((r=u[m-2])>>>17|r<<15)^(r>>>19|r<<13)^r>>>10)+u[m-7]+(((t=u[m-15])>>>7|t<<25)^(t>>>18|t<<14)^t>>>3)+u[m-16];for(var v=0;v<64;++v){var g=y+(((s=l)>>>6|s<<26)^(s>>>11|s<<21)^(s>>>25|s<<7))+((a=b)^l&(p^a))+_[v]+u[v]|0,w=0|(((o=c)>>>2|o<<30)^(o>>>13|o<<19)^(o>>>22|o<<10))+((n=c)&(i=f)|h&(n|i));y=b,b=p,p=l,l=d+g|0,d=h,h=f,f=c,c=g+w|0}this._a=c+this._a|0,this._b=f+this._b|0,this._c=h+this._c|0,this._d=d+this._d|0,this._e=l+this._e|0,this._f=p+this._f|0,this._g=b+this._g|0,this._h=y+this._h|0},s.prototype._hash=function(){var e=o.allocUnsafe(32);return e.writeInt32BE(this._a,0),e.writeInt32BE(this._b,4),e.writeInt32BE(this._c,8),e.writeInt32BE(this._d,12),e.writeInt32BE(this._e,16),e.writeInt32BE(this._f,20),e.writeInt32BE(this._g,24),e.writeInt32BE(this._h,28),e},t.exports=s},{"./hash":150,inherits:102,"safe-buffer":149}],156:[function(e,t,r){var n=e("inherits"),i=e("./sha512"),o=e("./hash"),a=e("safe-buffer").Buffer,s=new Array(160);function u(){this.init(),this._w=s,o.call(this,128,112)}n(u,i),u.prototype.init=function(){return this._ah=3418070365,this._bh=1654270250,this._ch=2438529370,this._dh=355462360,this._eh=1731405415,this._fh=2394180231,this._gh=3675008525,this._hh=1203062813,this._al=3238371032,this._bl=914150663,this._cl=812702999,this._dl=4144912697,this._el=4290775857,this._fl=1750603025,this._gl=1694076839,this._hl=3204075428,this},u.prototype._hash=function(){var n=a.allocUnsafe(48);function e(e,t,r){n.writeInt32BE(e,r),n.writeInt32BE(t,r+4)}return e(this._ah,this._al,0),e(this._bh,this._bl,8),e(this._ch,this._cl,16),e(this._dh,this._dl,24),e(this._eh,this._el,32),e(this._fh,this._fl,40),n},t.exports=u},{"./hash":150,"./sha512":157,inherits:102,"safe-buffer":149}],157:[function(e,t,r){var n=e("inherits"),i=e("./hash"),o=e("safe-buffer").Buffer,ee=[1116352408,3609767458,1899447441,602891725,3049323471,3964484399,3921009573,2173295548,961987163,4081628472,1508970993,3053834265,2453635748,2937671579,2870763221,3664609560,3624381080,2734883394,310598401,1164996542,607225278,1323610764,1426881987,3590304994,1925078388,4068182383,2162078206,991336113,2614888103,633803317,3248222580,3479774868,3835390401,2666613458,4022224774,944711139,264347078,2341262773,604807628,2007800933,770255983,1495990901,1249150122,1856431235,1555081692,3175218132,1996064986,2198950837,2554220882,3999719339,2821834349,766784016,2952996808,2566594879,3210313671,3203337956,3336571891,1034457026,3584528711,2466948901,113926993,3758326383,338241895,168717936,666307205,1188179964,773529912,1546045734,1294757372,1522805485,1396182291,2643833823,1695183700,2343527390,1986661051,1014477480,2177026350,1206759142,2456956037,344077627,2730485921,1290863460,2820302411,3158454273,3259730800,3505952657,3345764771,106217008,3516065817,3606008344,3600352804,1432725776,4094571909,1467031594,275423344,851169720,430227734,3100823752,506948616,1363258195,659060556,3750685593,883997877,3785050280,958139571,3318307427,1322822218,3812723403,1537002063,2003034995,1747873779,3602036899,1955562222,1575990012,2024104815,1125592928,2227730452,2716904306,2361852424,442776044,2428436474,593698344,2756734187,3733110249,3204031479,2999351573,3329325298,3815920427,3391569614,3928383900,3515267271,566280711,3940187606,3454069534,4118630271,4000239992,116418474,1914138554,174292421,2731055270,289380356,3203993006,460393269,320620315,685471733,587496836,852142971,1086792851,1017036298,365543100,1126000580,2618297676,1288033470,3409855158,1501505948,4234509866,1607167915,987167468,1816402316,1246189591],a=new Array(160);function s(){this.init(),this._w=a,i.call(this,128,112)}function te(e,t,r){return r^e&(t^r)}function re(e,t,r){return e&t|r&(e|t)}function ne(e,t){return(e>>>28|t<<4)^(t>>>2|e<<30)^(t>>>7|e<<25)}function ie(e,t){return(e>>>14|t<<18)^(e>>>18|t<<14)^(t>>>9|e<<23)}function oe(e,t){return e>>>0>>0?1:0}n(s,i),s.prototype.init=function(){return this._ah=1779033703,this._bh=3144134277,this._ch=1013904242,this._dh=2773480762,this._eh=1359893119,this._fh=2600822924,this._gh=528734635,this._hh=1541459225,this._al=4089235720,this._bl=2227873595,this._cl=4271175723,this._dl=1595750129,this._el=2917565137,this._fl=725511199,this._gl=4215389547,this._hl=327033209,this},s.prototype._update=function(e){for(var t,r,n,i,o,a,s,u,c=this._w,f=0|this._ah,h=0|this._bh,d=0|this._ch,l=0|this._dh,p=0|this._eh,b=0|this._fh,y=0|this._gh,m=0|this._hh,v=0|this._al,g=0|this._bl,w=0|this._cl,_=0|this._dl,A=0|this._el,x=0|this._fl,M=0|this._gl,k=0|this._hl,E=0;E<32;E+=2)c[E]=e.readInt32BE(4*E),c[E+1]=e.readInt32BE(4*E+4);for(;E<160;E+=2){var S=c[E-30],U=c[E-30+1],j=((s=S)>>>1|(u=U)<<31)^(s>>>8|u<<24)^s>>>7,I=((o=U)>>>1|(a=S)<<31)^(o>>>8|a<<24)^(o>>>7|a<<25);S=c[E-4],U=c[E-4+1];var T=((n=S)>>>19|(i=U)<<13)^(i>>>29|n<<3)^n>>>6,C=((t=U)>>>19|(r=S)<<13)^(r>>>29|t<<3)^(t>>>6|r<<26),B=c[E-14],N=c[E-14+1],P=c[E-32],R=c[E-32+1],O=I+N|0,L=j+B+oe(O,I)|0;L=(L=L+T+oe(O=O+C|0,C)|0)+P+oe(O=O+R|0,R)|0,c[E]=L,c[E+1]=O}for(var q=0;q<160;q+=2){L=c[q],O=c[q+1];var D=re(f,h,d),F=re(v,g,w),H=ne(f,v),z=ne(v,f),K=ie(p,A),V=ie(A,p),G=ee[q],W=ee[q+1],X=te(p,b,y),J=te(A,x,M),Z=k+V|0,$=m+K+oe(Z,k)|0;$=($=($=$+X+oe(Z=Z+J|0,J)|0)+G+oe(Z=Z+W|0,W)|0)+L+oe(Z=Z+O|0,O)|0;var Y=z+F|0,Q=H+D+oe(Y,z)|0;m=y,k=M,y=b,M=x,b=p,x=A,p=l+$+oe(A=_+Z|0,_)|0,l=d,_=w,d=h,w=g,h=f,g=v,f=$+Q+oe(v=Z+Y|0,Z)|0}this._al=this._al+v|0,this._bl=this._bl+g|0,this._cl=this._cl+w|0,this._dl=this._dl+_|0,this._el=this._el+A|0,this._fl=this._fl+x|0,this._gl=this._gl+M|0,this._hl=this._hl+k|0,this._ah=this._ah+f+oe(this._al,v)|0,this._bh=this._bh+h+oe(this._bl,g)|0,this._ch=this._ch+d+oe(this._cl,w)|0,this._dh=this._dh+l+oe(this._dl,_)|0,this._eh=this._eh+p+oe(this._el,A)|0,this._fh=this._fh+b+oe(this._fl,x)|0,this._gh=this._gh+y+oe(this._gl,M)|0,this._hh=this._hh+m+oe(this._hl,k)|0},s.prototype._hash=function(){var n=o.allocUnsafe(64);function e(e,t,r){n.writeInt32BE(e,r),n.writeInt32BE(t,r+4)}return e(this._ah,this._al,0),e(this._bh,this._bl,8),e(this._ch,this._cl,16),e(this._dh,this._dl,24),e(this._eh,this._el,32),e(this._fh,this._fl,40),e(this._gh,this._gl,48),e(this._hh,this._hl,56),n},t.exports=s},{"./hash":150,inherits:102,"safe-buffer":149}],158:[function(e,t,r){t.exports=n;var f=e("events").EventEmitter;function n(){f.call(this)}e("inherits")(n,f),n.Readable=e("readable-stream/readable.js"),n.Writable=e("readable-stream/writable.js"),n.Duplex=e("readable-stream/duplex.js"),n.Transform=e("readable-stream/transform.js"),n.PassThrough=e("readable-stream/passthrough.js"),(n.Stream=n).prototype.pipe=function(t,e){var r=this;function n(e){t.writable&&!1===t.write(e)&&r.pause&&r.pause()}function i(){r.readable&&r.resume&&r.resume()}r.on("data",n),t.on("drain",i),t._isStdio||e&&!1===e.end||(r.on("end",a),r.on("close",s));var o=!1;function a(){o||(o=!0,t.end())}function s(){o||(o=!0,"function"==typeof t.destroy&&t.destroy())}function u(e){if(c(),0===f.listenerCount(this,"error"))throw e}function c(){r.removeListener("data",n),t.removeListener("drain",i),r.removeListener("end",a),r.removeListener("close",s),r.removeListener("error",u),t.removeListener("error",u),r.removeListener("end",c),r.removeListener("close",c),t.removeListener("close",c)}return r.on("error",u),t.on("error",u),r.on("end",c),r.on("close",c),t.on("close",c),t.emit("pipe",r),t}},{events:83,inherits:102,"readable-stream/duplex.js":134,"readable-stream/passthrough.js":144,"readable-stream/readable.js":145,"readable-stream/transform.js":146,"readable-stream/writable.js":147}],159:[function(r,e,i){(function(u){var c=r("./lib/request"),e=r("./lib/response"),f=r("xtend"),t=r("builtin-status-codes"),h=r("url"),n=i;n.request=function(e,t){e="string"==typeof e?h.parse(e):f(e);var r=-1===u.location.protocol.search(/^https?:$/)?"http:":"",n=e.protocol||r,i=e.hostname||e.host,o=e.port,a=e.path||"/";i&&-1!==i.indexOf(":")&&(i="["+i+"]"),e.url=(i?n+"//"+i:"")+(o?":"+o:"")+a,e.method=(e.method||"GET").toUpperCase(),e.headers=e.headers||{};var s=new c(e);return t&&s.on("response",t),s},n.get=function(e,t){var r=n.request(e,t);return r.end(),r},n.ClientRequest=c,n.IncomingMessage=e.IncomingMessage,n.Agent=function(){},n.Agent.defaultMaxSockets=4,n.globalAgent=new n.Agent,n.STATUS_CODES=t,n.METHODS=["CHECKOUT","CONNECT","COPY","DELETE","GET","HEAD","LOCK","M-SEARCH","MERGE","MKACTIVITY","MKCOL","MOVE","NOTIFY","OPTIONS","PATCH","POST","PROPFIND","PROPPATCH","PURGE","PUT","REPORT","SEARCH","SUBSCRIBE","TRACE","UNLOCK","UNSUBSCRIBE"]}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./lib/request":161,"./lib/response":162,"builtin-status-codes":48,url:166,xtend:170}],160:[function(e,t,s){(function(e){s.fetch=a(e.fetch)&&a(e.ReadableStream),s.writableStream=a(e.WritableStream),s.abortController=a(e.AbortController),s.blobConstructor=!1;try{new Blob([new ArrayBuffer(1)]),s.blobConstructor=!0}catch(e){}var t;function r(){if(void 0!==t)return t;if(e.XMLHttpRequest){t=new e.XMLHttpRequest;try{t.open("GET",e.XDomainRequest?"/":"https://example.com")}catch(e){t=null}}else t=null;return t}function n(e){var t=r();if(!t)return!1;try{return t.responseType=e,t.responseType===e}catch(e){}return!1}var i=void 0!==e.ArrayBuffer,o=i&&a(e.ArrayBuffer.prototype.slice);function a(e){return"function"==typeof e}s.arraybuffer=s.fetch||i&&n("arraybuffer"),s.msstream=!s.fetch&&o&&n("ms-stream"),s.mozchunkedarraybuffer=!s.fetch&&i&&n("moz-chunked-arraybuffer"),s.overrideMimeType=s.fetch||!!r()&&a(r().overrideMimeType),s.vbArray=a(e.VBArray),t=null}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],161:[function(o,s,e){(function(u,c,f){var h=o("./capability"),e=o("inherits"),t=o("./response"),a=o("readable-stream"),d=o("to-arraybuffer"),r=t.IncomingMessage,l=t.readyStates;var n=s.exports=function(t){var e,r=this;a.Writable.call(r),r._opts=t,r._body=[],r._headers={},t.auth&&r.setHeader("Authorization","Basic "+new f(t.auth).toString("base64")),Object.keys(t.headers).forEach(function(e){r.setHeader(e,t.headers[e])});var n,i,o=!0;if("disable-fetch"===t.mode||"requestTimeout"in t&&!h.abortController)e=!(o=!1);else if("prefer-streaming"===t.mode)e=!1;else if("allow-wrong-content-type"===t.mode)e=!h.overrideMimeType;else{if(t.mode&&"default"!==t.mode&&"prefer-fast"!==t.mode)throw new Error("Invalid value for opts.mode");e=!0}r._mode=(n=e,i=o,h.fetch&&i?"fetch":h.mozchunkedarraybuffer?"moz-chunked-arraybuffer":h.msstream?"ms-stream":h.arraybuffer&&n?"arraybuffer":h.vbArray&&n?"text:vbarray":"text"),r._fetchTimer=null,r.on("finish",function(){r._onFinish()})};e(n,a.Writable),n.prototype.setHeader=function(e,t){var r=e.toLowerCase();-1===i.indexOf(r)&&(this._headers[r]={name:e,value:t})},n.prototype.getHeader=function(e){var t=this._headers[e.toLowerCase()];return t?t.value:null},n.prototype.removeHeader=function(e){delete this._headers[e.toLowerCase()]},n.prototype._onFinish=function(){var t=this;if(!t._destroyed){var e=t._opts,n=t._headers,r=null;"GET"!==e.method&&"HEAD"!==e.method&&(r=h.arraybuffer?d(f.concat(t._body)):h.blobConstructor?new c.Blob(t._body.map(function(e){return d(e)}),{type:(n["content-type"]||{}).value||""}):f.concat(t._body).toString());var i=[];if(Object.keys(n).forEach(function(e){var t=n[e].name,r=n[e].value;Array.isArray(r)?r.forEach(function(e){i.push([t,e])}):i.push([t,r])}),"fetch"===t._mode){var o=null;if(h.abortController){var a=new AbortController;o=a.signal,t._fetchAbortController=a,"requestTimeout"in e&&0!==e.requestTimeout&&(t._fetchTimer=c.setTimeout(function(){t.emit("requestTimeout"),t._fetchAbortController&&t._fetchAbortController.abort()},e.requestTimeout))}c.fetch(t._opts.url,{method:t._opts.method,headers:i,body:r||void 0,mode:"cors",credentials:e.withCredentials?"include":"same-origin",signal:o}).then(function(e){t._fetchResponse=e,t._connect()},function(e){c.clearTimeout(t._fetchTimer),t._destroyed||t.emit("error",e)})}else{var s=t._xhr=new c.XMLHttpRequest;try{s.open(t._opts.method,t._opts.url,!0)}catch(e){return void u.nextTick(function(){t.emit("error",e)})}"responseType"in s&&(s.responseType=t._mode.split(":")[0]),"withCredentials"in s&&(s.withCredentials=!!e.withCredentials),"text"===t._mode&&"overrideMimeType"in s&&s.overrideMimeType("text/plain; charset=x-user-defined"),"requestTimeout"in e&&(s.timeout=e.requestTimeout,s.ontimeout=function(){t.emit("requestTimeout")}),i.forEach(function(e){s.setRequestHeader(e[0],e[1])}),t._response=null,s.onreadystatechange=function(){switch(s.readyState){case l.LOADING:case l.DONE:t._onXHRProgress()}},"moz-chunked-arraybuffer"===t._mode&&(s.onprogress=function(){t._onXHRProgress()}),s.onerror=function(){t._destroyed||t.emit("error",new Error("XHR error"))};try{s.send(r)}catch(e){return void u.nextTick(function(){t.emit("error",e)})}}}},n.prototype._onXHRProgress=function(){(function(e){try{var t=e.status;return null!==t&&0!==t}catch(e){return!1}})(this._xhr)&&!this._destroyed&&(this._response||this._connect(),this._response._onXHRProgress())},n.prototype._connect=function(){var t=this;t._destroyed||(t._response=new r(t._xhr,t._fetchResponse,t._mode,t._fetchTimer),t._response.on("error",function(e){t.emit("error",e)}),t.emit("response",t._response))},n.prototype._write=function(e,t,r){this._body.push(e),r()},n.prototype.abort=n.prototype.destroy=function(){this._destroyed=!0,c.clearTimeout(this._fetchTimer),this._response&&(this._response._destroyed=!0),this._xhr?this._xhr.abort():this._fetchAbortController&&this._fetchAbortController.abort()},n.prototype.end=function(e,t,r){"function"==typeof e&&(r=e,e=void 0),a.Writable.prototype.end.call(this,e,t,r)},n.prototype.flushHeaders=function(){},n.prototype.setTimeout=function(){},n.prototype.setNoDelay=function(){},n.prototype.setSocketKeepAlive=function(){};var i=["accept-charset","accept-encoding","access-control-request-headers","access-control-request-method","connection","content-length","cookie","cookie2","date","dnt","expect","host","keep-alive","origin","referer","te","trailer","transfer-encoding","upgrade","user-agent","via"]}).call(this,o("_process"),"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{},o("buffer").Buffer)},{"./capability":160,"./response":162,_process:121,buffer:47,inherits:102,"readable-stream":145,"to-arraybuffer":165}],162:[function(r,e,n){(function(c,f,h){var d=r("./capability"),e=r("inherits"),l=r("readable-stream"),s=n.readyStates={UNSENT:0,OPENED:1,HEADERS_RECEIVED:2,LOADING:3,DONE:4},t=n.IncomingMessage=function(e,t,r,n){var i=this;if(l.Readable.call(i),i._mode=r,i.headers={},i.rawHeaders=[],i.trailers={},i.rawTrailers=[],i.on("end",function(){c.nextTick(function(){i.emit("close")})}),"fetch"===r){if(i._fetchResponse=t,i.url=t.url,i.statusCode=t.status,i.statusMessage=t.statusText,t.headers.forEach(function(e,t){i.headers[t.toLowerCase()]=e,i.rawHeaders.push(t,e)}),d.writableStream){var o=new WritableStream({write:function(r){return new Promise(function(e,t){i._destroyed?t():i.push(new h(r))?e():i._resumeFetch=e})},close:function(){f.clearTimeout(n),i._destroyed||i.push(null)},abort:function(e){i._destroyed||i.emit("error",e)}});try{return void t.body.pipeTo(o).catch(function(e){f.clearTimeout(n),i._destroyed||i.emit("error",e)})}catch(e){}}var a=t.body.getReader();!function t(){a.read().then(function(e){if(!i._destroyed){if(e.done)return f.clearTimeout(n),void i.push(null);i.push(new h(e.value)),t()}}).catch(function(e){f.clearTimeout(n),i._destroyed||i.emit("error",e)})}()}else{if(i._xhr=e,i._pos=0,i.url=e.responseURL,i.statusCode=e.status,i.statusMessage=e.statusText,e.getAllResponseHeaders().split(/\r?\n/).forEach(function(e){var t=e.match(/^([^:]+):\s*(.*)/);if(t){var r=t[1].toLowerCase();"set-cookie"===r?(void 0===i.headers[r]&&(i.headers[r]=[]),i.headers[r].push(t[2])):void 0!==i.headers[r]?i.headers[r]+=", "+t[2]:i.headers[r]=t[2],i.rawHeaders.push(t[1],t[2])}}),i._charset="x-user-defined",!d.overrideMimeType){var s=i.rawHeaders["mime-type"];if(s){var u=s.match(/;\s*charset=([^;])(;|$)/);u&&(i._charset=u[1].toLowerCase())}i._charset||(i._charset="utf-8")}}};e(t,l.Readable),t.prototype._read=function(){var e=this._resumeFetch;e&&(this._resumeFetch=null,e())},t.prototype._onXHRProgress=function(){var t=this,e=t._xhr,r=null;switch(t._mode){case"text:vbarray":if(e.readyState!==s.DONE)break;try{r=new f.VBArray(e.responseBody).toArray()}catch(e){}if(null!==r){t.push(new h(r));break}case"text":try{r=e.responseText}catch(e){t._mode="text:vbarray";break}if(r.length>t._pos){var n=r.substr(t._pos);if("x-user-defined"===t._charset){for(var i=new h(n.length),o=0;ot._pos&&(t.push(new h(new Uint8Array(a.result.slice(t._pos)))),t._pos=a.result.byteLength)},a.onload=function(){t.push(null)},a.readAsArrayBuffer(r)}t._xhr.readyState===s.DONE&&"ms-stream"!==t._mode&&t.push(null)}}).call(this,r("_process"),"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{},r("buffer").Buffer)},{"./capability":160,_process:121,buffer:47,inherits:102,"readable-stream":145}],163:[function(e,t,r){var n=e("safe-buffer").Buffer,i=n.isEncoding||function(e){switch((e=""+e)&&e.toLowerCase()){case"hex":case"utf8":case"utf-8":case"ascii":case"binary":case"base64":case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":case"raw":return!0;default:return!1}};function o(e){var t;switch(this.encoding=function(e){var t=function(e){if(!e)return"utf8";for(var t;;)switch(e){case"utf8":case"utf-8":return"utf8";case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return"utf16le";case"latin1":case"binary":return"latin1";case"base64":case"ascii":case"hex":return e;default:if(t)return;e=(""+e).toLowerCase(),t=!0}}(e);if("string"!=typeof t&&(n.isEncoding===i||!i(e)))throw new Error("Unknown encoding: "+e);return t||e}(e),this.encoding){case"utf16le":this.text=u,this.end=c,t=4;break;case"utf8":this.fillLast=s,t=4;break;case"base64":this.text=f,this.end=h,t=3;break;default:return this.write=d,void(this.end=l)}this.lastNeed=0,this.lastTotal=0,this.lastChar=n.allocUnsafe(t)}function a(e){return e<=127?0:e>>5==6?2:e>>4==14?3:e>>3==30?4:-1}function s(e){var t=this.lastTotal-this.lastNeed,r=function(e,t,r){if(128!=(192&t[0]))return e.lastNeed=0,"�".repeat(r);if(1",'"',"`"," ","\r","\n","\t"]),O=["'"].concat(i),L=["%","/","?",";","#"].concat(O),q=["/","?","#"],D=/^[+a-z0-9A-Z_-]{0,63}$/,F=/^([+a-z0-9A-Z_-]{0,63})(.*)$/,H={javascript:!0,"javascript:":!0},z={javascript:!0,"javascript:":!0},K={http:!0,https:!0,ftp:!0,gopher:!0,file:!0,"http:":!0,"https:":!0,"ftp:":!0,"gopher:":!0,"file:":!0},V=e("querystring");function o(e,t,r){if(e&&N.isObject(e)&&e instanceof S)return e;var n=new S;return n.parse(e,t,r),n}S.prototype.parse=function(e,t,r){if(!N.isString(e))throw new TypeError("Parameter 'url' must be a string, not "+(void 0===e?"undefined":_typeof(e)));var n=e.indexOf("?"),i=-1!==n&&n>6|192);else{if(55295>18|240),r+=t(i>>12&63|128)}else r+=t(i>>12|224);r+=t(i>>6&63|128)}r+=t(63&i|128)}}return r},toString:function(e){for(var t="",r=0,n=a(e);r>10|55296),t+=String.fromCharCode(1023&i|56320)}}return t},fromNumber:function(e){var t=e.toString(16);return t.length%2==0?"0x"+t:"0x0"+t},toNumber:function(e){return parseInt(e.slice(2),16)},fromNat:function(e){return"0x0"===e?"0x":e.length%2==0?e:"0x0"+e.slice(2)},toNat:function(e){return"0"===e[2]?"0x"+e.slice(3):e},fromArray:n,toArray:r,fromUint8Array:function(e){return n([].slice.call(e,0))},toUint8Array:function(e){return new Uint8Array(r(e))}}},{"./array.js":172}],174:[function(e,t,r){var p="0123456789abcdef".split(""),b=[1,256,65536,16777216],y=[0,8,16,24],fe=[1,0,32898,0,32906,2147483648,2147516416,2147483648,32907,0,2147483649,0,2147516545,2147483648,32777,2147483648,138,0,136,0,2147516425,0,2147483658,0,2147516555,0,139,2147483648,32905,2147483648,32771,2147483648,32770,2147483648,128,2147483648,32778,0,2147483658,2147483648,2147516545,2147483648,32896,2147483648,2147483649,0,2147516424,2147483648],m=function(e){var t,r,n,i,o,a,s,u,c,f,h,d,l,p,b,y,m,v,g,w,_,A,x,M,k,E,S,U,j,I,T,C,B,N,P,R,O,L,q,D,F,H,z,K,V,G,W,X,J,Z,$,Y,Q,ee,te,re,ne,ie,oe,ae,se,ue,ce;for(n=0;n<48;n+=2)i=e[0]^e[10]^e[20]^e[30]^e[40],o=e[1]^e[11]^e[21]^e[31]^e[41],a=e[2]^e[12]^e[22]^e[32]^e[42],s=e[3]^e[13]^e[23]^e[33]^e[43],u=e[4]^e[14]^e[24]^e[34]^e[44],c=e[5]^e[15]^e[25]^e[35]^e[45],f=e[6]^e[16]^e[26]^e[36]^e[46],h=e[7]^e[17]^e[27]^e[37]^e[47],t=(d=e[8]^e[18]^e[28]^e[38]^e[48])^(a<<1|s>>>31),r=(l=e[9]^e[19]^e[29]^e[39]^e[49])^(s<<1|a>>>31),e[0]^=t,e[1]^=r,e[10]^=t,e[11]^=r,e[20]^=t,e[21]^=r,e[30]^=t,e[31]^=r,e[40]^=t,e[41]^=r,t=i^(u<<1|c>>>31),r=o^(c<<1|u>>>31),e[2]^=t,e[3]^=r,e[12]^=t,e[13]^=r,e[22]^=t,e[23]^=r,e[32]^=t,e[33]^=r,e[42]^=t,e[43]^=r,t=a^(f<<1|h>>>31),r=s^(h<<1|f>>>31),e[4]^=t,e[5]^=r,e[14]^=t,e[15]^=r,e[24]^=t,e[25]^=r,e[34]^=t,e[35]^=r,e[44]^=t,e[45]^=r,t=u^(d<<1|l>>>31),r=c^(l<<1|d>>>31),e[6]^=t,e[7]^=r,e[16]^=t,e[17]^=r,e[26]^=t,e[27]^=r,e[36]^=t,e[37]^=r,e[46]^=t,e[47]^=r,t=f^(i<<1|o>>>31),r=h^(o<<1|i>>>31),e[8]^=t,e[9]^=r,e[18]^=t,e[19]^=r,e[28]^=t,e[29]^=r,e[38]^=t,e[39]^=r,e[48]^=t,e[49]^=r,p=e[0],b=e[1],G=e[11]<<4|e[10]>>>28,W=e[10]<<4|e[11]>>>28,U=e[20]<<3|e[21]>>>29,j=e[21]<<3|e[20]>>>29,ae=e[31]<<9|e[30]>>>23,se=e[30]<<9|e[31]>>>23,H=e[40]<<18|e[41]>>>14,z=e[41]<<18|e[40]>>>14,N=e[2]<<1|e[3]>>>31,P=e[3]<<1|e[2]>>>31,y=e[13]<<12|e[12]>>>20,m=e[12]<<12|e[13]>>>20,X=e[22]<<10|e[23]>>>22,J=e[23]<<10|e[22]>>>22,I=e[33]<<13|e[32]>>>19,T=e[32]<<13|e[33]>>>19,ue=e[42]<<2|e[43]>>>30,ce=e[43]<<2|e[42]>>>30,ee=e[5]<<30|e[4]>>>2,te=e[4]<<30|e[5]>>>2,R=e[14]<<6|e[15]>>>26,O=e[15]<<6|e[14]>>>26,v=e[25]<<11|e[24]>>>21,g=e[24]<<11|e[25]>>>21,Z=e[34]<<15|e[35]>>>17,$=e[35]<<15|e[34]>>>17,C=e[45]<<29|e[44]>>>3,B=e[44]<<29|e[45]>>>3,M=e[6]<<28|e[7]>>>4,k=e[7]<<28|e[6]>>>4,re=e[17]<<23|e[16]>>>9,ne=e[16]<<23|e[17]>>>9,L=e[26]<<25|e[27]>>>7,q=e[27]<<25|e[26]>>>7,w=e[36]<<21|e[37]>>>11,_=e[37]<<21|e[36]>>>11,Y=e[47]<<24|e[46]>>>8,Q=e[46]<<24|e[47]>>>8,K=e[8]<<27|e[9]>>>5,V=e[9]<<27|e[8]>>>5,E=e[18]<<20|e[19]>>>12,S=e[19]<<20|e[18]>>>12,ie=e[29]<<7|e[28]>>>25,oe=e[28]<<7|e[29]>>>25,D=e[38]<<8|e[39]>>>24,F=e[39]<<8|e[38]>>>24,A=e[48]<<14|e[49]>>>18,x=e[49]<<14|e[48]>>>18,e[0]=p^~y&v,e[1]=b^~m&g,e[10]=M^~E&U,e[11]=k^~S&j,e[20]=N^~R&L,e[21]=P^~O&q,e[30]=K^~G&X,e[31]=V^~W&J,e[40]=ee^~re&ie,e[41]=te^~ne&oe,e[2]=y^~v&w,e[3]=m^~g&_,e[12]=E^~U&I,e[13]=S^~j&T,e[22]=R^~L&D,e[23]=O^~q&F,e[32]=G^~X&Z,e[33]=W^~J&$,e[42]=re^~ie&ae,e[43]=ne^~oe&se,e[4]=v^~w&A,e[5]=g^~_&x,e[14]=U^~I&C,e[15]=j^~T&B,e[24]=L^~D&H,e[25]=q^~F&z,e[34]=X^~Z&Y,e[35]=J^~$&Q,e[44]=ie^~ae&ue,e[45]=oe^~se&ce,e[6]=w^~A&p,e[7]=_^~x&b,e[16]=I^~C&M,e[17]=T^~B&k,e[26]=D^~H&N,e[27]=F^~z&P,e[36]=Z^~Y&K,e[37]=$^~Q&V,e[46]=ae^~ue&ee,e[47]=se^~ce&te,e[8]=A^~p&y,e[9]=x^~b&m,e[18]=C^~M&E,e[19]=B^~k&S,e[28]=H^~N&R,e[29]=z^~P&O,e[38]=Y^~K&G,e[39]=Q^~V&W,e[48]=ue^~ee&re,e[49]=ce^~te&ne,e[0]^=fe[n],e[1]^=fe[n+1]},n=function(a){return function(e){var t,r,n;if("0x"===e.slice(0,2)){t=[];for(var i=2,o=e.length;i>2]|=t[c]<>2]|=r<>2]|=(192|r>>6)<>2]|=(224|r>>12)<>2]|=(240|r>>18)<>2]|=(128|r>>12&63)<>2]|=(128|r>>6&63)<>2]|=(128|63&r)<>2]|=b[3&d],e.lastByteIndex===o)for(i[0]=i[a],d=1;d>4&15]+p[15&f]+p[f>>12&15]+p[f>>8&15]+p[f>>20&15]+p[f>>16&15]+p[f>>28&15]+p[f>>24&15];l%a==0&&(m(u),d=0)}return"0x"+h}({blocks:[],reset:!0,block:0,start:0,blockCount:1600-((r=a)<<1)>>5,outputBlocks:r>>5,s:(n=[0,0,0,0,0,0,0,0,0,0],[].concat(n,n,n,n,n))},t)}};t.exports={keccak256:n(256),keccak512:n(512),keccak256s:n(256),keccak512s:n(512)}},{}],175:[function(e,t,r){var n=e("is-function");t.exports=function(e,t,r){if(!n(t))throw new TypeError("iterator must be a function");arguments.length<3&&(r=this);"[object Array]"===i.call(e)?function(e,t,r){for(var n=0,i=e.length;n":">",'"':""","'":"'","`":"`"},B=p.invert(C),N=function(t){var r=function(e){return t[e]},e="(?:"+p.keys(t).join("|")+")",n=RegExp(e),i=RegExp(e,"g");return function(e){return e=null==e?"":""+e,n.test(e)?e.replace(i,r):e}};p.escape=N(C),p.unescape=N(B),p.result=function(e,t,r){var n=null==e?void 0:e[t];return void 0===n&&(n=r),p.isFunction(n)?n.call(e):n};var P=0;p.uniqueId=function(e){var t=++P+"";return e?e+t:t},p.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var R=/(.)^/,O={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},L=/\\|'|\r|\n|\u2028|\u2029/g,q=function(e){return"\\"+O[e]};p.template=function(o,e,t){!e&&t&&(e=t),e=p.defaults({},e,p.templateSettings);var r=RegExp([(e.escape||R).source,(e.interpolate||R).source,(e.evaluate||R).source].join("|")+"|$","g"),a=0,s="__p+='";o.replace(r,function(e,t,r,n,i){return s+=o.slice(a,i).replace(L,q),a=i+e.length,t?s+="'+\n((__t=("+t+"))==null?'':_.escape(__t))+\n'":r?s+="'+\n((__t=("+r+"))==null?'':__t)+\n'":n&&(s+="';\n"+n+"\n__p+='"),e}),s+="';\n",e.variable||(s="with(obj||{}){\n"+s+"}\n"),s="var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};\n"+s+"return __p;\n";try{var n=new Function(e.variable||"obj","_",s)}catch(e){throw e.source=s,e}var i=function(e){return n.call(this,e,p)},u=e.variable||"obj";return i.source="function("+u+"){\n"+s+"}",i},p.chain=function(e){var t=p(e);return t._chain=!0,t};var D=function(e,t){return e._chain?p(t).chain():t};p.mixin=function(r){p.each(p.functions(r),function(e){var t=p[e]=r[e];p.prototype[e]=function(){var e=[this._wrapped];return i.apply(e,arguments),D(this,t.apply(p,e))}})},p.mixin(p),p.each(["pop","push","reverse","shift","sort","splice","unshift"],function(t){var r=n[t];p.prototype[t]=function(){var e=this._wrapped;return r.apply(e,arguments),"shift"!==t&&"splice"!==t||0!==e.length||delete e[0],D(this,e)}}),p.each(["concat","join","slice"],function(e){var t=n[e];p.prototype[e]=function(){return D(this,t.apply(this._wrapped,arguments))}}),p.prototype.value=function(){return this._wrapped},p.prototype.valueOf=p.prototype.toJSON=p.prototype.value,p.prototype.toString=function(){return""+this._wrapped},"function"==typeof define&&define.amd&&define("underscore",[],function(){return p})}).call(this)},{}],188:[function(e,t,r){t.exports=function(e,t){if(t){t=(t=t.trim().replace(/^(\?|#|&)/,""))?"?"+t:t;var r=e.split(/[\?\#]/),n=r[0];t&&/\:\/\/[^\/]*$/.test(n)&&(n+="/");var i=e.match(/(\#.*)$/);e=n+t,i&&(e+=i[0])}return e}},{}],189:[function(e,t,r){var i=e("xhr-request");t.exports=function(e,t){return new Promise(function(r,n){i(e,t,function(e,t){e?n(e):r(t)})})}},{"xhr-request":190}],190:[function(e,t,r){var s=e("query-string"),u=e("url-set-query"),c=e("object-assign"),f=e("./lib/ensure-header.js"),h=e("./lib/request.js"),d="application/json",l=function(){};t.exports=function(e,t,r){if(!e||"string"!=typeof e)throw new TypeError("must specify a URL");"function"==typeof t&&(r=t,t={});if(r&&"function"!=typeof r)throw new TypeError("expected cb to be undefined or a function");r=r||l;var n=(t=t||{}).json?"json":"text",i=(t=c({responseType:n},t)).headers||{},o=(t.method||"GET").toUpperCase(),a=t.query;a&&("string"!=typeof a&&(a=s.stringify(a)),e=u(e,a));"json"===t.responseType&&f(i,"Accept",d);t.json&&"GET"!==o&&"HEAD"!==o&&(f(i,"Content-Type",d),t.body=JSON.stringify(t.body));return t.method=o,t.url=e,t.headers=i,delete t.query,delete t.json,h(t,r)}},{"./lib/ensure-header.js":191,"./lib/request.js":193,"object-assign":178,"query-string":180,"url-set-query":188}],191:[function(e,t,r){t.exports=function(e,t,r){var n=t.toLowerCase();e[t]||e[n]||(e[t]=r)}},{}],192:[function(e,t,r){t.exports=function(e,t){return t?{statusCode:t.statusCode,headers:t.headers,method:e.method,url:e.url,rawRequest:t.rawRequest?t.rawRequest:t}:null}},{}],193:[function(e,t,r){var n=e("xhr"),s=e("./normalize-response"),u=function(){};t.exports=function(i,o){delete i.uri;var a=!1;"json"===i.responseType&&(i.responseType="text",a=!0);var t=n(i,function(t,e,r){if(a&&!t)try{var n=e.rawRequest.responseText;r=JSON.parse(n)}catch(e){t=e}e=s(i,e),o(t,t?null:r,e),o=u}),r=t.onabort;return t.onabort=function(){var e=r.apply(t,Array.prototype.slice.call(arguments));return o(new Error("XHR Aborted")),o=u,e},t}},{"./normalize-response":192,xhr:194}],194:[function(e,t,r){var n=e("global/window"),i=e("is-function"),y=e("parse-headers"),o=e("xtend");function a(e,t,r){var n=e;return i(t)?(r=t,"string"==typeof e&&(n={uri:e})):n=o(t,{uri:e}),n.callback=r,n}function m(e,t,r){return s(t=a(e,t,r))}function s(n){if(void 0===n.callback)throw new Error("callback argument missing");var i=!1,o=function(e,t,r){i||(i=!0,n.callback(e,t,r))};function t(e){return clearTimeout(u),e instanceof Error||(e=new Error(""+(e||"Unknown XMLHttpRequest Error"))),e.statusCode=0,o(e,b)}function e(){if(!a){var e;clearTimeout(u),e=n.useXDR&&void 0===s.status?200:1223===s.status?204:s.status;var t=b,r=null;return 0!==e?(t={body:function(){var e=void 0;if(e=s.response?s.response:s.responseText||function(e){try{if("document"===e.responseType)return e.responseXML;var t=e.responseXML&&"parsererror"===e.responseXML.documentElement.nodeName;if(""===e.responseType&&!t)return e.responseXML}catch(e){}return null}(s),p)try{e=JSON.parse(e)}catch(e){}return e}(),statusCode:e,method:f,headers:{},url:c,rawRequest:s},s.getAllResponseHeaders&&(t.headers=y(s.getAllResponseHeaders()))):r=new Error("Internal XMLHttpRequest Error"),o(r,t,t.body)}}var r,a,s=n.xhr||null;s||(s=n.cors||n.useXDR?new m.XDomainRequest:new m.XMLHttpRequest);var u,c=s.url=n.uri||n.url,f=s.method=n.method||"GET",h=n.body||n.data,d=s.headers=n.headers||{},l=!!n.sync,p=!1,b={body:void 0,headers:{},statusCode:0,method:f,url:c,rawRequest:s};if("json"in n&&!1!==n.json&&(p=!0,d.accept||d.Accept||(d.Accept="application/json"),"GET"!==f&&"HEAD"!==f&&(d["content-type"]||d["Content-Type"]||(d["Content-Type"]="application/json"),h=JSON.stringify(!0===n.json?h:n.json))),s.onreadystatechange=function(){4===s.readyState&&setTimeout(e,0)},s.onload=e,s.onerror=t,s.onprogress=function(){},s.onabort=function(){a=!0},s.ontimeout=t,s.open(f,c,!l,n.username,n.password),l||(s.withCredentials=!!n.withCredentials),!l&&0>>26-a&67108863,26<=(a+=24)&&(a-=26,i++);else if("le"===r)for(i=n=0;n>>26-a&67108863,26<=(a+=24)&&(a-=26,i++);return this.strip()},m.prototype._parseHex=function(e,t){this.length=Math.ceil((e.length-t)/6),this.words=new Array(this.length);for(var r=0;r>>26-o&4194303,26<=(o+=24)&&(o-=26,n++);r+6!==t&&(i=a(e,t,r+6),this.words[n]|=i<>>26-o&4194303),this.strip()},m.prototype._parseBase=function(e,t,r){this.words=[0];for(var n=0,i=this.length=1;i<=67108863;i*=t)n++;n--,i=i/t|0;for(var o=e.length-r,a=o%n,s=Math.min(o,o-a)+r,u=0,c=r;c"};var d=["","0","00","000","0000","00000","000000","0000000","00000000","000000000","0000000000","00000000000","000000000000","0000000000000","00000000000000","000000000000000","0000000000000000","00000000000000000","000000000000000000","0000000000000000000","00000000000000000000","000000000000000000000","0000000000000000000000","00000000000000000000000","000000000000000000000000","0000000000000000000000000"],l=[0,0,25,16,12,11,10,9,8,8,7,7,7,7,6,6,6,6,6,6,6,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5],p=[0,0,33554432,43046721,16777216,48828125,60466176,40353607,16777216,43046721,1e7,19487171,35831808,62748517,7529536,11390625,16777216,24137569,34012224,47045881,64e6,4084101,5153632,6436343,7962624,9765625,11881376,14348907,17210368,20511149,243e5,28629151,33554432,39135393,45435424,52521875,60466176];function i(e,t,r){r.negative=t.negative^e.negative;var n=e.length+t.length|0;n=(r.length=n)-1|0;var i=0|e.words[0],o=0|t.words[0],a=i*o,s=67108863&a,u=a/67108864|0;r.words[0]=s;for(var c=1;c>>26,h=67108863&u,d=Math.min(c,t.length-1),l=Math.max(0,c-e.length+1);l<=d;l++){var p=c-l|0;f+=(a=(i=0|e.words[p])*(o=0|t.words[l])+h)/67108864|0,h=67108863&a}r.words[c]=0|h,u=0|f}return 0!==u?r.words[c]=0|u:r.length--,r.strip()}m.prototype.toString=function(e,t){var r;if(t=0|t||1,16===(e=e||10)||"hex"===e){r="";for(var n=0,i=0,o=0;o>>24-n&16777215)||o!==this.length-1?d[6-s.length]+s+r:s+r,26<=(n+=2)&&(n-=26,o--)}for(0!==i&&(r=i.toString(16)+r);r.length%t!=0;)r="0"+r;return 0!==this.negative&&(r="-"+r),r}if(e===(0|e)&&2<=e&&e<=36){var u=l[e],c=p[e];r="";var f=this.clone();for(f.negative=0;!f.isZero();){var h=f.modn(c).toString(e);r=(f=f.idivn(c)).isZero()?h+r:d[u-h.length]+h+r}for(this.isZero()&&(r="0"+r);r.length%t!=0;)r="0"+r;return 0!==this.negative&&(r="-"+r),r}y(!1,"Base should be between 2 and 36")},m.prototype.toNumber=function(){var e=this.words[0];return 2===this.length?e+=67108864*this.words[1]:3===this.length&&1===this.words[2]?e+=4503599627370496+67108864*this.words[1]:2>>=13),64<=t&&(r+=7,t>>>=7),8<=t&&(r+=4,t>>>=4),2<=t&&(r+=2,t>>>=2),r+t},m.prototype._zeroBits=function(e){if(0===e)return 26;var t=e,r=0;return 0==(8191&t)&&(r+=13,t>>>=13),0==(127&t)&&(r+=7,t>>>=7),0==(15&t)&&(r+=4,t>>>=4),0==(3&t)&&(r+=2,t>>>=2),0==(1&t)&&r++,r},m.prototype.bitLength=function(){var e=this.words[this.length-1],t=this._countBits(e);return 26*(this.length-1)+t},m.prototype.zeroBits=function(){if(this.isZero())return 0;for(var e=0,t=0;te.length?this.clone().ior(e):e.clone().ior(this)},m.prototype.uor=function(e){return this.length>e.length?this.clone().iuor(e):e.clone().iuor(this)},m.prototype.iuand=function(e){var t;t=this.length>e.length?e:this;for(var r=0;re.length?this.clone().iand(e):e.clone().iand(this)},m.prototype.uand=function(e){return this.length>e.length?this.clone().iuand(e):e.clone().iuand(this)},m.prototype.iuxor=function(e){var t,r;this.length>e.length?(t=this,r=e):(t=e,r=this);for(var n=0;ne.length?this.clone().ixor(e):e.clone().ixor(this)},m.prototype.uxor=function(e){return this.length>e.length?this.clone().iuxor(e):e.clone().iuxor(this)},m.prototype.inotn=function(e){y("number"==typeof e&&0<=e);var t=0|Math.ceil(e/26),r=e%26;this._expand(t),0>26-r),this.strip()},m.prototype.notn=function(e){return this.clone().inotn(e)},m.prototype.setn=function(e,t){y("number"==typeof e&&0<=e);var r=e/26|0,n=e%26;return this._expand(r+1),this.words[r]=t?this.words[r]|1<e.length?(r=this,n=e):(r=e,n=this);for(var i=0,o=0;o>>26;for(;0!==i&&o>>26;if(this.length=r.length,0!==i)this.words[this.length]=i,this.length++;else if(r!==this)for(;oe.length?this.clone().iadd(e):e.clone().iadd(this)},m.prototype.isub=function(e){if(0!==e.negative){e.negative=0;var t=this.iadd(e);return e.negative=1,t._normSign()}if(0!==this.negative)return this.negative=0,this.iadd(e),this.negative=1,this._normSign();var r,n,i=this.cmp(e);if(0===i)return this.negative=0,this.length=1,this.words[0]=0,this;0>26,this.words[a]=67108863&t;for(;0!==o&&a>26,this.words[a]=67108863&t;if(0===o&&a>>13,l=0|a[1],p=8191&l,b=l>>>13,y=0|a[2],m=8191&y,v=y>>>13,g=0|a[3],w=8191&g,_=g>>>13,A=0|a[4],x=8191&A,M=A>>>13,k=0|a[5],E=8191&k,S=k>>>13,U=0|a[6],j=8191&U,I=U>>>13,T=0|a[7],C=8191&T,B=T>>>13,N=0|a[8],P=8191&N,R=N>>>13,O=0|a[9],L=8191&O,q=O>>>13,D=0|s[0],F=8191&D,H=D>>>13,z=0|s[1],K=8191&z,V=z>>>13,G=0|s[2],W=8191&G,X=G>>>13,J=0|s[3],Z=8191&J,$=J>>>13,Y=0|s[4],Q=8191&Y,ee=Y>>>13,te=0|s[5],re=8191&te,ne=te>>>13,ie=0|s[6],oe=8191&ie,ae=ie>>>13,se=0|s[7],ue=8191&se,ce=se>>>13,fe=0|s[8],he=8191&fe,de=fe>>>13,le=0|s[9],pe=8191&le,be=le>>>13;r.negative=e.negative^t.negative,r.length=19;var ye=(c+(n=Math.imul(h,F))|0)+((8191&(i=(i=Math.imul(h,H))+Math.imul(d,F)|0))<<13)|0;c=((o=Math.imul(d,H))+(i>>>13)|0)+(ye>>>26)|0,ye&=67108863,n=Math.imul(p,F),i=(i=Math.imul(p,H))+Math.imul(b,F)|0,o=Math.imul(b,H);var me=(c+(n=n+Math.imul(h,K)|0)|0)+((8191&(i=(i=i+Math.imul(h,V)|0)+Math.imul(d,K)|0))<<13)|0;c=((o=o+Math.imul(d,V)|0)+(i>>>13)|0)+(me>>>26)|0,me&=67108863,n=Math.imul(m,F),i=(i=Math.imul(m,H))+Math.imul(v,F)|0,o=Math.imul(v,H),n=n+Math.imul(p,K)|0,i=(i=i+Math.imul(p,V)|0)+Math.imul(b,K)|0,o=o+Math.imul(b,V)|0;var ve=(c+(n=n+Math.imul(h,W)|0)|0)+((8191&(i=(i=i+Math.imul(h,X)|0)+Math.imul(d,W)|0))<<13)|0;c=((o=o+Math.imul(d,X)|0)+(i>>>13)|0)+(ve>>>26)|0,ve&=67108863,n=Math.imul(w,F),i=(i=Math.imul(w,H))+Math.imul(_,F)|0,o=Math.imul(_,H),n=n+Math.imul(m,K)|0,i=(i=i+Math.imul(m,V)|0)+Math.imul(v,K)|0,o=o+Math.imul(v,V)|0,n=n+Math.imul(p,W)|0,i=(i=i+Math.imul(p,X)|0)+Math.imul(b,W)|0,o=o+Math.imul(b,X)|0;var ge=(c+(n=n+Math.imul(h,Z)|0)|0)+((8191&(i=(i=i+Math.imul(h,$)|0)+Math.imul(d,Z)|0))<<13)|0;c=((o=o+Math.imul(d,$)|0)+(i>>>13)|0)+(ge>>>26)|0,ge&=67108863,n=Math.imul(x,F),i=(i=Math.imul(x,H))+Math.imul(M,F)|0,o=Math.imul(M,H),n=n+Math.imul(w,K)|0,i=(i=i+Math.imul(w,V)|0)+Math.imul(_,K)|0,o=o+Math.imul(_,V)|0,n=n+Math.imul(m,W)|0,i=(i=i+Math.imul(m,X)|0)+Math.imul(v,W)|0,o=o+Math.imul(v,X)|0,n=n+Math.imul(p,Z)|0,i=(i=i+Math.imul(p,$)|0)+Math.imul(b,Z)|0,o=o+Math.imul(b,$)|0;var we=(c+(n=n+Math.imul(h,Q)|0)|0)+((8191&(i=(i=i+Math.imul(h,ee)|0)+Math.imul(d,Q)|0))<<13)|0;c=((o=o+Math.imul(d,ee)|0)+(i>>>13)|0)+(we>>>26)|0,we&=67108863,n=Math.imul(E,F),i=(i=Math.imul(E,H))+Math.imul(S,F)|0,o=Math.imul(S,H),n=n+Math.imul(x,K)|0,i=(i=i+Math.imul(x,V)|0)+Math.imul(M,K)|0,o=o+Math.imul(M,V)|0,n=n+Math.imul(w,W)|0,i=(i=i+Math.imul(w,X)|0)+Math.imul(_,W)|0,o=o+Math.imul(_,X)|0,n=n+Math.imul(m,Z)|0,i=(i=i+Math.imul(m,$)|0)+Math.imul(v,Z)|0,o=o+Math.imul(v,$)|0,n=n+Math.imul(p,Q)|0,i=(i=i+Math.imul(p,ee)|0)+Math.imul(b,Q)|0,o=o+Math.imul(b,ee)|0;var _e=(c+(n=n+Math.imul(h,re)|0)|0)+((8191&(i=(i=i+Math.imul(h,ne)|0)+Math.imul(d,re)|0))<<13)|0;c=((o=o+Math.imul(d,ne)|0)+(i>>>13)|0)+(_e>>>26)|0,_e&=67108863,n=Math.imul(j,F),i=(i=Math.imul(j,H))+Math.imul(I,F)|0,o=Math.imul(I,H),n=n+Math.imul(E,K)|0,i=(i=i+Math.imul(E,V)|0)+Math.imul(S,K)|0,o=o+Math.imul(S,V)|0,n=n+Math.imul(x,W)|0,i=(i=i+Math.imul(x,X)|0)+Math.imul(M,W)|0,o=o+Math.imul(M,X)|0,n=n+Math.imul(w,Z)|0,i=(i=i+Math.imul(w,$)|0)+Math.imul(_,Z)|0,o=o+Math.imul(_,$)|0,n=n+Math.imul(m,Q)|0,i=(i=i+Math.imul(m,ee)|0)+Math.imul(v,Q)|0,o=o+Math.imul(v,ee)|0,n=n+Math.imul(p,re)|0,i=(i=i+Math.imul(p,ne)|0)+Math.imul(b,re)|0,o=o+Math.imul(b,ne)|0;var Ae=(c+(n=n+Math.imul(h,oe)|0)|0)+((8191&(i=(i=i+Math.imul(h,ae)|0)+Math.imul(d,oe)|0))<<13)|0;c=((o=o+Math.imul(d,ae)|0)+(i>>>13)|0)+(Ae>>>26)|0,Ae&=67108863,n=Math.imul(C,F),i=(i=Math.imul(C,H))+Math.imul(B,F)|0,o=Math.imul(B,H),n=n+Math.imul(j,K)|0,i=(i=i+Math.imul(j,V)|0)+Math.imul(I,K)|0,o=o+Math.imul(I,V)|0,n=n+Math.imul(E,W)|0,i=(i=i+Math.imul(E,X)|0)+Math.imul(S,W)|0,o=o+Math.imul(S,X)|0,n=n+Math.imul(x,Z)|0,i=(i=i+Math.imul(x,$)|0)+Math.imul(M,Z)|0,o=o+Math.imul(M,$)|0,n=n+Math.imul(w,Q)|0,i=(i=i+Math.imul(w,ee)|0)+Math.imul(_,Q)|0,o=o+Math.imul(_,ee)|0,n=n+Math.imul(m,re)|0,i=(i=i+Math.imul(m,ne)|0)+Math.imul(v,re)|0,o=o+Math.imul(v,ne)|0,n=n+Math.imul(p,oe)|0,i=(i=i+Math.imul(p,ae)|0)+Math.imul(b,oe)|0,o=o+Math.imul(b,ae)|0;var xe=(c+(n=n+Math.imul(h,ue)|0)|0)+((8191&(i=(i=i+Math.imul(h,ce)|0)+Math.imul(d,ue)|0))<<13)|0;c=((o=o+Math.imul(d,ce)|0)+(i>>>13)|0)+(xe>>>26)|0,xe&=67108863,n=Math.imul(P,F),i=(i=Math.imul(P,H))+Math.imul(R,F)|0,o=Math.imul(R,H),n=n+Math.imul(C,K)|0,i=(i=i+Math.imul(C,V)|0)+Math.imul(B,K)|0,o=o+Math.imul(B,V)|0,n=n+Math.imul(j,W)|0,i=(i=i+Math.imul(j,X)|0)+Math.imul(I,W)|0,o=o+Math.imul(I,X)|0,n=n+Math.imul(E,Z)|0,i=(i=i+Math.imul(E,$)|0)+Math.imul(S,Z)|0,o=o+Math.imul(S,$)|0,n=n+Math.imul(x,Q)|0,i=(i=i+Math.imul(x,ee)|0)+Math.imul(M,Q)|0,o=o+Math.imul(M,ee)|0,n=n+Math.imul(w,re)|0,i=(i=i+Math.imul(w,ne)|0)+Math.imul(_,re)|0,o=o+Math.imul(_,ne)|0,n=n+Math.imul(m,oe)|0,i=(i=i+Math.imul(m,ae)|0)+Math.imul(v,oe)|0,o=o+Math.imul(v,ae)|0,n=n+Math.imul(p,ue)|0,i=(i=i+Math.imul(p,ce)|0)+Math.imul(b,ue)|0,o=o+Math.imul(b,ce)|0;var Me=(c+(n=n+Math.imul(h,he)|0)|0)+((8191&(i=(i=i+Math.imul(h,de)|0)+Math.imul(d,he)|0))<<13)|0;c=((o=o+Math.imul(d,de)|0)+(i>>>13)|0)+(Me>>>26)|0,Me&=67108863,n=Math.imul(L,F),i=(i=Math.imul(L,H))+Math.imul(q,F)|0,o=Math.imul(q,H),n=n+Math.imul(P,K)|0,i=(i=i+Math.imul(P,V)|0)+Math.imul(R,K)|0,o=o+Math.imul(R,V)|0,n=n+Math.imul(C,W)|0,i=(i=i+Math.imul(C,X)|0)+Math.imul(B,W)|0,o=o+Math.imul(B,X)|0,n=n+Math.imul(j,Z)|0,i=(i=i+Math.imul(j,$)|0)+Math.imul(I,Z)|0,o=o+Math.imul(I,$)|0,n=n+Math.imul(E,Q)|0,i=(i=i+Math.imul(E,ee)|0)+Math.imul(S,Q)|0,o=o+Math.imul(S,ee)|0,n=n+Math.imul(x,re)|0,i=(i=i+Math.imul(x,ne)|0)+Math.imul(M,re)|0,o=o+Math.imul(M,ne)|0,n=n+Math.imul(w,oe)|0,i=(i=i+Math.imul(w,ae)|0)+Math.imul(_,oe)|0,o=o+Math.imul(_,ae)|0,n=n+Math.imul(m,ue)|0,i=(i=i+Math.imul(m,ce)|0)+Math.imul(v,ue)|0,o=o+Math.imul(v,ce)|0,n=n+Math.imul(p,he)|0,i=(i=i+Math.imul(p,de)|0)+Math.imul(b,he)|0,o=o+Math.imul(b,de)|0;var ke=(c+(n=n+Math.imul(h,pe)|0)|0)+((8191&(i=(i=i+Math.imul(h,be)|0)+Math.imul(d,pe)|0))<<13)|0;c=((o=o+Math.imul(d,be)|0)+(i>>>13)|0)+(ke>>>26)|0,ke&=67108863,n=Math.imul(L,K),i=(i=Math.imul(L,V))+Math.imul(q,K)|0,o=Math.imul(q,V),n=n+Math.imul(P,W)|0,i=(i=i+Math.imul(P,X)|0)+Math.imul(R,W)|0,o=o+Math.imul(R,X)|0,n=n+Math.imul(C,Z)|0,i=(i=i+Math.imul(C,$)|0)+Math.imul(B,Z)|0,o=o+Math.imul(B,$)|0,n=n+Math.imul(j,Q)|0,i=(i=i+Math.imul(j,ee)|0)+Math.imul(I,Q)|0,o=o+Math.imul(I,ee)|0,n=n+Math.imul(E,re)|0,i=(i=i+Math.imul(E,ne)|0)+Math.imul(S,re)|0,o=o+Math.imul(S,ne)|0,n=n+Math.imul(x,oe)|0,i=(i=i+Math.imul(x,ae)|0)+Math.imul(M,oe)|0,o=o+Math.imul(M,ae)|0,n=n+Math.imul(w,ue)|0,i=(i=i+Math.imul(w,ce)|0)+Math.imul(_,ue)|0,o=o+Math.imul(_,ce)|0,n=n+Math.imul(m,he)|0,i=(i=i+Math.imul(m,de)|0)+Math.imul(v,he)|0,o=o+Math.imul(v,de)|0;var Ee=(c+(n=n+Math.imul(p,pe)|0)|0)+((8191&(i=(i=i+Math.imul(p,be)|0)+Math.imul(b,pe)|0))<<13)|0;c=((o=o+Math.imul(b,be)|0)+(i>>>13)|0)+(Ee>>>26)|0,Ee&=67108863,n=Math.imul(L,W),i=(i=Math.imul(L,X))+Math.imul(q,W)|0,o=Math.imul(q,X),n=n+Math.imul(P,Z)|0,i=(i=i+Math.imul(P,$)|0)+Math.imul(R,Z)|0,o=o+Math.imul(R,$)|0,n=n+Math.imul(C,Q)|0,i=(i=i+Math.imul(C,ee)|0)+Math.imul(B,Q)|0,o=o+Math.imul(B,ee)|0,n=n+Math.imul(j,re)|0,i=(i=i+Math.imul(j,ne)|0)+Math.imul(I,re)|0,o=o+Math.imul(I,ne)|0,n=n+Math.imul(E,oe)|0,i=(i=i+Math.imul(E,ae)|0)+Math.imul(S,oe)|0,o=o+Math.imul(S,ae)|0,n=n+Math.imul(x,ue)|0,i=(i=i+Math.imul(x,ce)|0)+Math.imul(M,ue)|0,o=o+Math.imul(M,ce)|0,n=n+Math.imul(w,he)|0,i=(i=i+Math.imul(w,de)|0)+Math.imul(_,he)|0,o=o+Math.imul(_,de)|0;var Se=(c+(n=n+Math.imul(m,pe)|0)|0)+((8191&(i=(i=i+Math.imul(m,be)|0)+Math.imul(v,pe)|0))<<13)|0;c=((o=o+Math.imul(v,be)|0)+(i>>>13)|0)+(Se>>>26)|0,Se&=67108863,n=Math.imul(L,Z),i=(i=Math.imul(L,$))+Math.imul(q,Z)|0,o=Math.imul(q,$),n=n+Math.imul(P,Q)|0,i=(i=i+Math.imul(P,ee)|0)+Math.imul(R,Q)|0,o=o+Math.imul(R,ee)|0,n=n+Math.imul(C,re)|0,i=(i=i+Math.imul(C,ne)|0)+Math.imul(B,re)|0,o=o+Math.imul(B,ne)|0,n=n+Math.imul(j,oe)|0,i=(i=i+Math.imul(j,ae)|0)+Math.imul(I,oe)|0,o=o+Math.imul(I,ae)|0,n=n+Math.imul(E,ue)|0,i=(i=i+Math.imul(E,ce)|0)+Math.imul(S,ue)|0,o=o+Math.imul(S,ce)|0,n=n+Math.imul(x,he)|0,i=(i=i+Math.imul(x,de)|0)+Math.imul(M,he)|0,o=o+Math.imul(M,de)|0;var Ue=(c+(n=n+Math.imul(w,pe)|0)|0)+((8191&(i=(i=i+Math.imul(w,be)|0)+Math.imul(_,pe)|0))<<13)|0;c=((o=o+Math.imul(_,be)|0)+(i>>>13)|0)+(Ue>>>26)|0,Ue&=67108863,n=Math.imul(L,Q),i=(i=Math.imul(L,ee))+Math.imul(q,Q)|0,o=Math.imul(q,ee),n=n+Math.imul(P,re)|0,i=(i=i+Math.imul(P,ne)|0)+Math.imul(R,re)|0,o=o+Math.imul(R,ne)|0,n=n+Math.imul(C,oe)|0,i=(i=i+Math.imul(C,ae)|0)+Math.imul(B,oe)|0,o=o+Math.imul(B,ae)|0,n=n+Math.imul(j,ue)|0,i=(i=i+Math.imul(j,ce)|0)+Math.imul(I,ue)|0,o=o+Math.imul(I,ce)|0,n=n+Math.imul(E,he)|0,i=(i=i+Math.imul(E,de)|0)+Math.imul(S,he)|0,o=o+Math.imul(S,de)|0;var je=(c+(n=n+Math.imul(x,pe)|0)|0)+((8191&(i=(i=i+Math.imul(x,be)|0)+Math.imul(M,pe)|0))<<13)|0;c=((o=o+Math.imul(M,be)|0)+(i>>>13)|0)+(je>>>26)|0,je&=67108863,n=Math.imul(L,re),i=(i=Math.imul(L,ne))+Math.imul(q,re)|0,o=Math.imul(q,ne),n=n+Math.imul(P,oe)|0,i=(i=i+Math.imul(P,ae)|0)+Math.imul(R,oe)|0,o=o+Math.imul(R,ae)|0,n=n+Math.imul(C,ue)|0,i=(i=i+Math.imul(C,ce)|0)+Math.imul(B,ue)|0,o=o+Math.imul(B,ce)|0,n=n+Math.imul(j,he)|0,i=(i=i+Math.imul(j,de)|0)+Math.imul(I,he)|0,o=o+Math.imul(I,de)|0;var Ie=(c+(n=n+Math.imul(E,pe)|0)|0)+((8191&(i=(i=i+Math.imul(E,be)|0)+Math.imul(S,pe)|0))<<13)|0;c=((o=o+Math.imul(S,be)|0)+(i>>>13)|0)+(Ie>>>26)|0,Ie&=67108863,n=Math.imul(L,oe),i=(i=Math.imul(L,ae))+Math.imul(q,oe)|0,o=Math.imul(q,ae),n=n+Math.imul(P,ue)|0,i=(i=i+Math.imul(P,ce)|0)+Math.imul(R,ue)|0,o=o+Math.imul(R,ce)|0,n=n+Math.imul(C,he)|0,i=(i=i+Math.imul(C,de)|0)+Math.imul(B,he)|0,o=o+Math.imul(B,de)|0;var Te=(c+(n=n+Math.imul(j,pe)|0)|0)+((8191&(i=(i=i+Math.imul(j,be)|0)+Math.imul(I,pe)|0))<<13)|0;c=((o=o+Math.imul(I,be)|0)+(i>>>13)|0)+(Te>>>26)|0,Te&=67108863,n=Math.imul(L,ue),i=(i=Math.imul(L,ce))+Math.imul(q,ue)|0,o=Math.imul(q,ce),n=n+Math.imul(P,he)|0,i=(i=i+Math.imul(P,de)|0)+Math.imul(R,he)|0,o=o+Math.imul(R,de)|0;var Ce=(c+(n=n+Math.imul(C,pe)|0)|0)+((8191&(i=(i=i+Math.imul(C,be)|0)+Math.imul(B,pe)|0))<<13)|0;c=((o=o+Math.imul(B,be)|0)+(i>>>13)|0)+(Ce>>>26)|0,Ce&=67108863,n=Math.imul(L,he),i=(i=Math.imul(L,de))+Math.imul(q,he)|0,o=Math.imul(q,de);var Be=(c+(n=n+Math.imul(P,pe)|0)|0)+((8191&(i=(i=i+Math.imul(P,be)|0)+Math.imul(R,pe)|0))<<13)|0;c=((o=o+Math.imul(R,be)|0)+(i>>>13)|0)+(Be>>>26)|0,Be&=67108863;var Ne=(c+(n=Math.imul(L,pe))|0)+((8191&(i=(i=Math.imul(L,be))+Math.imul(q,pe)|0))<<13)|0;return c=((o=Math.imul(q,be))+(i>>>13)|0)+(Ne>>>26)|0,Ne&=67108863,u[0]=ye,u[1]=me,u[2]=ve,u[3]=ge,u[4]=we,u[5]=_e,u[6]=Ae,u[7]=xe,u[8]=Me,u[9]=ke,u[10]=Ee,u[11]=Se,u[12]=Ue,u[13]=je,u[14]=Ie,u[15]=Te,u[16]=Ce,u[17]=Be,u[18]=Ne,0!==c&&(u[19]=c,r.length++),r};function s(e,t,r){return(new u).mulp(e,t,r)}function u(e,t){this.x=e,this.y=t}Math.imul||(o=i),m.prototype.mulTo=function(e,t){var r=this.length+e.length;return 10===this.length&&10===e.length?o(this,e,t):r<63?i(this,e,t):r<1024?function(e,t,r){r.negative=t.negative^e.negative,r.length=e.length+t.length;for(var n=0,i=0,o=0;o>>26)|0)>>>26,a&=67108863}r.words[o]=s,n=a,a=i}return 0!==n?r.words[o]=n:r.length--,r.strip()}(this,e,t):s(this,e,t)},u.prototype.makeRBT=function(e){for(var t=new Array(e),r=m.prototype._countBits(e)-1,n=0;n>=1;return n},u.prototype.permute=function(e,t,r,n,i,o){for(var a=0;a>>=1)i++;return 1<>>=13,r[2*o+1]=8191&i,i>>>=13;for(o=2*t;o>=26,t+=n/67108864|0,t+=i>>>26,this.words[r]=67108863&i}return 0!==t&&(this.words[r]=t,this.length++),this},m.prototype.muln=function(e){return this.clone().imuln(e)},m.prototype.sqr=function(){return this.mul(this)},m.prototype.isqr=function(){return this.imul(this.clone())},m.prototype.pow=function(e){var t=function(e){for(var t=new Array(e.bitLength()),r=0;r>>i}return t}(e);if(0===t.length)return new m(1);for(var r=this,n=0;n>>26-r<<26-r;if(0!==r){var o=0;for(t=0;t>>26-r}o&&(this.words[t]=o,this.length++)}if(0!==n){for(t=this.length-1;0<=t;t--)this.words[t+n]=this.words[t];for(t=0;t>>i<o)for(this.length-=o,u=0;u>>i,c=f&a}return s&&0!==c&&(s.words[s.length++]=c),0===this.length&&(this.words[0]=0,this.length=1),this.strip()},m.prototype.ishrn=function(e,t,r){return y(0===this.negative),this.iushrn(e,t,r)},m.prototype.shln=function(e){return this.clone().ishln(e)},m.prototype.ushln=function(e){return this.clone().iushln(e)},m.prototype.shrn=function(e){return this.clone().ishrn(e)},m.prototype.ushrn=function(e){return this.clone().iushrn(e)},m.prototype.testn=function(e){y("number"==typeof e&&0<=e);var t=e%26,r=(e-t)/26,n=1<>>t<>26)-(s/67108864|0),this.words[n+r]=67108863&i}for(;n>26,this.words[n+r]=67108863&i;if(0===a)return this.strip();for(y(-1===a),n=a=0;n>26,this.words[n]=67108863&i;return this.negative=1,this.strip()},m.prototype._wordDiv=function(e,t){var r=(this.length,e.length),n=this.clone(),i=e,o=0|i.words[i.length-1];0!==(r=26-this._countBits(o))&&(i=i.ushln(r),n.iushln(r),o=0|i.words[i.length-1]);var a,s=n.length-i.length;if("mod"!==t){(a=new m(null)).length=s+1,a.words=new Array(a.length);for(var u=0;uthis.length||this.cmp(e)<0?{div:new m(0),mod:this}:1===e.length?"div"===t?{div:this.divn(e.words[0]),mod:null}:"mod"===t?{div:null,mod:new m(this.modn(e.words[0]))}:{div:this.divn(e.words[0]),mod:new m(this.modn(e.words[0]))}:this._wordDiv(e,t);var n,i,o},m.prototype.div=function(e){return this.divmod(e,"div",!1).div},m.prototype.mod=function(e){return this.divmod(e,"mod",!1).mod},m.prototype.umod=function(e){return this.divmod(e,"mod",!0).mod},m.prototype.divRound=function(e){var t=this.divmod(e);if(t.mod.isZero())return t.div;var r=0!==t.div.negative?t.mod.isub(e):t.mod,n=e.ushrn(1),i=e.andln(1),o=r.cmp(n);return o<0||1===i&&0===o?t.div:0!==t.div.negative?t.div.isubn(1):t.div.iaddn(1)},m.prototype.modn=function(e){y(e<=67108863);for(var t=(1<<26)%e,r=0,n=this.length-1;0<=n;n--)r=(t*r+(0|this.words[n]))%e;return r},m.prototype.idivn=function(e){y(e<=67108863);for(var t=0,r=this.length-1;0<=r;r--){var n=(0|this.words[r])+67108864*t;this.words[r]=n/e|0,t=n%e}return this.strip()},m.prototype.divn=function(e){return this.clone().idivn(e)},m.prototype.egcd=function(e){y(0===e.negative),y(!e.isZero());var t=this,r=e.clone();t=0!==t.negative?t.umod(e):t.clone();for(var n=new m(1),i=new m(0),o=new m(0),a=new m(1),s=0;t.isEven()&&r.isEven();)t.iushrn(1),r.iushrn(1),++s;for(var u=r.clone(),c=t.clone();!t.isZero();){for(var f=0,h=1;0==(t.words[0]&h)&&f<26;++f,h<<=1);if(0>>26,a&=67108863,this.words[o]=a}return 0!==i&&(this.words[o]=i,this.length++),this},m.prototype.isZero=function(){return 1===this.length&&0===this.words[0]},m.prototype.cmpn=function(e){var t,r=e<0;if(0!==this.negative&&!r)return-1;if(0===this.negative&&r)return 1;if(this.strip(),1e.length)return 1;if(this.lengththis.n;);var n=t>>22,i=o}i>>>=22,0===(e.words[n-10]=i)&&10>>=26,e.words[r]=i,t=n}return 0!==t&&(e.words[e.length++]=t),e},m._prime=function(e){if(c[e])return c[e];var t;if("k256"===e)t=new b;else if("p224"===e)t=new v;else if("p192"===e)t=new g;else{if("p25519"!==e)throw new Error("Unknown prime "+e);t=new w}return c[e]=t},_.prototype._verify1=function(e){y(0===e.negative,"red works only with positives"),y(e.red,"red works only with red numbers")},_.prototype._verify2=function(e,t){y(0==(e.negative|t.negative),"red works only with positives"),y(e.red&&e.red===t.red,"red works only with red numbers")},_.prototype.imod=function(e){return this.prime?this.prime.ireduce(e)._forceRed(this):e.umod(this.m)._forceRed(this)},_.prototype.neg=function(e){return e.isZero()?e.clone():this.m.sub(e)._forceRed(this)},_.prototype.add=function(e,t){this._verify2(e,t);var r=e.add(t);return 0<=r.cmp(this.m)&&r.isub(this.m),r._forceRed(this)},_.prototype.iadd=function(e,t){this._verify2(e,t);var r=e.iadd(t);return 0<=r.cmp(this.m)&&r.isub(this.m),r},_.prototype.sub=function(e,t){this._verify2(e,t);var r=e.sub(t);return r.cmpn(0)<0&&r.iadd(this.m),r._forceRed(this)},_.prototype.isub=function(e,t){this._verify2(e,t);var r=e.isub(t);return r.cmpn(0)<0&&r.iadd(this.m),r},_.prototype.shl=function(e,t){return this._verify1(e),this.imod(e.ushln(t))},_.prototype.imul=function(e,t){return this._verify2(e,t),this.imod(e.imul(t))},_.prototype.mul=function(e,t){return this._verify2(e,t),this.imod(e.mul(t))},_.prototype.isqr=function(e){return this.imul(e,e.clone())},_.prototype.sqr=function(e){return this.mul(e,e)},_.prototype.sqrt=function(e){if(e.isZero())return e.clone();var t=this.m.andln(3);if(y(t%2==1),3===t){var r=this.m.add(new m(1)).iushrn(2);return this.pow(e,r)}for(var n=this.m.subn(1),i=0;!n.isZero()&&0===n.andln(1);)i++,n.iushrn(1);y(!n.isZero());var o=new m(1).toRed(this),a=o.redNeg(),s=this.m.subn(1).iushrn(1),u=this.m.bitLength();for(u=new m(2*u*u).toRed(this);0!==this.pow(u,s).cmp(a);)u.redIAdd(a);for(var c=this.pow(u,n),f=this.pow(e,n.addn(1).iushrn(1)),h=this.pow(e,n),d=i;0!==h.cmp(o);){for(var l=h,p=0;0!==l.cmp(o);p++)l=l.redSqr();y(p>c&1;i!==r[0]&&(i=this.sqr(i)),0!==f||0!==o?(o<<=1,o|=f,(4===++a||0===n&&0===c)&&(i=this.mul(i,r[o]),o=a=0)):a=0}s=26}return i},_.prototype.convertTo=function(e){var t=e.umod(this.m);return t===e?t.clone():t},_.prototype.convertFrom=function(e){var t=e.clone();return t.red=null,t},m.mont=function(e){return new A(e)},r(A,_),A.prototype.convertTo=function(e){return this.imod(e.ushln(this.shift))},A.prototype.convertFrom=function(e){var t=this.imod(e.mul(this.rinv));return t.red=null,t},A.prototype.imul=function(e,t){if(e.isZero()||t.isZero())return e.words[0]=0,e.length=1,e;var r=e.imul(t),n=r.maskn(this.shift).mul(this.minv).imaskn(this.shift).mul(this.m),i=r.isub(n).iushrn(this.shift),o=i;return 0<=i.cmp(this.m)?o=i.isub(this.m):i.cmpn(0)<0&&(o=i.iadd(this.m)),o._forceRed(this)},A.prototype.mul=function(e,t){if(e.isZero()||t.isZero())return new m(0)._forceRed(this);var r=e.mul(t),n=r.maskn(this.shift).mul(this.minv).imaskn(this.shift).mul(this.m),i=r.isub(n).iushrn(this.shift),o=i;return 0<=i.cmp(this.m)?o=i.isub(this.m):i.cmpn(0)<0&&(o=i.iadd(this.m)),o._forceRed(this)},A.prototype.invm=function(e){return this.imod(e._invmp(this.m).mul(this.r2))._forceRed(this)}}(void 0===e||e,this)},{buffer:17}],220:[function(e,t,r){var n,i=this&&this.__extends||(n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)t.hasOwnProperty(r)&&(e[r]=t[r])},function(e,t){function r(){this.constructor=e}n(e,t),e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)}),o=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var r in e)Object.hasOwnProperty.call(e,r)&&(t[r]=e[r]);return t.default=e,t};Object.defineProperty(r,"__esModule",{value:!0});var a=e("./address"),s=e("./bignumber"),u=e("./bytes"),c=e("./utf8"),f=e("./properties"),h=o(e("./errors")),d=new RegExp(/^bytes([0-9]*)$/),l=new RegExp(/^(u?int)([0-9]*)$/),p=new RegExp(/^(.*)\[([0-9]*)\]$/);r.defaultCoerceFunc=function(e,t){var r=e.match(l);return r&&parseInt(r[2])<=48?t.toNumber():t};var b=new RegExp("^([^)(]*)\\((.*)\\)([^)(]*)$"),y=new RegExp("^[A-Za-z_][A-Za-z0-9_]*$");function m(e){return e.match(/^uint($|[^1-9])/)?e="uint256"+e.substring(4):e.match(/^int($|[^1-9])/)&&(e="int256"+e.substring(3)),e}function v(t,e){function r(e){throw new Error('unexpected character "'+t[e]+'" at position '+e+' in "'+t+'"')}for(var n={type:"",name:"",state:{allowType:!0}},i=n,o=0;oe.length)throw new Error("invalid null");return{consumed:0,value:this.coerceFunc("null",void 0)}},e}(w),x=function(a){function e(e,t,r,n){var i=this,o=(r?"int":"uint")+8*t;return(i=a.call(this,e,o,o,n,!1)||this).size=t,i.signed=r,i}return i(e,a),e.prototype.encode=function(t){try{var e=s.bigNumberify(t);return e=e.toTwos(8*this.size).maskn(8*this.size),this.signed&&(e=e.fromTwos(8*this.size).toTwos(256)),u.padZeros(u.arrayify(e),32)}catch(e){h.throwError("invalid number value",h.INVALID_ARGUMENT,{arg:this.localName,coderType:this.name,value:t})}return null},e.prototype.decode=function(e,t){e.length>1]>>4&&(t[i]=t[i].toUpperCase()),8<=(15&r[i>>1])&&(t[i+1]=t[i+1].toUpperCase());return"0x"+t.join("")}for(var f={},h=0;h<10;h++)f[String(h)]=String(h);for(h=0;h<26;h++)f[String.fromCharCode(65+h)]=String(10+h);var d,l=Math.floor((d=9007199254740991,Math.log10?Math.log10(d):Math.log(d)/Math.LN10));function p(e){e=(e=e.toUpperCase()).substring(4)+e.substring(0,2)+"00";var t="";for(e.split("").forEach(function(e){t+=f[e]});t.length>=l;){var r=t.substring(0,l);t=parseInt(r,10)%97+t.substring(r.length)}for(var n=String(98-parseInt(t,10)%97);n.length<2;)n="0"+n;return n}function b(e){var t=null;if("string"!=typeof e&&u.throwError("invalid address",u.INVALID_ARGUMENT,{arg:"address",value:e}),e.match(/^(0x)?[0-9a-fA-F]{40}$/))"0x"!==e.substring(0,2)&&(e="0x"+e),t=c(e),e.match(/([A-F].*[a-f])|([a-f].*[A-F])/)&&t!==e&&u.throwError("bad address checksum",u.INVALID_ARGUMENT,{arg:"address",value:e});else if(e.match(/^XE[0-9]{2}[0-9A-Za-z]{30,31}$/)){for(e.substring(2,4)!==p(e)&&u.throwError("bad icap checksum",u.INVALID_ARGUMENT,{arg:"address",value:e}),t=new i.default.BN(e.substring(4),36).toString(16);t.length<40;)t="0"+t;t=c("0x"+t)}else u.throwError("invalid address",u.INVALID_ARGUMENT,{arg:"address",value:e});return t}r.getAddress=b,r.getIcapAddress=function(e){for(var t=new i.default.BN(b(e).substring(2),16).toString(36).toUpperCase();t.length<30;)t="0"+t;return"XE"+p("XE00"+t)+t},r.getContractAddress=function(e){if(!e.from)throw new Error("missing from address");var t=e.nonce;return b("0x"+a.keccak256(s.encode([b(e.from),o.stripZeros(o.hexlify(t))])).substring(26))}},{"./bytes":223,"./errors":224,"./keccak256":225,"./rlp":227,"bn.js":219}],222:[function(e,t,r){var n,i=this&&this.__extends||(n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)t.hasOwnProperty(r)&&(e[r]=t[r])},function(e,t){function r(){this.constructor=e}n(e,t),e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)}),o=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}},a=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var r in e)Object.hasOwnProperty.call(e,r)&&(t[r]=e[r]);return t.default=e,t};Object.defineProperty(r,"__esModule",{value:!0});var s=o(e("bn.js")),u=e("./bytes"),c=e("./properties"),f=e("./types"),h=a(e("./errors")),d=new s.default.BN(-1);function l(e){var t=e.toString(16);return"-"===t[0]?t.length%2==0?"-0x0"+t.substring(1):"-0x"+t.substring(1):t.length%2==1?"0x0"+t:"0x"+t}function p(e){return m(e)._bn}function b(e){return new y(l(e))}var y=function(r){function n(e){var t=r.call(this)||this;if(h.checkNew(t,n),"string"==typeof e)u.isHexString(e)?("0x"==e&&(e="0x0"),c.defineReadOnly(t,"_hex",e)):"-"===e[0]&&u.isHexString(e.substring(1))?c.defineReadOnly(t,"_hex",e):e.match(/^-?[0-9]*$/)?(""==e&&(e="0"),c.defineReadOnly(t,"_hex",l(new s.default.BN(e)))):h.throwError("invalid BigNumber string value",h.INVALID_ARGUMENT,{arg:"value",value:e});else if("number"==typeof e){parseInt(String(e))!==e&&h.throwError("underflow",h.NUMERIC_FAULT,{operation:"setValue",fault:"underflow",value:e,outputValue:parseInt(String(e))});try{c.defineReadOnly(t,"_hex",l(new s.default.BN(e)))}catch(e){h.throwError("overflow",h.NUMERIC_FAULT,{operation:"setValue",fault:"overflow",details:e.message})}}else e instanceof n?c.defineReadOnly(t,"_hex",e._hex):e.toHexString?c.defineReadOnly(t,"_hex",l(p(e.toHexString()))):u.isArrayish(e)?c.defineReadOnly(t,"_hex",l(new s.default.BN(u.hexlify(e).substring(2),16))):h.throwError("invalid BigNumber value",h.INVALID_ARGUMENT,{arg:"value",value:e});return t}return i(n,r),Object.defineProperty(n.prototype,"_bn",{get:function(){return"-"===this._hex[0]?new s.default.BN(this._hex.substring(3),16).mul(d):new s.default.BN(this._hex.substring(2),16)},enumerable:!0,configurable:!0}),n.prototype.fromTwos=function(e){return b(this._bn.fromTwos(e))},n.prototype.toTwos=function(e){return b(this._bn.toTwos(e))},n.prototype.add=function(e){return b(this._bn.add(p(e)))},n.prototype.sub=function(e){return b(this._bn.sub(p(e)))},n.prototype.div=function(e){return m(e).isZero()&&h.throwError("division by zero",h.NUMERIC_FAULT,{operation:"divide",fault:"division by zero"}),b(this._bn.div(p(e)))},n.prototype.mul=function(e){return b(this._bn.mul(p(e)))},n.prototype.mod=function(e){return b(this._bn.mod(p(e)))},n.prototype.pow=function(e){return b(this._bn.pow(p(e)))},n.prototype.maskn=function(e){return b(this._bn.maskn(e))},n.prototype.eq=function(e){return this._bn.eq(p(e))},n.prototype.lt=function(e){return this._bn.lt(p(e))},n.prototype.lte=function(e){return this._bn.lte(p(e))},n.prototype.gt=function(e){return this._bn.gt(p(e))},n.prototype.gte=function(e){return this._bn.gte(p(e))},n.prototype.isZero=function(){return this._bn.isZero()},n.prototype.toNumber=function(){try{return this._bn.toNumber()}catch(e){h.throwError("overflow",h.NUMERIC_FAULT,{operation:"setValue",fault:"overflow",details:e.message})}return null},n.prototype.toString=function(){return this._bn.toString(10)},n.prototype.toHexString=function(){return this._hex},n}(f.BigNumber);function m(e){return e instanceof y?e:new y(e)}r.bigNumberify=m,r.ConstantNegativeOne=m(-1),r.ConstantZero=m(0),r.ConstantOne=m(1),r.ConstantTwo=m(2),r.ConstantWeiPerEther=m("1000000000000000000")},{"./bytes":223,"./errors":224,"./properties":226,"./types":228,"bn.js":219}],223:[function(e,t,r){Object.defineProperty(r,"__esModule",{value:!0});var s=e("./errors");function a(e){return!!e._bn}function u(t){return t.slice||(t.slice=function(){var e=Array.prototype.slice.call(arguments);return new Uint8Array(Array.prototype.slice.apply(t,e))}),t}function c(e){if(!e||parseInt(String(e.length))!=e.length||"string"==typeof e)return!1;for(var t=0;t>4]+h[15&o])}return"0x"+n.join("")}return s.throwError("invalid hexlify value",null,{arg:"value",value:e}),"never"}function l(e,t){for(i(e)||s.throwError("invalid hex string",s.INVALID_ARGUMENT,{arg:"value",value:e});e.length<2*t+2;)e="0x0"+e.substring(2);return e}function o(e){var t,r=0,n="0x",i="0x";if((t=e)&&null!=t.r&&null!=t.s){null==e.v&&null==e.recoveryParam&&s.throwError("at least on of recoveryParam or v must be specified",s.INVALID_ARGUMENT,{argument:"signature",value:e}),n=l(e.r,32),i=l(e.s,32),"string"==typeof(r=e.v)&&(r=parseInt(r,16));var o=e.recoveryParam;null==o&&null!=e.v&&(o=1-r%2),r=27+o}else{var a=f(e);if(65!==a.length)throw new Error("invalid signature");n=d(a.slice(0,32)),i=d(a.slice(32,64)),27!==(r=a[64])&&28!==r&&(r=27+r%2)}return{r:n,s:i,recoveryParam:r-27,v:r}}r.hexlify=d,r.hexDataLength=function(e){return i(e)&&e.length%2==0?(e.length-2)/2:null},r.hexDataSlice=function(e,t,r){return i(e)||s.throwError("invalid hex data",s.INVALID_ARGUMENT,{arg:"value",value:e}),e.length%2!=0&&s.throwError("hex data length must be even",s.INVALID_ARGUMENT,{arg:"value",value:e}),t=2+2*t,null!=r?"0x"+e.substring(t,t+2*r):"0x"+e.substring(t)},r.hexStripZeros=function(e){for(i(e)||s.throwError("invalid hex string",s.INVALID_ARGUMENT,{arg:"value",value:e});3>=8;return t}function i(e,t,r){for(var n=0,i=0;ie.length)throw new Error("too short");if(t+1+r+(n=i(e,t+1,r))>e.length)throw new Error("to short");return s(e,t,t+1+r,r+n)}if(192<=e[t]){if(t+1+(n=e[t]-192)>e.length)throw new Error("invalid rlp data");return s(e,t,t+1,n)}if(184<=e[t]){var r;if(t+1+(r=e[t]-183)>e.length)throw new Error("invalid rlp data");if(t+1+r+(n=i(e,t+1,r))>e.length)throw new Error("invalid rlp data");return{consumed:1+r+n,result:o.hexlify(e.slice(t+1+r,t+1+r+n))}}if(128<=e[t]){var n;if(t+1+(n=e[t]-128)>e.length)throw new Error("invlaid rlp data");return{consumed:1+n,result:o.hexlify(e.slice(t+1,t+1+n))}}return{consumed:1,result:o.hexlify(e[t])}}r.encode=function(e){return o.hexlify(function t(e){if(Array.isArray(e)){var r=[];return e.forEach(function(e){r=r.concat(t(e))}),r.length<=55?(r.unshift(192+r.length),r):((n=a(r.length)).unshift(247+n.length),n.concat(r))}var n,i=Array.prototype.slice.call(o.arrayify(e));return 1===i.length&&i[0]<=127?i:i.length<=55?(i.unshift(128+i.length),i):((n=a(i.length)).unshift(183+n.length),n.concat(i))}(e))},r.decode=function(e){var t=o.arrayify(e),r=u(t,0);if(r.consumed!==t.length)throw new Error("invalid rlp data");return r.result}},{"./bytes":223}],228:[function(e,t,r){Object.defineProperty(r,"__esModule",{value:!0});var n=function(){};r.BigNumber=n;var i=function(){};r.Indexed=i;var o=function(){};r.MinimalProvider=o;var a=function(){};r.Signer=a;var s=function(){};r.HDNode=s},{}],229:[function(e,t,r){Object.defineProperty(r,"__esModule",{value:!0});var a,n,u=e("./bytes");(n=a=r.UnicodeNormalizationForm||(r.UnicodeNormalizationForm={})).current="",n.NFC="NFC",n.NFD="NFD",n.NFKC="NFKC",n.NFKD="NFKD",r.toUtf8Bytes=function(e,t){void 0===t&&(t=a.current),t!=a.current&&(e=e.normalize(t));for(var r=[],n=0,i=0;i>6|192:(55296==(64512&o)&&i+1>18|240,r[n++]=o>>12&63|128):r[n++]=o>>12|224,r[n++]=o>>6&63|128),r[n++]=63&o|128)}return u.arrayify(r)},r.toUtf8String=function(e){e=u.arrayify(e);for(var t="",r=0;r>7!=0){if(n>>6!=2){var i=null;if(n>>5==6)i=1;else if(n>>4==14)i=2;else if(n>>3==30)i=3;else if(n>>2==62)i=4;else{if(n>>1!=126)continue;i=5}if(r+i>e.length){for(;r>6==2;r++);if(r!=e.length)continue;return t}var o,a=n&(1<<8-i-1)-1;for(o=0;o>6!=2)break;a=a<<6|63&s}o==i?a<=65535?t+=String.fromCharCode(a):(a-=65536,t+=String.fromCharCode(55296+(a>>10&1023),56320+(1023&a))):r--}}else t+=String.fromCharCode(n)}return t}},{"./bytes":223}],230:[function(e,_,t){(function(g,w){!function(){var e="object"===("undefined"==typeof window?"undefined":_typeof(window))?window:{};!e.JS_SHA3_NO_NODE_JS&&"object"===(void 0===g?"undefined":_typeof(g))&&g.versions&&g.versions.node&&(e=w);for(var t=!e.JS_SHA3_NO_COMMON_JS&&"object"===(void 0===_?"undefined":_typeof(_))&&_.exports,u="0123456789abcdef".split(""),f=[0,8,16,24],fe=[1,0,32898,0,32906,2147483648,2147516416,2147483648,32907,0,2147483649,0,2147516545,2147483648,32777,2147483648,138,0,136,0,2147516425,0,2147483658,0,2147516555,0,139,2147483648,32905,2147483648,32771,2147483648,32770,2147483648,128,2147483648,32778,0,2147483658,2147483648,2147516545,2147483648,32896,2147483648,2147483649,0,2147516424,2147483648],r=[224,256,384,512],o=["hex","buffer","arrayBuffer","array"],a=function(t,r,n){return function(e){return new m(t,r,t).update(e)[n]()}},s=function(r,n,i){return function(e,t){return new m(r,n,t).update(e)[i]()}},n=function(e,t){var r=a(e,t,"hex");r.create=function(){return new m(e,t,e)},r.update=function(e){return r.create().update(e)};for(var n=0;n>5,this.byteCount=this.blockCount<<2,this.outputBlocks=r>>5,this.extraBytes=(31&r)>>3;for(var n=0;n<50;++n)this.s[n]=0}m.prototype.update=function(e){var t="string"!=typeof e;t&&e.constructor===ArrayBuffer&&(e=new Uint8Array(e));for(var r,n,i=e.length,o=this.blocks,a=this.byteCount,s=this.blockCount,u=0,c=this.s;u>2]|=e[u]<>2]|=n<>2]|=(192|n>>6)<>2]|=(224|n>>12)<>2]|=(240|n>>18)<>2]|=(128|n>>12&63)<>2]|=(128|n>>6&63)<>2]|=(128|63&n)<>2]|=this.padding[3&t],this.lastByteIndex===this.byteCount)for(e[0]=e[r],t=1;t>4&15]+u[15&e]+u[e>>12&15]+u[e>>8&15]+u[e>>20&15]+u[e>>16&15]+u[e>>28&15]+u[e>>24&15];a%t==0&&(v(r),o=0)}return i&&(e=r[o],0>4&15]+u[15&e]),1>12&15]+u[e>>8&15]),2>20&15]+u[e>>16&15])),s},m.prototype.buffer=m.prototype.arrayBuffer=function(){this.finalize();var e,t=this.blockCount,r=this.s,n=this.outputBlocks,i=this.extraBytes,o=0,a=0,s=this.outputBits>>3;e=i?new ArrayBuffer(n+1<<2):new ArrayBuffer(s);for(var u=new Uint32Array(e);a>8&255,u[e+2]=t>>16&255,u[e+3]=t>>24&255;s%r==0&&v(n)}return o&&(e=s<<2,t=n[a],0>8&255),2>16&255)),u};var v=function(e){var t,r,n,i,o,a,s,u,c,f,h,d,l,p,b,y,m,v,g,w,_,A,x,M,k,E,S,U,j,I,T,C,B,N,P,R,O,L,q,D,F,H,z,K,V,G,W,X,J,Z,$,Y,Q,ee,te,re,ne,ie,oe,ae,se,ue,ce;for(n=0;n<48;n+=2)i=e[0]^e[10]^e[20]^e[30]^e[40],o=e[1]^e[11]^e[21]^e[31]^e[41],a=e[2]^e[12]^e[22]^e[32]^e[42],s=e[3]^e[13]^e[23]^e[33]^e[43],u=e[4]^e[14]^e[24]^e[34]^e[44],c=e[5]^e[15]^e[25]^e[35]^e[45],f=e[6]^e[16]^e[26]^e[36]^e[46],h=e[7]^e[17]^e[27]^e[37]^e[47],t=(d=e[8]^e[18]^e[28]^e[38]^e[48])^(a<<1|s>>>31),r=(l=e[9]^e[19]^e[29]^e[39]^e[49])^(s<<1|a>>>31),e[0]^=t,e[1]^=r,e[10]^=t,e[11]^=r,e[20]^=t,e[21]^=r,e[30]^=t,e[31]^=r,e[40]^=t,e[41]^=r,t=i^(u<<1|c>>>31),r=o^(c<<1|u>>>31),e[2]^=t,e[3]^=r,e[12]^=t,e[13]^=r,e[22]^=t,e[23]^=r,e[32]^=t,e[33]^=r,e[42]^=t,e[43]^=r,t=a^(f<<1|h>>>31),r=s^(h<<1|f>>>31),e[4]^=t,e[5]^=r,e[14]^=t,e[15]^=r,e[24]^=t,e[25]^=r,e[34]^=t,e[35]^=r,e[44]^=t,e[45]^=r,t=u^(d<<1|l>>>31),r=c^(l<<1|d>>>31),e[6]^=t,e[7]^=r,e[16]^=t,e[17]^=r,e[26]^=t,e[27]^=r,e[36]^=t,e[37]^=r,e[46]^=t,e[47]^=r,t=f^(i<<1|o>>>31),r=h^(o<<1|i>>>31),e[8]^=t,e[9]^=r,e[18]^=t,e[19]^=r,e[28]^=t,e[29]^=r,e[38]^=t,e[39]^=r,e[48]^=t,e[49]^=r,p=e[0],b=e[1],G=e[11]<<4|e[10]>>>28,W=e[10]<<4|e[11]>>>28,U=e[20]<<3|e[21]>>>29,j=e[21]<<3|e[20]>>>29,ae=e[31]<<9|e[30]>>>23,se=e[30]<<9|e[31]>>>23,H=e[40]<<18|e[41]>>>14,z=e[41]<<18|e[40]>>>14,N=e[2]<<1|e[3]>>>31,P=e[3]<<1|e[2]>>>31,y=e[13]<<12|e[12]>>>20,m=e[12]<<12|e[13]>>>20,X=e[22]<<10|e[23]>>>22,J=e[23]<<10|e[22]>>>22,I=e[33]<<13|e[32]>>>19,T=e[32]<<13|e[33]>>>19,ue=e[42]<<2|e[43]>>>30,ce=e[43]<<2|e[42]>>>30,ee=e[5]<<30|e[4]>>>2,te=e[4]<<30|e[5]>>>2,R=e[14]<<6|e[15]>>>26,O=e[15]<<6|e[14]>>>26,v=e[25]<<11|e[24]>>>21,g=e[24]<<11|e[25]>>>21,Z=e[34]<<15|e[35]>>>17,$=e[35]<<15|e[34]>>>17,C=e[45]<<29|e[44]>>>3,B=e[44]<<29|e[45]>>>3,M=e[6]<<28|e[7]>>>4,k=e[7]<<28|e[6]>>>4,re=e[17]<<23|e[16]>>>9,ne=e[16]<<23|e[17]>>>9,L=e[26]<<25|e[27]>>>7,q=e[27]<<25|e[26]>>>7,w=e[36]<<21|e[37]>>>11,_=e[37]<<21|e[36]>>>11,Y=e[47]<<24|e[46]>>>8,Q=e[46]<<24|e[47]>>>8,K=e[8]<<27|e[9]>>>5,V=e[9]<<27|e[8]>>>5,E=e[18]<<20|e[19]>>>12,S=e[19]<<20|e[18]>>>12,ie=e[29]<<7|e[28]>>>25,oe=e[28]<<7|e[29]>>>25,D=e[38]<<8|e[39]>>>24,F=e[39]<<8|e[38]>>>24,A=e[48]<<14|e[49]>>>18,x=e[49]<<14|e[48]>>>18,e[0]=p^~y&v,e[1]=b^~m&g,e[10]=M^~E&U,e[11]=k^~S&j,e[20]=N^~R&L,e[21]=P^~O&q,e[30]=K^~G&X,e[31]=V^~W&J,e[40]=ee^~re&ie,e[41]=te^~ne&oe,e[2]=y^~v&w,e[3]=m^~g&_,e[12]=E^~U&I,e[13]=S^~j&T,e[22]=R^~L&D,e[23]=O^~q&F,e[32]=G^~X&Z,e[33]=W^~J&$,e[42]=re^~ie&ae,e[43]=ne^~oe&se,e[4]=v^~w&A,e[5]=g^~_&x,e[14]=U^~I&C,e[15]=j^~T&B,e[24]=L^~D&H,e[25]=q^~F&z,e[34]=X^~Z&Y,e[35]=J^~$&Q,e[44]=ie^~ae&ue,e[45]=oe^~se&ce,e[6]=w^~A&p,e[7]=_^~x&b,e[16]=I^~C&M,e[17]=T^~B&k,e[26]=D^~H&N,e[27]=F^~z&P,e[36]=Z^~Y&K,e[37]=$^~Q&V,e[46]=ae^~ue&ee,e[47]=se^~ce&te,e[8]=A^~p&y,e[9]=x^~b&m,e[18]=C^~M&E,e[19]=B^~k&S,e[28]=H^~N&R,e[29]=z^~P&O,e[38]=Y^~K&G,e[39]=Q^~V&W,e[48]=ue^~ee&re,e[49]=ce^~te&ne,e[0]^=fe[n],e[1]^=fe[n+1]};if(t)_.exports=c;else for(d=0;d=t)throw new Error("invalid sig")}t.exports=function(e,t,r,n,i){var o=b(r);if("ec"===o.type){if("ecdsa"!==n&&"ecdsa/rsa"!==n)throw new Error("wrong public key type");return function(e,t,r){var n=y[r.data.algorithm.curve.join(".")];if(!n)throw new Error("unknown curve "+r.data.algorithm.curve.join("."));var i=new p(n),o=r.data.subjectPrivateKey.data;return i.verify(t,e,o)}(e,t,o)}if("dsa"===o.type){if("dsa"!==n)throw new Error("wrong public key type");return function(e,t,r){var n=r.data.p,i=r.data.q,o=r.data.g,a=r.data.pub_key,s=b.signature.decode(e,"der"),u=s.s,c=s.r;m(u,i),m(c,i);var f=l.mont(n),h=u.invm(i);return 0===o.toRed(f).redPow(new l(t).mul(h).mod(i)).fromRed().mul(a.toRed(f).redPow(c.mul(h).mod(i)).fromRed()).mod(n).mod(i).cmp(c)}(e,t,o)}if("rsa"!==n&&"ecdsa/rsa"!==n)throw new Error("wrong public key type");t=d.concat([i,t]);for(var a=o.modulus.byteLength(),s=[1],u=0;t.length+s.length+2=t.length)throw"";var e=t.slice(r,r+2);return e<"80"?(r+=2,"0x"+e):e<"c0"?o():a()},i=function(){var e=parseInt(t.slice(r,r+=2),16)%64;return e<56?e:parseInt(t.slice(r,r+=2*(e-55)),16)},o=function(){var e=i();return"0x"+t.slice(r,r+=2*e)},a=function(){for(var e=2*i()+r,t=[];r>>32-t}function u(e,t,r,n,i,o,a){return s(e+(t&r|~t&n)+i+o|0,a)+t|0}function c(e,t,r,n,i,o,a){return s(e+(t&n|r&~n)+i+o|0,a)+t|0}function f(e,t,r,n,i,o,a){return s(e+(t^r^n)+i+o|0,a)+t|0}function h(e,t,r,n,i,o,a){return s(e+(r^(t|~n))+i+o|0,a)+t|0}e(n,r),n.prototype._update=function(){for(var e=a,t=0;t<16;++t)e[t]=this._block.readInt32LE(4*t);var r=this._a,n=this._b,i=this._c,o=this._d;n=h(n=h(n=h(n=h(n=f(n=f(n=f(n=f(n=c(n=c(n=c(n=c(n=u(n=u(n=u(n=u(n,i=u(i,o=u(o,r=u(r,n,i,o,e[0],3614090360,7),n,i,e[1],3905402710,12),r,n,e[2],606105819,17),o,r,e[3],3250441966,22),i=u(i,o=u(o,r=u(r,n,i,o,e[4],4118548399,7),n,i,e[5],1200080426,12),r,n,e[6],2821735955,17),o,r,e[7],4249261313,22),i=u(i,o=u(o,r=u(r,n,i,o,e[8],1770035416,7),n,i,e[9],2336552879,12),r,n,e[10],4294925233,17),o,r,e[11],2304563134,22),i=u(i,o=u(o,r=u(r,n,i,o,e[12],1804603682,7),n,i,e[13],4254626195,12),r,n,e[14],2792965006,17),o,r,e[15],1236535329,22),i=c(i,o=c(o,r=c(r,n,i,o,e[1],4129170786,5),n,i,e[6],3225465664,9),r,n,e[11],643717713,14),o,r,e[0],3921069994,20),i=c(i,o=c(o,r=c(r,n,i,o,e[5],3593408605,5),n,i,e[10],38016083,9),r,n,e[15],3634488961,14),o,r,e[4],3889429448,20),i=c(i,o=c(o,r=c(r,n,i,o,e[9],568446438,5),n,i,e[14],3275163606,9),r,n,e[3],4107603335,14),o,r,e[8],1163531501,20),i=c(i,o=c(o,r=c(r,n,i,o,e[13],2850285829,5),n,i,e[2],4243563512,9),r,n,e[7],1735328473,14),o,r,e[12],2368359562,20),i=f(i,o=f(o,r=f(r,n,i,o,e[5],4294588738,4),n,i,e[8],2272392833,11),r,n,e[11],1839030562,16),o,r,e[14],4259657740,23),i=f(i,o=f(o,r=f(r,n,i,o,e[1],2763975236,4),n,i,e[4],1272893353,11),r,n,e[7],4139469664,16),o,r,e[10],3200236656,23),i=f(i,o=f(o,r=f(r,n,i,o,e[13],681279174,4),n,i,e[0],3936430074,11),r,n,e[3],3572445317,16),o,r,e[6],76029189,23),i=f(i,o=f(o,r=f(r,n,i,o,e[9],3654602809,4),n,i,e[12],3873151461,11),r,n,e[15],530742520,16),o,r,e[2],3299628645,23),i=h(i,o=h(o,r=h(r,n,i,o,e[0],4096336452,6),n,i,e[7],1126891415,10),r,n,e[14],2878612391,15),o,r,e[5],4237533241,21),i=h(i,o=h(o,r=h(r,n,i,o,e[12],1700485571,6),n,i,e[3],2399980690,10),r,n,e[10],4293915773,15),o,r,e[1],2240044497,21),i=h(i,o=h(o,r=h(r,n,i,o,e[8],1873313359,6),n,i,e[15],4264355552,10),r,n,e[6],2734768916,15),o,r,e[13],1309151649,21),i=h(i,o=h(o,r=h(r,n,i,o,e[4],4149444226,6),n,i,e[11],3174756917,10),r,n,e[2],718787259,15),o,r,e[9],3951481745,21),this._a=this._a+r|0,this._b=this._b+n|0,this._c=this._c+i|0,this._d=this._d+o|0},n.prototype._digest=function(){this._block[this._blockOffset++]=128,56a||0<=new c(t).cmp(o.modulus))throw new Error("decryption error");i=r?b(new c(t),o):l(t,o);var s=new f(a-i.length);if(s.fill(0),i=f.concat([s,i],a),4===n)return function(e,t){e.modulus;var r=e.modulus.byteLength(),n=(t.length,p("sha1").update(new f("")).digest()),i=n.length;if(0!==t[0])throw new Error("decryption error");var o=t.slice(1,i+1),a=t.slice(i+1),s=d(o,h(a,i)),u=d(a,h(s,r-i-1));if(function(e,t){e=new f(e),t=new f(t);var r=0,n=e.length;e.length!==t.length&&(r++,n=Math.min(e.length,t.length));var i=-1;for(;++i=t.length){o++;break}var a=t.slice(2,i-1);t.slice(i-1,i);("0002"!==n.toString("hex")&&!r||"0001"!==n.toString("hex")&&r)&&o++;a.length<8&&o++;if(o)throw new Error("decryption error");return t.slice(i)}(0,i,r);if(3===n)return i;throw new Error("unknown padding")}}).call(this,e("buffer").Buffer)},{"./mgf":350,"./withPublic":353,"./xor":354,"bn.js":250,"browserify-rsa":272,buffer:47,"create-hash":282,"parse-asn1":343}],352:[function(e,t,r){(function(d){var a=e("parse-asn1"),l=e("randombytes"),p=e("create-hash"),b=e("./mgf"),y=e("./xor"),m=e("bn.js"),s=e("./withPublic"),u=e("browserify-rsa");t.exports=function(e,t,r){var n;n=e.padding?e.padding:r?1:4;var i,o=a(e);if(4===n)i=function(e,t){var r=e.modulus.byteLength(),n=t.length,i=p("sha1").update(new d("")).digest(),o=i.length,a=2*o;if(r-a-2 0 and a power of 2");if(2147483647/128/n>>32-t}function w(e){var t;for(t=0;t<16;t++)f[t]=(255&e[4*t+0])<<0,f[t]|=(255&e[4*t+1])<<8,f[t]|=(255&e[4*t+2])<<16,f[t]|=(255&e[4*t+3])<<24;for(M(f,0,h,0,16),t=8;0>0&255,e[r+1]=f[t]>>8&255,e[r+2]=f[t]>>16&255,e[r+3]=f[t]>>24&255}}function _(e,t,r,n,i){for(var o=0;o>>((3&t)<<3)&255;return n}}i.exports=t}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],371:[function(e,t,r){for(var a=e("./rng"),i=[],o={},n=0;n<256;n++)i[n]=(n+256).toString(16).substr(1),o[i[n]]=n;function l(e,t){var r=t||0,n=i;return n[e[r++]]+n[e[r++]]+n[e[r++]]+n[e[r++]]+"-"+n[e[r++]]+n[e[r++]]+"-"+n[e[r++]]+n[e[r++]]+"-"+n[e[r++]]+n[e[r++]]+"-"+n[e[r++]]+n[e[r++]]+n[e[r++]]+n[e[r++]]+n[e[r++]]+n[e[r++]]}var s=a(),p=[1|s[0],s[1],s[2],s[3],s[4],s[5]],b=16383&(s[6]<<8|s[7]),y=0,m=0;function u(e,t,r){var n=t&&r||0;"string"==typeof e&&(t="binary"==e?new Array(16):null,e=null);var i=(e=e||{}).random||(e.rng||a)();if(i[6]=15&i[6]|64,i[8]=63&i[8]|128,t)for(var o=0;o<16;o++)t[n+o]=i[o];return t||l(i)}var c=u;c.v1=function(e,t,r){var n=t&&r||0,i=t||[],o=void 0!==(e=e||{}).clockseq?e.clockseq:b,a=void 0!==e.msecs?e.msecs:(new Date).getTime(),s=void 0!==e.nsecs?e.nsecs:m+1,u=a-y+(s-m)/1e4;if(u<0&&void 0===e.clockseq&&(o=o+1&16383),(u<0||y>>24&255,i[n++]=c>>>16&255,i[n++]=c>>>8&255,i[n++]=255&c;var f=a/4294967296*1e4&268435455;i[n++]=f>>>8&255,i[n++]=255&f,i[n++]=f>>>24&15|16,i[n++]=f>>>16&255,i[n++]=o>>>8|128,i[n++]=255&o;for(var h=e.node||p,d=0;d<6;d++)i[n+d]=h[d];return t||l(i)},c.v4=u,c.parse=function(e,t,r){var n=t&&r||0,i=0;for(t=t||[],e.toLowerCase().replace(/[0-9a-f]{2}/g,function(e){i<16&&(t[n+i++]=o[e])});i<16;)t[n+i++]=0;return t},c.unparse=l,t.exports=c},{"./rng":370}],372:[function(a,s,e){(function(e,d){var l=a("underscore"),r=a("web3-core"),n=a("web3-core-method"),p=a("any-promise"),b=a("eth-lib/lib/account"),y=a("eth-lib/lib/hash"),m=a("eth-lib/lib/rlp"),v=a("eth-lib/lib/nat"),g=a("eth-lib/lib/bytes"),w=a(void 0===e?"crypto-browserify":"crypto"),_=a("scrypt.js"),A=a("uuid"),x=a("web3-utils"),M=a("web3-core-helpers"),i=function(e){return l.isUndefined(e)||l.isNull(e)},k=function(e){for(;e&&e.startsWith("0x0");)e="0x"+e.slice(3);return e},E=function(e){return e.length%2==1&&(e=e.replace("0x","0x0")),e},t=function(){var t=this;r.packageInit(this,arguments),delete this.BatchRequest,delete this.extend;var e=[new n({name:"getId",call:"net_version",params:0,outputFormatter:x.hexToNumber}),new n({name:"getGasPrice",call:"eth_gasPrice",params:0}),new n({name:"getTransactionCount",call:"eth_getTransactionCount",params:2,inputFormatter:[function(e){if(x.isAddress(e))return e;throw new Error("Address "+e+' is not a valid address to get the "transactionCount".')},function(){return"latest"}]})];this._ethereumCall={},l.each(e,function(e){e.attachToObject(t._ethereumCall),e.setRequestManager(t._requestManager)}),this.wallet=new o(this)};function o(e){this._accounts=e,this.length=0,this.defaultKeyName="web3js_wallet"}t.prototype._addAccountFunctions=function(r){var n=this;return r.signTransaction=function(e,t){return n.signTransaction(e,r.privateKey,t)},r.sign=function(e){return n.sign(e,r.privateKey)},r.encrypt=function(e,t){return n.encrypt(r.privateKey,e,t)},r},t.prototype.create=function(e){return this._addAccountFunctions(b.create(e||x.randomHex(32)))},t.prototype.privateKeyToAccount=function(e){return this._addAccountFunctions(b.fromPrivate(e))},t.prototype.signTransaction=function(t,u,c){var f,h=!1;if(c=c||function(){},!t)return h=new Error("No transaction object given!"),c(h),p.reject(h);function r(e){if(e.gas||e.gasLimit||(h=new Error('"gas" is missing')),(e.nonce<0||e.gas<0||e.gasPrice<0||e.chainId<0)&&(h=new Error("Gas, gasPrice, nonce or chainId is lower than 0")),h)return c(h),p.reject(h);try{var t=e=M.formatters.inputCallFormatter(e);t.to=e.to||"0x",t.data=e.data||"0x",t.value=e.value||"0x",t.chainId=x.numberToHex(e.chainId);var r=m.encode([g.fromNat(t.nonce),g.fromNat(t.gasPrice),g.fromNat(t.gas),t.to.toLowerCase(),g.fromNat(t.value),t.data,g.fromNat(t.chainId||"0x1"),"0x","0x"]),n=y.keccak256(r),i=b.makeSigner(2*v.toNumber(t.chainId||"0x1")+35)(y.keccak256(r),u),o=m.decode(r).slice(0,6).concat(b.decodeSignature(i));o[6]=E(k(o[6])),o[7]=E(k(o[7])),o[8]=E(k(o[8]));var a=m.encode(o),s=m.decode(a);f={messageHash:n,v:k(s[6]),r:k(s[7]),s:k(s[8]),rawTransaction:a}}catch(e){return c(e),p.reject(e)}return c(null,f),f}return void 0!==t.nonce&&void 0!==t.chainId&&void 0!==t.gasPrice?p.resolve(r(t)):p.all([i(t.chainId)?this._ethereumCall.getId():t.chainId,i(t.gasPrice)?this._ethereumCall.getGasPrice():t.gasPrice,i(t.nonce)?this._ethereumCall.getTransactionCount(this.privateKeyToAccount(u).address):t.nonce]).then(function(e){if(i(e[0])||i(e[1])||i(e[2]))throw new Error('One of the values "chainId", "gasPrice", or "nonce" couldn\'t be fetched: '+JSON.stringify(e));return r(l.extend(t,{chainId:e[0],gasPrice:e[1],nonce:e[2]}))})},t.prototype.recoverTransaction=function(e){var t=m.decode(e),r=b.encodeSignature(t.slice(6,9)),n=g.toNumber(t[6]),i=n<35?[]:[g.fromNumber(n-35>>1),"0x","0x"],o=t.slice(0,6).concat(i),a=m.encode(o);return b.recover(y.keccak256(a),r)},t.prototype.hashMessage=function(e){var t=x.isHexStrict(e)?x.hexToBytes(e):e,r=d.from(t),n="Ethereum Signed Message:\n"+t.length,i=d.from(n),o=d.concat([i,r]);return y.keccak256s(o)},t.prototype.sign=function(e,t){var r=this.hashMessage(e),n=b.sign(r,t),i=b.decodeSignature(n);return{message:e,messageHash:r,v:i[0],r:i[1],s:i[2],signature:n}},t.prototype.recover=function(e,t,r){var n=[].slice.apply(arguments);return l.isObject(e)?this.recover(e.messageHash,b.encodeSignature([e.v,e.r,e.s]),!0):(r||(e=this.hashMessage(e)),4<=n.length?(r=n.slice(-1)[0],r=!!l.isBoolean(r)&&!!r,this.recover(e,b.encodeSignature(n.slice(1,4)),r)):b.recover(e,t))},t.prototype.decrypt=function(e,t,r){if(!l.isString(t))throw new Error("No password given.");var n,i,o=l.isObject(e)?e:JSON.parse(r?e.toLowerCase():e);if(3!==o.version)throw new Error("Not a valid V3 wallet");if("scrypt"===o.crypto.kdf)i=o.crypto.kdfparams,n=_(new d(t),new d(i.salt,"hex"),i.n,i.r,i.p,i.dklen);else{if("pbkdf2"!==o.crypto.kdf)throw new Error("Unsupported key derivation scheme");if("hmac-sha256"!==(i=o.crypto.kdfparams).prf)throw new Error("Unsupported parameters to PBKDF2");n=w.pbkdf2Sync(new d(t),new d(i.salt,"hex"),i.c,i.dklen,"sha256")}var a=new d(o.crypto.ciphertext,"hex");if(x.sha3(d.concat([n.slice(16,32),a])).replace("0x","")!==o.crypto.mac)throw new Error("Key derivation failed - possibly wrong password");var s=w.createDecipheriv(o.crypto.cipher,n.slice(0,16),new d(o.crypto.cipherparams.iv,"hex")),u="0x"+d.concat([s.update(a),s.final()]).toString("hex");return this.privateKeyToAccount(u)},t.prototype.encrypt=function(e,t,r){var n,i=this.privateKeyToAccount(e),o=(r=r||{}).salt||w.randomBytes(32),a=r.iv||w.randomBytes(16),s=r.kdf||"scrypt",u={dklen:r.dklen||32,salt:o.toString("hex")};if("pbkdf2"===s)u.c=r.c||262144,u.prf="hmac-sha256",n=w.pbkdf2Sync(new d(t),o,u.c,u.dklen,"sha256");else{if("scrypt"!==s)throw new Error("Unsupported kdf");u.n=r.n||8192,u.r=r.r||8,u.p=r.p||1,n=_(new d(t),o,u.n,u.r,u.p,u.dklen)}var c=w.createCipheriv(r.cipher||"aes-128-ctr",n.slice(0,16),a);if(!c)throw new Error("Unsupported cipher");var f=d.concat([c.update(new d(i.privateKey.replace("0x",""),"hex")),c.final()]),h=x.sha3(d.concat([n.slice(16,32),new d(f,"hex")])).replace("0x","");return{version:3,id:A.v4({random:r.uuid||w.randomBytes(16)}),address:i.address.toLowerCase().replace("0x",""),crypto:{ciphertext:f.toString("hex"),cipherparams:{iv:a.toString("hex")},cipher:r.cipher||"aes-128-ctr",kdf:s,kdfparams:u,mac:h.toString("hex")}}},o.prototype._findSafeIndex=function(e){return e=e||0,l.has(this,e)?this._findSafeIndex(e+1):e},o.prototype._currentIndexes=function(){return Object.keys(this).map(function(e){return parseInt(e)}).filter(function(e){return e<9e20})},o.prototype.create=function(e,t){for(var r=0;r\\$%@ءؤة\"'^|~⦅⦆・ゥャ¢£¬¦¥₩│←↑→↓■○𐐨𐐩𐐪𐐫𐐬𐐭𐐮𐐯𐐰𐐱𐐲𐐳𐐴𐐵𐐶𐐷𐐸𐐹𐐺𐐻𐐼𐐽𐐾𐐿𐑀𐑁𐑂𐑃𐑄𐑅𐑆𐑇𐑈𐑉𐑊𐑋𐑌𐑍𐑎𐑏𐓘𐓙𐓚𐓛𐓜𐓝𐓞𐓟𐓠𐓡𐓢𐓣𐓤𐓥𐓦𐓧𐓨𐓩𐓪𐓫𐓬𐓭𐓮𐓯𐓰𐓱𐓲𐓳𐓴𐓵𐓶𐓷𐓸𐓹𐓺𐓻𐳀𐳁𐳂𐳃𐳄𐳅𐳆𐳇𐳈𐳉𐳊𐳋𐳌𐳍𐳎𐳏𐳐𐳑𐳒𐳓𐳔𐳕𐳖𐳗𐳘𐳙𐳚𐳛𐳜𐳝𐳞𐳟𐳠𐳡𐳢𐳣𐳤𐳥𐳦𐳧𐳨𐳩𐳪𐳫𐳬𐳭𐳮𐳯𐳰𐳱𐳲𑣀𑣁𑣂𑣃𑣄𑣅𑣆𑣇𑣈𑣉𑣊𑣋𑣌𑣍𑣎𑣏𑣐𑣑𑣒𑣓𑣔𑣕𑣖𑣗𑣘𑣙𑣚𑣛𑣜𑣝𑣞𑣟ıȷ∇∂𞤢𞤣𞤤𞤥𞤦𞤧𞤨𞤩𞤪𞤫𞤬𞤭𞤮𞤯𞤰𞤱𞤲𞤳𞤴𞤵𞤶𞤷𞤸𞤹𞤺𞤻𞤼𞤽𞤾𞤿𞥀𞥁𞥂𞥃ٮڡٯ字双多解交映無前後再新初終販声吹演投捕遊指禁空合満申割営配得可丽丸乁𠄢你侻倂偺備像㒞𠘺兔兤具𠔜㒹內𠕋冗冤仌冬𩇟刃㓟刻剆剷㔕包匆卉博即卽卿𠨬灰及叟𠭣叫叱吆咞吸呈周咢哶唐啓啣善喫喳嗂圖圗噑噴壮城埴堍型堲報墬𡓤売壷夆夢奢𡚨𡛪姬娛娧姘婦㛮嬈嬾𡧈寃寘寳𡬘寿将㞁屠峀岍𡷤嵃𡷦嵮嵫嵼巡巢㠯巽帨帽幩㡢𢆃㡼庰庳庶𪎒𢌱舁弢㣇𣊸𦇚形彫㣣徚忍志忹悁㤺㤜𢛔惇慈慌慺憲憤憯懞戛扝抱拔捐𢬌挽拼捨掃揤𢯱搢揅掩㨮摩摾撝摷㩬敬𣀊旣書晉㬙㬈㫤冒冕最暜肭䏙朡杞杓𣏃㭉柺枅桒𣑭梎栟椔楂榣槪檨𣚣櫛㰘次𣢧歔㱎歲殟殻𣪍𡴋𣫺汎𣲼沿泍汧洖派浩浸涅𣴞洴港湮㴳滇𣻑淹潮𣽞𣾎濆瀹瀛㶖灊災灷炭𠔥煅𤉣熜爨牐𤘈犀犕𤜵𤠔獺王㺬玥㺸瑇瑜璅瓊㼛甤𤰶甾𤲒𢆟瘐𤾡𤾸𥁄㿼䀈𥃳𥃲𥄙𥄳眞真瞋䁆䂖𥐝硎䃣𥘦𥚚𥛅秫䄯穊穏𥥼𥪧䈂𥮫篆築䈧𥲀糒䊠糨糣紀𥾆絣䌁緇縂繅䌴𦈨𦉇䍙𦋙罺𦌾羕翺𦓚𦔣聠𦖨聰𣍟䏕育脃䐋脾媵𦞧𦞵𣎓𣎜舄辞䑫芑芋芝劳花芳芽苦𦬼茝荣莭茣莽菧荓菊菌菜𦰶𦵫𦳕䔫蓱蓳蔖𧏊蕤𦼬䕝䕡𦾱𧃒䕫虐虧虩蚩蚈蜎蛢蜨蝫螆蟡蠁䗹衠𧙧裗裞䘵裺㒻𧢮𧥦䚾䛇誠𧲨貫賁贛起𧼯𠠄跋趼跰𠣞軔𨗒𨗭邔郱鄑𨜮鄛鈸鋗鋘鉼鏹鐕𨯺開䦕閷𨵷䧦雃嶲霣𩅅𩈚䩮䩶韠𩐊䪲𩒖頩𩖶飢䬳餩馧駂駾䯎𩬰鱀鳽䳎䳭鵧𪃎䳸𪄅𪈎𪊑䵖黾鼅鼏鼖𪘀",mapChar:function(e){return 196608<=e?917760<=e&&e<=917999?18874368:0:t[r[e>>4]][15&e]}}},"function"==typeof define&&define.amd?define([],function(){return i()}):"object"===(void 0===r?"undefined":_typeof(r))?t.exports=i():n.uts46_map=i()},{}],377:[function(e,t,r){var n,i;n=this,i=function(p,b){function i(e,t,r){for(var n=[],i=p.ucs2.decode(e),o=0;o>23,f=u>>21&3,h=u>>5&65535,d=31&u,l=b.mapStr.substr(h,d);if(0===f||t&&1&c)throw new Error("Illegal char "+s);1===f?n.push(l):2===f?n.push(r?l:s):3===f&&n.push(s)}return n.join("").normalize("NFC")}function c(e,t,r){void 0===r&&(r=!1);var n=i(e,r,t).split(".");return(n=n.map(function(e){return e.startsWith("xn--")?o(e=p.decode(e.substring(4)),r,!1):o(e,r,t),e})).join(".")}function o(e,t,r){if("-"===e[2]&&"-"===e[3])throw new Error("Failed to validate "+e);if(e.startsWith("-")||e.endsWith("-"))throw new Error("Failed to validate "+e);if(e.includes("."))throw new Error("Failed to validate "+e);if(i(e,t,r)!==e)throw new Error("Failed to validate "+e);var n=e.codePointAt(0);if(b.mapChar(n)&2<<23)throw new Error("Label contains illegal character: "+n)}return{toUnicode:function(e,t){return void 0===t&&(t={}),c(e,!1,"useStd3ASCII"in t&&t.useStd3ASCII)},toAscii:function(e,t){void 0===t&&(t={});var r,n=!("transitional"in t)||t.transitional,i="useStd3ASCII"in t&&t.useStd3ASCII,o="verifyDnsLength"in t&&t.verifyDnsLength,a=c(e,n,i).split(".").map(p.toASCII),s=a.join(".");if(o){if(s.length<1||253>5,this.byteCount=this.blockCount<<2,this.outputBlocks=r>>5,this.extraBytes=(31&r)>>3;for(var n=0;n<50;++n)this.s[n]=0}m.prototype.update=function(e){var t="string"!=typeof e;t&&e.constructor===ArrayBuffer&&(e=new Uint8Array(e));for(var r,n,i=e.length,o=this.blocks,a=this.byteCount,s=this.blockCount,u=0,c=this.s;u>2]|=e[u]<>2]|=n<>2]|=(192|n>>6)<>2]|=(224|n>>12)<>2]|=(240|n>>18)<>2]|=(128|n>>12&63)<>2]|=(128|n>>6&63)<>2]|=(128|63&n)<>2]|=this.padding[3&t],this.lastByteIndex===this.byteCount)for(e[0]=e[r],t=1;t>4&15]+u[15&e]+u[e>>12&15]+u[e>>8&15]+u[e>>20&15]+u[e>>16&15]+u[e>>28&15]+u[e>>24&15];a%t==0&&(v(r),o=0)}return i&&(e=r[o],0>4&15]+u[15&e]),1>12&15]+u[e>>8&15]),2>20&15]+u[e>>16&15])),s},m.prototype.buffer=m.prototype.arrayBuffer=function(){this.finalize();var e,t=this.blockCount,r=this.s,n=this.outputBlocks,i=this.extraBytes,o=0,a=0,s=this.outputBits>>3;e=i?new ArrayBuffer(n+1<<2):new ArrayBuffer(s);for(var u=new Uint32Array(e);a>8&255,u[e+2]=t>>16&255,u[e+3]=t>>24&255;s%r==0&&v(n)}return o&&(e=s<<2,t=n[a],0>8&255),2>16&255)),u};var v=function(e){var t,r,n,i,o,a,s,u,c,f,h,d,l,p,b,y,m,v,g,w,_,A,x,M,k,E,S,U,j,I,T,C,B,N,P,R,O,L,q,D,F,H,z,K,V,G,W,X,J,Z,$,Y,Q,ee,te,re,ne,ie,oe,ae,se,ue,ce;for(n=0;n<48;n+=2)i=e[0]^e[10]^e[20]^e[30]^e[40],o=e[1]^e[11]^e[21]^e[31]^e[41],a=e[2]^e[12]^e[22]^e[32]^e[42],s=e[3]^e[13]^e[23]^e[33]^e[43],u=e[4]^e[14]^e[24]^e[34]^e[44],c=e[5]^e[15]^e[25]^e[35]^e[45],f=e[6]^e[16]^e[26]^e[36]^e[46],h=e[7]^e[17]^e[27]^e[37]^e[47],t=(d=e[8]^e[18]^e[28]^e[38]^e[48])^(a<<1|s>>>31),r=(l=e[9]^e[19]^e[29]^e[39]^e[49])^(s<<1|a>>>31),e[0]^=t,e[1]^=r,e[10]^=t,e[11]^=r,e[20]^=t,e[21]^=r,e[30]^=t,e[31]^=r,e[40]^=t,e[41]^=r,t=i^(u<<1|c>>>31),r=o^(c<<1|u>>>31),e[2]^=t,e[3]^=r,e[12]^=t,e[13]^=r,e[22]^=t,e[23]^=r,e[32]^=t,e[33]^=r,e[42]^=t,e[43]^=r,t=a^(f<<1|h>>>31),r=s^(h<<1|f>>>31),e[4]^=t,e[5]^=r,e[14]^=t,e[15]^=r,e[24]^=t,e[25]^=r,e[34]^=t,e[35]^=r,e[44]^=t,e[45]^=r,t=u^(d<<1|l>>>31),r=c^(l<<1|d>>>31),e[6]^=t,e[7]^=r,e[16]^=t,e[17]^=r,e[26]^=t,e[27]^=r,e[36]^=t,e[37]^=r,e[46]^=t,e[47]^=r,t=f^(i<<1|o>>>31),r=h^(o<<1|i>>>31),e[8]^=t,e[9]^=r,e[18]^=t,e[19]^=r,e[28]^=t,e[29]^=r,e[38]^=t,e[39]^=r,e[48]^=t,e[49]^=r,p=e[0],b=e[1],G=e[11]<<4|e[10]>>>28,W=e[10]<<4|e[11]>>>28,U=e[20]<<3|e[21]>>>29,j=e[21]<<3|e[20]>>>29,ae=e[31]<<9|e[30]>>>23,se=e[30]<<9|e[31]>>>23,H=e[40]<<18|e[41]>>>14,z=e[41]<<18|e[40]>>>14,N=e[2]<<1|e[3]>>>31,P=e[3]<<1|e[2]>>>31,y=e[13]<<12|e[12]>>>20,m=e[12]<<12|e[13]>>>20,X=e[22]<<10|e[23]>>>22,J=e[23]<<10|e[22]>>>22,I=e[33]<<13|e[32]>>>19,T=e[32]<<13|e[33]>>>19,ue=e[42]<<2|e[43]>>>30,ce=e[43]<<2|e[42]>>>30,ee=e[5]<<30|e[4]>>>2,te=e[4]<<30|e[5]>>>2,R=e[14]<<6|e[15]>>>26,O=e[15]<<6|e[14]>>>26,v=e[25]<<11|e[24]>>>21,g=e[24]<<11|e[25]>>>21,Z=e[34]<<15|e[35]>>>17,$=e[35]<<15|e[34]>>>17,C=e[45]<<29|e[44]>>>3,B=e[44]<<29|e[45]>>>3,M=e[6]<<28|e[7]>>>4,k=e[7]<<28|e[6]>>>4,re=e[17]<<23|e[16]>>>9,ne=e[16]<<23|e[17]>>>9,L=e[26]<<25|e[27]>>>7,q=e[27]<<25|e[26]>>>7,w=e[36]<<21|e[37]>>>11,_=e[37]<<21|e[36]>>>11,Y=e[47]<<24|e[46]>>>8,Q=e[46]<<24|e[47]>>>8,K=e[8]<<27|e[9]>>>5,V=e[9]<<27|e[8]>>>5,E=e[18]<<20|e[19]>>>12,S=e[19]<<20|e[18]>>>12,ie=e[29]<<7|e[28]>>>25,oe=e[28]<<7|e[29]>>>25,D=e[38]<<8|e[39]>>>24,F=e[39]<<8|e[38]>>>24,A=e[48]<<14|e[49]>>>18,x=e[49]<<14|e[48]>>>18,e[0]=p^~y&v,e[1]=b^~m&g,e[10]=M^~E&U,e[11]=k^~S&j,e[20]=N^~R&L,e[21]=P^~O&q,e[30]=K^~G&X,e[31]=V^~W&J,e[40]=ee^~re&ie,e[41]=te^~ne&oe,e[2]=y^~v&w,e[3]=m^~g&_,e[12]=E^~U&I,e[13]=S^~j&T,e[22]=R^~L&D,e[23]=O^~q&F,e[32]=G^~X&Z,e[33]=W^~J&$,e[42]=re^~ie&ae,e[43]=ne^~oe&se,e[4]=v^~w&A,e[5]=g^~_&x,e[14]=U^~I&C,e[15]=j^~T&B,e[24]=L^~D&H,e[25]=q^~F&z,e[34]=X^~Z&Y,e[35]=J^~$&Q,e[44]=ie^~ae&ue,e[45]=oe^~se&ce,e[6]=w^~A&p,e[7]=_^~x&b,e[16]=I^~C&M,e[17]=T^~B&k,e[26]=D^~H&N,e[27]=F^~z&P,e[36]=Z^~Y&K,e[37]=$^~Q&V,e[46]=ae^~ue&ee,e[47]=se^~ce&te,e[8]=A^~p&y,e[9]=x^~b&m,e[18]=C^~M&E,e[19]=B^~k&S,e[28]=H^~N&R,e[29]=z^~P&O,e[38]=Y^~K&G,e[39]=Q^~V&W,e[48]=ue^~ee&re,e[49]=ce^~te&ne,e[0]^=fe[n],e[1]^=fe[n+1]};if(t)_.exports=c;else for(d=0;d>>26-a&67108863,26<=(a+=24)&&(a-=26,i++);else if("le"===r)for(i=n=0;n>>26-a&67108863,26<=(a+=24)&&(a-=26,i++);return this.strip()},m.prototype._parseHex=function(e,t){this.length=Math.ceil((e.length-t)/6),this.words=new Array(this.length);for(var r=0;r>>26-o&4194303,26<=(o+=24)&&(o-=26,n++);r+6!==t&&(i=a(e,t,r+6),this.words[n]|=i<>>26-o&4194303),this.strip()},m.prototype._parseBase=function(e,t,r){this.words=[0];for(var n=0,i=this.length=1;i<=67108863;i*=t)n++;n--,i=i/t|0;for(var o=e.length-r,a=o%n,s=Math.min(o,o-a)+r,u=0,c=r;c"};var d=["","0","00","000","0000","00000","000000","0000000","00000000","000000000","0000000000","00000000000","000000000000","0000000000000","00000000000000","000000000000000","0000000000000000","00000000000000000","000000000000000000","0000000000000000000","00000000000000000000","000000000000000000000","0000000000000000000000","00000000000000000000000","000000000000000000000000","0000000000000000000000000"],l=[0,0,25,16,12,11,10,9,8,8,7,7,7,7,6,6,6,6,6,6,6,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5],p=[0,0,33554432,43046721,16777216,48828125,60466176,40353607,16777216,43046721,1e7,19487171,35831808,62748517,7529536,11390625,16777216,24137569,34012224,47045881,64e6,4084101,5153632,6436343,7962624,9765625,11881376,14348907,17210368,20511149,243e5,28629151,33554432,39135393,45435424,52521875,60466176];function i(e,t,r){r.negative=t.negative^e.negative;var n=e.length+t.length|0;n=(r.length=n)-1|0;var i=0|e.words[0],o=0|t.words[0],a=i*o,s=67108863&a,u=a/67108864|0;r.words[0]=s;for(var c=1;c>>26,h=67108863&u,d=Math.min(c,t.length-1),l=Math.max(0,c-e.length+1);l<=d;l++){var p=c-l|0;f+=(a=(i=0|e.words[p])*(o=0|t.words[l])+h)/67108864|0,h=67108863&a}r.words[c]=0|h,u=0|f}return 0!==u?r.words[c]=0|u:r.length--,r.strip()}m.prototype.toString=function(e,t){var r;if(t=0|t||1,16===(e=e||10)||"hex"===e){r="";for(var n=0,i=0,o=0;o>>24-n&16777215)||o!==this.length-1?d[6-s.length]+s+r:s+r,26<=(n+=2)&&(n-=26,o--)}for(0!==i&&(r=i.toString(16)+r);r.length%t!=0;)r="0"+r;return 0!==this.negative&&(r="-"+r),r}if(e===(0|e)&&2<=e&&e<=36){var u=l[e],c=p[e];r="";var f=this.clone();for(f.negative=0;!f.isZero();){var h=f.modn(c).toString(e);r=(f=f.idivn(c)).isZero()?h+r:d[u-h.length]+h+r}for(this.isZero()&&(r="0"+r);r.length%t!=0;)r="0"+r;return 0!==this.negative&&(r="-"+r),r}y(!1,"Base should be between 2 and 36")},m.prototype.toNumber=function(){var e=this.words[0];return 2===this.length?e+=67108864*this.words[1]:3===this.length&&1===this.words[2]?e+=4503599627370496+67108864*this.words[1]:2>>=13),64<=t&&(r+=7,t>>>=7),8<=t&&(r+=4,t>>>=4),2<=t&&(r+=2,t>>>=2),r+t},m.prototype._zeroBits=function(e){if(0===e)return 26;var t=e,r=0;return 0==(8191&t)&&(r+=13,t>>>=13),0==(127&t)&&(r+=7,t>>>=7),0==(15&t)&&(r+=4,t>>>=4),0==(3&t)&&(r+=2,t>>>=2),0==(1&t)&&r++,r},m.prototype.bitLength=function(){var e=this.words[this.length-1],t=this._countBits(e);return 26*(this.length-1)+t},m.prototype.zeroBits=function(){if(this.isZero())return 0;for(var e=0,t=0;te.length?this.clone().ior(e):e.clone().ior(this)},m.prototype.uor=function(e){return this.length>e.length?this.clone().iuor(e):e.clone().iuor(this)},m.prototype.iuand=function(e){var t;t=this.length>e.length?e:this;for(var r=0;re.length?this.clone().iand(e):e.clone().iand(this)},m.prototype.uand=function(e){return this.length>e.length?this.clone().iuand(e):e.clone().iuand(this)},m.prototype.iuxor=function(e){var t,r;this.length>e.length?(t=this,r=e):(t=e,r=this);for(var n=0;ne.length?this.clone().ixor(e):e.clone().ixor(this)},m.prototype.uxor=function(e){return this.length>e.length?this.clone().iuxor(e):e.clone().iuxor(this)},m.prototype.inotn=function(e){y("number"==typeof e&&0<=e);var t=0|Math.ceil(e/26),r=e%26;this._expand(t),0>26-r),this.strip()},m.prototype.notn=function(e){return this.clone().inotn(e)},m.prototype.setn=function(e,t){y("number"==typeof e&&0<=e);var r=e/26|0,n=e%26;return this._expand(r+1),this.words[r]=t?this.words[r]|1<e.length?(r=this,n=e):(r=e,n=this);for(var i=0,o=0;o>>26;for(;0!==i&&o>>26;if(this.length=r.length,0!==i)this.words[this.length]=i,this.length++;else if(r!==this)for(;oe.length?this.clone().iadd(e):e.clone().iadd(this)},m.prototype.isub=function(e){if(0!==e.negative){e.negative=0;var t=this.iadd(e);return e.negative=1,t._normSign()}if(0!==this.negative)return this.negative=0,this.iadd(e),this.negative=1,this._normSign();var r,n,i=this.cmp(e);if(0===i)return this.negative=0,this.length=1,this.words[0]=0,this;0>26,this.words[a]=67108863&t;for(;0!==o&&a>26,this.words[a]=67108863&t;if(0===o&&a>>13,l=0|a[1],p=8191&l,b=l>>>13,y=0|a[2],m=8191&y,v=y>>>13,g=0|a[3],w=8191&g,_=g>>>13,A=0|a[4],x=8191&A,M=A>>>13,k=0|a[5],E=8191&k,S=k>>>13,U=0|a[6],j=8191&U,I=U>>>13,T=0|a[7],C=8191&T,B=T>>>13,N=0|a[8],P=8191&N,R=N>>>13,O=0|a[9],L=8191&O,q=O>>>13,D=0|s[0],F=8191&D,H=D>>>13,z=0|s[1],K=8191&z,V=z>>>13,G=0|s[2],W=8191&G,X=G>>>13,J=0|s[3],Z=8191&J,$=J>>>13,Y=0|s[4],Q=8191&Y,ee=Y>>>13,te=0|s[5],re=8191&te,ne=te>>>13,ie=0|s[6],oe=8191&ie,ae=ie>>>13,se=0|s[7],ue=8191&se,ce=se>>>13,fe=0|s[8],he=8191&fe,de=fe>>>13,le=0|s[9],pe=8191&le,be=le>>>13;r.negative=e.negative^t.negative,r.length=19;var ye=(c+(n=Math.imul(h,F))|0)+((8191&(i=(i=Math.imul(h,H))+Math.imul(d,F)|0))<<13)|0;c=((o=Math.imul(d,H))+(i>>>13)|0)+(ye>>>26)|0,ye&=67108863,n=Math.imul(p,F),i=(i=Math.imul(p,H))+Math.imul(b,F)|0,o=Math.imul(b,H);var me=(c+(n=n+Math.imul(h,K)|0)|0)+((8191&(i=(i=i+Math.imul(h,V)|0)+Math.imul(d,K)|0))<<13)|0;c=((o=o+Math.imul(d,V)|0)+(i>>>13)|0)+(me>>>26)|0,me&=67108863,n=Math.imul(m,F),i=(i=Math.imul(m,H))+Math.imul(v,F)|0,o=Math.imul(v,H),n=n+Math.imul(p,K)|0,i=(i=i+Math.imul(p,V)|0)+Math.imul(b,K)|0,o=o+Math.imul(b,V)|0;var ve=(c+(n=n+Math.imul(h,W)|0)|0)+((8191&(i=(i=i+Math.imul(h,X)|0)+Math.imul(d,W)|0))<<13)|0;c=((o=o+Math.imul(d,X)|0)+(i>>>13)|0)+(ve>>>26)|0,ve&=67108863,n=Math.imul(w,F),i=(i=Math.imul(w,H))+Math.imul(_,F)|0,o=Math.imul(_,H),n=n+Math.imul(m,K)|0,i=(i=i+Math.imul(m,V)|0)+Math.imul(v,K)|0,o=o+Math.imul(v,V)|0,n=n+Math.imul(p,W)|0,i=(i=i+Math.imul(p,X)|0)+Math.imul(b,W)|0,o=o+Math.imul(b,X)|0;var ge=(c+(n=n+Math.imul(h,Z)|0)|0)+((8191&(i=(i=i+Math.imul(h,$)|0)+Math.imul(d,Z)|0))<<13)|0;c=((o=o+Math.imul(d,$)|0)+(i>>>13)|0)+(ge>>>26)|0,ge&=67108863,n=Math.imul(x,F),i=(i=Math.imul(x,H))+Math.imul(M,F)|0,o=Math.imul(M,H),n=n+Math.imul(w,K)|0,i=(i=i+Math.imul(w,V)|0)+Math.imul(_,K)|0,o=o+Math.imul(_,V)|0,n=n+Math.imul(m,W)|0,i=(i=i+Math.imul(m,X)|0)+Math.imul(v,W)|0,o=o+Math.imul(v,X)|0,n=n+Math.imul(p,Z)|0,i=(i=i+Math.imul(p,$)|0)+Math.imul(b,Z)|0,o=o+Math.imul(b,$)|0;var we=(c+(n=n+Math.imul(h,Q)|0)|0)+((8191&(i=(i=i+Math.imul(h,ee)|0)+Math.imul(d,Q)|0))<<13)|0;c=((o=o+Math.imul(d,ee)|0)+(i>>>13)|0)+(we>>>26)|0,we&=67108863,n=Math.imul(E,F),i=(i=Math.imul(E,H))+Math.imul(S,F)|0,o=Math.imul(S,H),n=n+Math.imul(x,K)|0,i=(i=i+Math.imul(x,V)|0)+Math.imul(M,K)|0,o=o+Math.imul(M,V)|0,n=n+Math.imul(w,W)|0,i=(i=i+Math.imul(w,X)|0)+Math.imul(_,W)|0,o=o+Math.imul(_,X)|0,n=n+Math.imul(m,Z)|0,i=(i=i+Math.imul(m,$)|0)+Math.imul(v,Z)|0,o=o+Math.imul(v,$)|0,n=n+Math.imul(p,Q)|0,i=(i=i+Math.imul(p,ee)|0)+Math.imul(b,Q)|0,o=o+Math.imul(b,ee)|0;var _e=(c+(n=n+Math.imul(h,re)|0)|0)+((8191&(i=(i=i+Math.imul(h,ne)|0)+Math.imul(d,re)|0))<<13)|0;c=((o=o+Math.imul(d,ne)|0)+(i>>>13)|0)+(_e>>>26)|0,_e&=67108863,n=Math.imul(j,F),i=(i=Math.imul(j,H))+Math.imul(I,F)|0,o=Math.imul(I,H),n=n+Math.imul(E,K)|0,i=(i=i+Math.imul(E,V)|0)+Math.imul(S,K)|0,o=o+Math.imul(S,V)|0,n=n+Math.imul(x,W)|0,i=(i=i+Math.imul(x,X)|0)+Math.imul(M,W)|0,o=o+Math.imul(M,X)|0,n=n+Math.imul(w,Z)|0,i=(i=i+Math.imul(w,$)|0)+Math.imul(_,Z)|0,o=o+Math.imul(_,$)|0,n=n+Math.imul(m,Q)|0,i=(i=i+Math.imul(m,ee)|0)+Math.imul(v,Q)|0,o=o+Math.imul(v,ee)|0,n=n+Math.imul(p,re)|0,i=(i=i+Math.imul(p,ne)|0)+Math.imul(b,re)|0,o=o+Math.imul(b,ne)|0;var Ae=(c+(n=n+Math.imul(h,oe)|0)|0)+((8191&(i=(i=i+Math.imul(h,ae)|0)+Math.imul(d,oe)|0))<<13)|0;c=((o=o+Math.imul(d,ae)|0)+(i>>>13)|0)+(Ae>>>26)|0,Ae&=67108863,n=Math.imul(C,F),i=(i=Math.imul(C,H))+Math.imul(B,F)|0,o=Math.imul(B,H),n=n+Math.imul(j,K)|0,i=(i=i+Math.imul(j,V)|0)+Math.imul(I,K)|0,o=o+Math.imul(I,V)|0,n=n+Math.imul(E,W)|0,i=(i=i+Math.imul(E,X)|0)+Math.imul(S,W)|0,o=o+Math.imul(S,X)|0,n=n+Math.imul(x,Z)|0,i=(i=i+Math.imul(x,$)|0)+Math.imul(M,Z)|0,o=o+Math.imul(M,$)|0,n=n+Math.imul(w,Q)|0,i=(i=i+Math.imul(w,ee)|0)+Math.imul(_,Q)|0,o=o+Math.imul(_,ee)|0,n=n+Math.imul(m,re)|0,i=(i=i+Math.imul(m,ne)|0)+Math.imul(v,re)|0,o=o+Math.imul(v,ne)|0,n=n+Math.imul(p,oe)|0,i=(i=i+Math.imul(p,ae)|0)+Math.imul(b,oe)|0,o=o+Math.imul(b,ae)|0;var xe=(c+(n=n+Math.imul(h,ue)|0)|0)+((8191&(i=(i=i+Math.imul(h,ce)|0)+Math.imul(d,ue)|0))<<13)|0;c=((o=o+Math.imul(d,ce)|0)+(i>>>13)|0)+(xe>>>26)|0,xe&=67108863,n=Math.imul(P,F),i=(i=Math.imul(P,H))+Math.imul(R,F)|0,o=Math.imul(R,H),n=n+Math.imul(C,K)|0,i=(i=i+Math.imul(C,V)|0)+Math.imul(B,K)|0,o=o+Math.imul(B,V)|0,n=n+Math.imul(j,W)|0,i=(i=i+Math.imul(j,X)|0)+Math.imul(I,W)|0,o=o+Math.imul(I,X)|0,n=n+Math.imul(E,Z)|0,i=(i=i+Math.imul(E,$)|0)+Math.imul(S,Z)|0,o=o+Math.imul(S,$)|0,n=n+Math.imul(x,Q)|0,i=(i=i+Math.imul(x,ee)|0)+Math.imul(M,Q)|0,o=o+Math.imul(M,ee)|0,n=n+Math.imul(w,re)|0,i=(i=i+Math.imul(w,ne)|0)+Math.imul(_,re)|0,o=o+Math.imul(_,ne)|0,n=n+Math.imul(m,oe)|0,i=(i=i+Math.imul(m,ae)|0)+Math.imul(v,oe)|0,o=o+Math.imul(v,ae)|0,n=n+Math.imul(p,ue)|0,i=(i=i+Math.imul(p,ce)|0)+Math.imul(b,ue)|0,o=o+Math.imul(b,ce)|0;var Me=(c+(n=n+Math.imul(h,he)|0)|0)+((8191&(i=(i=i+Math.imul(h,de)|0)+Math.imul(d,he)|0))<<13)|0;c=((o=o+Math.imul(d,de)|0)+(i>>>13)|0)+(Me>>>26)|0,Me&=67108863,n=Math.imul(L,F),i=(i=Math.imul(L,H))+Math.imul(q,F)|0,o=Math.imul(q,H),n=n+Math.imul(P,K)|0,i=(i=i+Math.imul(P,V)|0)+Math.imul(R,K)|0,o=o+Math.imul(R,V)|0,n=n+Math.imul(C,W)|0,i=(i=i+Math.imul(C,X)|0)+Math.imul(B,W)|0,o=o+Math.imul(B,X)|0,n=n+Math.imul(j,Z)|0,i=(i=i+Math.imul(j,$)|0)+Math.imul(I,Z)|0,o=o+Math.imul(I,$)|0,n=n+Math.imul(E,Q)|0,i=(i=i+Math.imul(E,ee)|0)+Math.imul(S,Q)|0,o=o+Math.imul(S,ee)|0,n=n+Math.imul(x,re)|0,i=(i=i+Math.imul(x,ne)|0)+Math.imul(M,re)|0,o=o+Math.imul(M,ne)|0,n=n+Math.imul(w,oe)|0,i=(i=i+Math.imul(w,ae)|0)+Math.imul(_,oe)|0,o=o+Math.imul(_,ae)|0,n=n+Math.imul(m,ue)|0,i=(i=i+Math.imul(m,ce)|0)+Math.imul(v,ue)|0,o=o+Math.imul(v,ce)|0,n=n+Math.imul(p,he)|0,i=(i=i+Math.imul(p,de)|0)+Math.imul(b,he)|0,o=o+Math.imul(b,de)|0;var ke=(c+(n=n+Math.imul(h,pe)|0)|0)+((8191&(i=(i=i+Math.imul(h,be)|0)+Math.imul(d,pe)|0))<<13)|0;c=((o=o+Math.imul(d,be)|0)+(i>>>13)|0)+(ke>>>26)|0,ke&=67108863,n=Math.imul(L,K),i=(i=Math.imul(L,V))+Math.imul(q,K)|0,o=Math.imul(q,V),n=n+Math.imul(P,W)|0,i=(i=i+Math.imul(P,X)|0)+Math.imul(R,W)|0,o=o+Math.imul(R,X)|0,n=n+Math.imul(C,Z)|0,i=(i=i+Math.imul(C,$)|0)+Math.imul(B,Z)|0,o=o+Math.imul(B,$)|0,n=n+Math.imul(j,Q)|0,i=(i=i+Math.imul(j,ee)|0)+Math.imul(I,Q)|0,o=o+Math.imul(I,ee)|0,n=n+Math.imul(E,re)|0,i=(i=i+Math.imul(E,ne)|0)+Math.imul(S,re)|0,o=o+Math.imul(S,ne)|0,n=n+Math.imul(x,oe)|0,i=(i=i+Math.imul(x,ae)|0)+Math.imul(M,oe)|0,o=o+Math.imul(M,ae)|0,n=n+Math.imul(w,ue)|0,i=(i=i+Math.imul(w,ce)|0)+Math.imul(_,ue)|0,o=o+Math.imul(_,ce)|0,n=n+Math.imul(m,he)|0,i=(i=i+Math.imul(m,de)|0)+Math.imul(v,he)|0,o=o+Math.imul(v,de)|0;var Ee=(c+(n=n+Math.imul(p,pe)|0)|0)+((8191&(i=(i=i+Math.imul(p,be)|0)+Math.imul(b,pe)|0))<<13)|0;c=((o=o+Math.imul(b,be)|0)+(i>>>13)|0)+(Ee>>>26)|0,Ee&=67108863,n=Math.imul(L,W),i=(i=Math.imul(L,X))+Math.imul(q,W)|0,o=Math.imul(q,X),n=n+Math.imul(P,Z)|0,i=(i=i+Math.imul(P,$)|0)+Math.imul(R,Z)|0,o=o+Math.imul(R,$)|0,n=n+Math.imul(C,Q)|0,i=(i=i+Math.imul(C,ee)|0)+Math.imul(B,Q)|0,o=o+Math.imul(B,ee)|0,n=n+Math.imul(j,re)|0,i=(i=i+Math.imul(j,ne)|0)+Math.imul(I,re)|0,o=o+Math.imul(I,ne)|0,n=n+Math.imul(E,oe)|0,i=(i=i+Math.imul(E,ae)|0)+Math.imul(S,oe)|0,o=o+Math.imul(S,ae)|0,n=n+Math.imul(x,ue)|0,i=(i=i+Math.imul(x,ce)|0)+Math.imul(M,ue)|0,o=o+Math.imul(M,ce)|0,n=n+Math.imul(w,he)|0,i=(i=i+Math.imul(w,de)|0)+Math.imul(_,he)|0,o=o+Math.imul(_,de)|0;var Se=(c+(n=n+Math.imul(m,pe)|0)|0)+((8191&(i=(i=i+Math.imul(m,be)|0)+Math.imul(v,pe)|0))<<13)|0;c=((o=o+Math.imul(v,be)|0)+(i>>>13)|0)+(Se>>>26)|0,Se&=67108863,n=Math.imul(L,Z),i=(i=Math.imul(L,$))+Math.imul(q,Z)|0,o=Math.imul(q,$),n=n+Math.imul(P,Q)|0,i=(i=i+Math.imul(P,ee)|0)+Math.imul(R,Q)|0,o=o+Math.imul(R,ee)|0,n=n+Math.imul(C,re)|0,i=(i=i+Math.imul(C,ne)|0)+Math.imul(B,re)|0,o=o+Math.imul(B,ne)|0,n=n+Math.imul(j,oe)|0,i=(i=i+Math.imul(j,ae)|0)+Math.imul(I,oe)|0,o=o+Math.imul(I,ae)|0,n=n+Math.imul(E,ue)|0,i=(i=i+Math.imul(E,ce)|0)+Math.imul(S,ue)|0,o=o+Math.imul(S,ce)|0,n=n+Math.imul(x,he)|0,i=(i=i+Math.imul(x,de)|0)+Math.imul(M,he)|0,o=o+Math.imul(M,de)|0;var Ue=(c+(n=n+Math.imul(w,pe)|0)|0)+((8191&(i=(i=i+Math.imul(w,be)|0)+Math.imul(_,pe)|0))<<13)|0;c=((o=o+Math.imul(_,be)|0)+(i>>>13)|0)+(Ue>>>26)|0,Ue&=67108863,n=Math.imul(L,Q),i=(i=Math.imul(L,ee))+Math.imul(q,Q)|0,o=Math.imul(q,ee),n=n+Math.imul(P,re)|0,i=(i=i+Math.imul(P,ne)|0)+Math.imul(R,re)|0,o=o+Math.imul(R,ne)|0,n=n+Math.imul(C,oe)|0,i=(i=i+Math.imul(C,ae)|0)+Math.imul(B,oe)|0,o=o+Math.imul(B,ae)|0,n=n+Math.imul(j,ue)|0,i=(i=i+Math.imul(j,ce)|0)+Math.imul(I,ue)|0,o=o+Math.imul(I,ce)|0,n=n+Math.imul(E,he)|0,i=(i=i+Math.imul(E,de)|0)+Math.imul(S,he)|0,o=o+Math.imul(S,de)|0;var je=(c+(n=n+Math.imul(x,pe)|0)|0)+((8191&(i=(i=i+Math.imul(x,be)|0)+Math.imul(M,pe)|0))<<13)|0;c=((o=o+Math.imul(M,be)|0)+(i>>>13)|0)+(je>>>26)|0,je&=67108863,n=Math.imul(L,re),i=(i=Math.imul(L,ne))+Math.imul(q,re)|0,o=Math.imul(q,ne),n=n+Math.imul(P,oe)|0,i=(i=i+Math.imul(P,ae)|0)+Math.imul(R,oe)|0,o=o+Math.imul(R,ae)|0,n=n+Math.imul(C,ue)|0,i=(i=i+Math.imul(C,ce)|0)+Math.imul(B,ue)|0,o=o+Math.imul(B,ce)|0,n=n+Math.imul(j,he)|0,i=(i=i+Math.imul(j,de)|0)+Math.imul(I,he)|0,o=o+Math.imul(I,de)|0;var Ie=(c+(n=n+Math.imul(E,pe)|0)|0)+((8191&(i=(i=i+Math.imul(E,be)|0)+Math.imul(S,pe)|0))<<13)|0;c=((o=o+Math.imul(S,be)|0)+(i>>>13)|0)+(Ie>>>26)|0,Ie&=67108863,n=Math.imul(L,oe),i=(i=Math.imul(L,ae))+Math.imul(q,oe)|0,o=Math.imul(q,ae),n=n+Math.imul(P,ue)|0,i=(i=i+Math.imul(P,ce)|0)+Math.imul(R,ue)|0,o=o+Math.imul(R,ce)|0,n=n+Math.imul(C,he)|0,i=(i=i+Math.imul(C,de)|0)+Math.imul(B,he)|0,o=o+Math.imul(B,de)|0;var Te=(c+(n=n+Math.imul(j,pe)|0)|0)+((8191&(i=(i=i+Math.imul(j,be)|0)+Math.imul(I,pe)|0))<<13)|0;c=((o=o+Math.imul(I,be)|0)+(i>>>13)|0)+(Te>>>26)|0,Te&=67108863,n=Math.imul(L,ue),i=(i=Math.imul(L,ce))+Math.imul(q,ue)|0,o=Math.imul(q,ce),n=n+Math.imul(P,he)|0,i=(i=i+Math.imul(P,de)|0)+Math.imul(R,he)|0,o=o+Math.imul(R,de)|0;var Ce=(c+(n=n+Math.imul(C,pe)|0)|0)+((8191&(i=(i=i+Math.imul(C,be)|0)+Math.imul(B,pe)|0))<<13)|0;c=((o=o+Math.imul(B,be)|0)+(i>>>13)|0)+(Ce>>>26)|0,Ce&=67108863,n=Math.imul(L,he),i=(i=Math.imul(L,de))+Math.imul(q,he)|0,o=Math.imul(q,de);var Be=(c+(n=n+Math.imul(P,pe)|0)|0)+((8191&(i=(i=i+Math.imul(P,be)|0)+Math.imul(R,pe)|0))<<13)|0;c=((o=o+Math.imul(R,be)|0)+(i>>>13)|0)+(Be>>>26)|0,Be&=67108863;var Ne=(c+(n=Math.imul(L,pe))|0)+((8191&(i=(i=Math.imul(L,be))+Math.imul(q,pe)|0))<<13)|0;return c=((o=Math.imul(q,be))+(i>>>13)|0)+(Ne>>>26)|0,Ne&=67108863,u[0]=ye,u[1]=me,u[2]=ve,u[3]=ge,u[4]=we,u[5]=_e,u[6]=Ae,u[7]=xe,u[8]=Me,u[9]=ke,u[10]=Ee,u[11]=Se,u[12]=Ue,u[13]=je,u[14]=Ie,u[15]=Te,u[16]=Ce,u[17]=Be,u[18]=Ne,0!==c&&(u[19]=c,r.length++),r};function s(e,t,r){return(new u).mulp(e,t,r)}function u(e,t){this.x=e,this.y=t}Math.imul||(o=i),m.prototype.mulTo=function(e,t){var r=this.length+e.length;return 10===this.length&&10===e.length?o(this,e,t):r<63?i(this,e,t):r<1024?function(e,t,r){r.negative=t.negative^e.negative,r.length=e.length+t.length;for(var n=0,i=0,o=0;o>>26)|0)>>>26,a&=67108863}r.words[o]=s,n=a,a=i}return 0!==n?r.words[o]=n:r.length--,r.strip()}(this,e,t):s(this,e,t)},u.prototype.makeRBT=function(e){for(var t=new Array(e),r=m.prototype._countBits(e)-1,n=0;n>=1;return n},u.prototype.permute=function(e,t,r,n,i,o){for(var a=0;a>>=1)i++;return 1<>>=13,r[2*o+1]=8191&i,i>>>=13;for(o=2*t;o>=26,t+=n/67108864|0,t+=i>>>26,this.words[r]=67108863&i}return 0!==t&&(this.words[r]=t,this.length++),this},m.prototype.muln=function(e){return this.clone().imuln(e)},m.prototype.sqr=function(){return this.mul(this)},m.prototype.isqr=function(){return this.imul(this.clone())},m.prototype.pow=function(e){var t=function(e){for(var t=new Array(e.bitLength()),r=0;r>>i}return t}(e);if(0===t.length)return new m(1);for(var r=this,n=0;n>>26-r<<26-r;if(0!==r){var o=0;for(t=0;t>>26-r}o&&(this.words[t]=o,this.length++)}if(0!==n){for(t=this.length-1;0<=t;t--)this.words[t+n]=this.words[t];for(t=0;t>>i<o)for(this.length-=o,u=0;u>>i,c=f&a}return s&&0!==c&&(s.words[s.length++]=c),0===this.length&&(this.words[0]=0,this.length=1),this.strip()},m.prototype.ishrn=function(e,t,r){return y(0===this.negative),this.iushrn(e,t,r)},m.prototype.shln=function(e){return this.clone().ishln(e)},m.prototype.ushln=function(e){return this.clone().iushln(e)},m.prototype.shrn=function(e){return this.clone().ishrn(e)},m.prototype.ushrn=function(e){return this.clone().iushrn(e)},m.prototype.testn=function(e){y("number"==typeof e&&0<=e);var t=e%26,r=(e-t)/26,n=1<>>t<>26)-(s/67108864|0),this.words[n+r]=67108863&i}for(;n>26,this.words[n+r]=67108863&i;if(0===a)return this.strip();for(y(-1===a),n=a=0;n>26,this.words[n]=67108863&i;return this.negative=1,this.strip()},m.prototype._wordDiv=function(e,t){var r=(this.length,e.length),n=this.clone(),i=e,o=0|i.words[i.length-1];0!==(r=26-this._countBits(o))&&(i=i.ushln(r),n.iushln(r),o=0|i.words[i.length-1]);var a,s=n.length-i.length;if("mod"!==t){(a=new m(null)).length=s+1,a.words=new Array(a.length);for(var u=0;uthis.length||this.cmp(e)<0?{div:new m(0),mod:this}:1===e.length?"div"===t?{div:this.divn(e.words[0]),mod:null}:"mod"===t?{div:null,mod:new m(this.modn(e.words[0]))}:{div:this.divn(e.words[0]),mod:new m(this.modn(e.words[0]))}:this._wordDiv(e,t);var n,i,o},m.prototype.div=function(e){return this.divmod(e,"div",!1).div},m.prototype.mod=function(e){return this.divmod(e,"mod",!1).mod},m.prototype.umod=function(e){return this.divmod(e,"mod",!0).mod},m.prototype.divRound=function(e){var t=this.divmod(e);if(t.mod.isZero())return t.div;var r=0!==t.div.negative?t.mod.isub(e):t.mod,n=e.ushrn(1),i=e.andln(1),o=r.cmp(n);return o<0||1===i&&0===o?t.div:0!==t.div.negative?t.div.isubn(1):t.div.iaddn(1)},m.prototype.modn=function(e){y(e<=67108863);for(var t=(1<<26)%e,r=0,n=this.length-1;0<=n;n--)r=(t*r+(0|this.words[n]))%e;return r},m.prototype.idivn=function(e){y(e<=67108863);for(var t=0,r=this.length-1;0<=r;r--){var n=(0|this.words[r])+67108864*t;this.words[r]=n/e|0,t=n%e}return this.strip()},m.prototype.divn=function(e){return this.clone().idivn(e)},m.prototype.egcd=function(e){y(0===e.negative),y(!e.isZero());var t=this,r=e.clone();t=0!==t.negative?t.umod(e):t.clone();for(var n=new m(1),i=new m(0),o=new m(0),a=new m(1),s=0;t.isEven()&&r.isEven();)t.iushrn(1),r.iushrn(1),++s;for(var u=r.clone(),c=t.clone();!t.isZero();){for(var f=0,h=1;0==(t.words[0]&h)&&f<26;++f,h<<=1);if(0>>26,a&=67108863,this.words[o]=a}return 0!==i&&(this.words[o]=i,this.length++),this},m.prototype.isZero=function(){return 1===this.length&&0===this.words[0]},m.prototype.cmpn=function(e){var t,r=e<0;if(0!==this.negative&&!r)return-1;if(0===this.negative&&r)return 1;if(this.strip(),1e.length)return 1;if(this.lengththis.n;);var n=t>>22,i=o}i>>>=22,0===(e.words[n-10]=i)&&10>>=26,e.words[r]=i,t=n}return 0!==t&&(e.words[e.length++]=t),e},m._prime=function(e){if(c[e])return c[e];var t;if("k256"===e)t=new b;else if("p224"===e)t=new v;else if("p192"===e)t=new g;else{if("p25519"!==e)throw new Error("Unknown prime "+e);t=new w}return c[e]=t},_.prototype._verify1=function(e){y(0===e.negative,"red works only with positives"),y(e.red,"red works only with red numbers")},_.prototype._verify2=function(e,t){y(0==(e.negative|t.negative),"red works only with positives"),y(e.red&&e.red===t.red,"red works only with red numbers")},_.prototype.imod=function(e){return this.prime?this.prime.ireduce(e)._forceRed(this):e.umod(this.m)._forceRed(this)},_.prototype.neg=function(e){return e.isZero()?e.clone():this.m.sub(e)._forceRed(this)},_.prototype.add=function(e,t){this._verify2(e,t);var r=e.add(t);return 0<=r.cmp(this.m)&&r.isub(this.m),r._forceRed(this)},_.prototype.iadd=function(e,t){this._verify2(e,t);var r=e.iadd(t);return 0<=r.cmp(this.m)&&r.isub(this.m),r},_.prototype.sub=function(e,t){this._verify2(e,t);var r=e.sub(t);return r.cmpn(0)<0&&r.iadd(this.m),r._forceRed(this)},_.prototype.isub=function(e,t){this._verify2(e,t);var r=e.isub(t);return r.cmpn(0)<0&&r.iadd(this.m),r},_.prototype.shl=function(e,t){return this._verify1(e),this.imod(e.ushln(t))},_.prototype.imul=function(e,t){return this._verify2(e,t),this.imod(e.imul(t))},_.prototype.mul=function(e,t){return this._verify2(e,t),this.imod(e.mul(t))},_.prototype.isqr=function(e){return this.imul(e,e.clone())},_.prototype.sqr=function(e){return this.mul(e,e)},_.prototype.sqrt=function(e){if(e.isZero())return e.clone();var t=this.m.andln(3);if(y(t%2==1),3===t){var r=this.m.add(new m(1)).iushrn(2);return this.pow(e,r)}for(var n=this.m.subn(1),i=0;!n.isZero()&&0===n.andln(1);)i++,n.iushrn(1);y(!n.isZero());var o=new m(1).toRed(this),a=o.redNeg(),s=this.m.subn(1).iushrn(1),u=this.m.bitLength();for(u=new m(2*u*u).toRed(this);0!==this.pow(u,s).cmp(a);)u.redIAdd(a);for(var c=this.pow(u,n),f=this.pow(e,n.addn(1).iushrn(1)),h=this.pow(e,n),d=i;0!==h.cmp(o);){for(var l=h,p=0;0!==l.cmp(o);p++)l=l.redSqr();y(p>c&1;i!==r[0]&&(i=this.sqr(i)),0!==f||0!==o?(o<<=1,o|=f,(4===++a||0===n&&0===c)&&(i=this.mul(i,r[o]),o=a=0)):a=0}s=26}return i},_.prototype.convertTo=function(e){var t=e.umod(this.m);return t===e?t.clone():t},_.prototype.convertFrom=function(e){var t=e.clone();return t.red=null,t},m.mont=function(e){return new A(e)},r(A,_),A.prototype.convertTo=function(e){return this.imod(e.ushln(this.shift))},A.prototype.convertFrom=function(e){var t=this.imod(e.mul(this.rinv));return t.red=null,t},A.prototype.imul=function(e,t){if(e.isZero()||t.isZero())return e.words[0]=0,e.length=1,e;var r=e.imul(t),n=r.maskn(this.shift).mul(this.minv).imaskn(this.shift).mul(this.m),i=r.isub(n).iushrn(this.shift),o=i;return 0<=i.cmp(this.m)?o=i.isub(this.m):i.cmpn(0)<0&&(o=i.iadd(this.m)),o._forceRed(this)},A.prototype.mul=function(e,t){if(e.isZero()||t.isZero())return new m(0)._forceRed(this);var r=e.mul(t),n=r.maskn(this.shift).mul(this.minv).imaskn(this.shift).mul(this.m),i=r.isub(n).iushrn(this.shift),o=i;return 0<=i.cmp(this.m)?o=i.isub(this.m):i.cmpn(0)<0&&(o=i.iadd(this.m)),o._forceRed(this)},A.prototype.invm=function(e){return this.imod(e._invmp(this.m).mul(this.r2))._forceRed(this)}}(void 0===e||e,this)},{}],388:[function(e,t,r){var n=e("web3-utils"),i=e("bn.js"),o=function(e){var r="A".charCodeAt(0),n="Z".charCodeAt(0);return(e=(e=e.toUpperCase()).substr(4)+e.substr(0,4)).split("").map(function(e){var t=e.charCodeAt(0);return r<=t&&t<=n?t-r+10:e}).join("")},a=function(e){for(var t,r=e;2e.highestBlock-200&&(t._isSyncing=!1,t.emit("changed",t._isSyncing),s.isFunction(t.callback)&&t.callback(null,t._isSyncing,t))},500))}}}})];a.forEach(function(e){e.attachToObject(t),e.setRequestManager(t._requestManager,t.accounts),e.defaultBlock=t.defaultBlock,e.defaultAccount=t.defaultAccount})};u.addProviders(i),t.exports=i},{"./getNetworkType.js":391,underscore:390,"web3-core":218,"web3-core-helpers":200,"web3-core-method":202,"web3-core-subscriptions":215,"web3-eth-abi":232,"web3-eth-accounts":372,"web3-eth-contract":374,"web3-eth-ens":383,"web3-eth-iban":388,"web3-eth-personal":389,"web3-net":393,"web3-utils":419}],393:[function(e,t,r){var n=e("web3-core"),i=e("web3-core-method"),o=e("web3-utils"),a=function(){var t=this;n.packageInit(this,arguments),[new i({name:"getId",call:"net_version",params:0,outputFormatter:o.hexToNumber}),new i({name:"isListening",call:"net_listening",params:0}),new i({name:"getPeerCount",call:"net_peerCount",params:0,outputFormatter:o.hexToNumber})].forEach(function(e){e.attachToObject(t),e.setRequestManager(t._requestManager)})};n.addProviders(a),t.exports=a},{"web3-core":218,"web3-core-method":202,"web3-utils":419}],394:[function(e,t,r){!function(){function i(e,t,r,n){return this instanceof i?(this.domain=e||void 0,this.path=t||"/",this.secure=!!r,this.script=!!n,this):new i(e,t,r,n)}function u(e,t,r){return e instanceof u?e:this instanceof u?(this.name=null,this.value=null,this.expiration_date=1/0,this.path=String(r||"/"),this.explicit_path=!1,this.domain=t||null,this.explicit_domain=!1,this.secure=!1,this.noscript=!1,e&&this.parse(e,t,r),this):new u(e,t,r)}i.All=Object.freeze(Object.create(null)),r.CookieAccessInfo=i,(r.Cookie=u).prototype.toString=function(){var e=[this.name+"="+this.value];return this.expiration_date!==1/0&&e.push("expires="+new Date(this.expiration_date).toGMTString()),this.domain&&e.push("domain="+this.domain),this.path&&e.push("path="+this.path),this.secure&&e.push("secure"),this.noscript&&e.push("httponly"),e.join("; ")},u.prototype.toValueString=function(){return this.name+"="+this.value};var a=/[:](?=\s*[a-zA-Z0-9_\-]+\s*[=])/g;function e(){var o,a;return this instanceof e?(o=Object.create(null),this.setCookie=function(e,t,r){var n,i;if(n=(e=new u(e,t,r)).expiration_date<=Date.now(),void 0!==o[e.name]){for(a=o[e.name],i=0;ih&&(G("Max buffer length exceeded: textNode"),i=Math.max(i,N.length)),P.length>h&&(G("Max buffer length exceeded: numberNode"),i=Math.max(i,P.length)),B=h-i+z);var i}),e(se).on(function(){if(L==l)return c({}),f(),void(O=!0);L===p&&0===H||G("Unexpected end");N!==J&&(c(N),f(),N=J);O=!0})}var e,t,o,B,N,P,R,O,L,q,D,F=(e=l(function(e){return e.unshift(/^/),(t=RegExp(e.map(n("source")).join(""))).exec.bind(t);var t}),B=e(t=/(\$?)/,/([\w-_]+|\*)/,o=/(?:{([\w ]*?)})?/),N=e(t,/\["([^"]+)"\]/,o),P=e(t,/\[(\d+|\*)\]/,o),R=e(t,/()/,/{([\w ]*?)}/),O=e(/\.\./),L=e(/\./),q=e(t,/!/),D=e(/$/),function(e){return e(m(B,N,P,R),O,L,q,D)});function H(e,t){return{key:e,node:t}}var z=n("key"),K=n("node"),V={};function G(e){var i=e(Y).emit,t=e(Q).emit,u=e(ie).emit,r=e(ne).emit;function c(e,t,r){K(S(e))[t]=r}function f(e,t,r){e&&c(e,t,r);var n=E(H(t,r),e);return i(n),n}var n={};return n[fe]=function(e,t){if(!e)return u(t),f(e,V,t);var r,n,i,o=(n=t,i=K(S(r=e)),_(h,i)?f(r,A(i),n):r),a=U(o),s=z(S(o));return c(a,s,t),E(H(s,t),a)},n[he]=function(e){return t(e),U(e)||r(K(S(e)))},n[ce]=f,n}var W=F(function(e,t,r,n,i){var o=y(z,S),a=y(K,S);function s(e,t){return!!t[1]?v(e,S):e}function u(e){if(e==w)return w;return v(function(e){return o(e)!=V},y(e,U))}function c(){return function(e){return o(e)==V}}function f(e,t,r,n,i){var o,a=e(r);if(a){var s=(o=a,T(function(e,t){return t(e,o)},n,t));return i(r.substr(A(a[0])),s)}}function h(e,t){return b(f,e,t)}var d=m(h(e,I(s,function(e,t){var r=t[3];return r?v(y(b(k,j(r.split(/\W+/))),a),e):e},function(e,t){var r=t[2];return v(r&&"*"!=r?function(e){return o(e)==r}:w,e)},u)),h(t,I(function(e){if(e==w)return w;var t=c(),r=e,n=u(function(e){return i(e)}),i=m(t,r,n);return i})),h(r,I()),h(n,I(s,c)),h(i,I(function(r){return function(e){var t=r(e);return!0===t?S(e):t}})),function(e){throw X('"'+e+'" could not be tokenised')});function l(e,t){return t}function p(e,t){return d(e,t,e?p:l)}return function(t){try{return p(t,w)}catch(e){throw X('Could not compile "'+t+'" because '+e.message)}}});function Z(n,i,r){var o,a;function s(t){return function(e){return e.id==t}}return{on:function(e,t){var r={listener:e,id:t||e};return i&&i.emit(n,e,r.id),o=E(r,o),a=E(e,a),this},emit:function(){!function e(t,r){t&&(S(t).apply(null,r),e(U(t),r))}(a,arguments)},un:function(e){var t;o=f(o,s(e),function(e){t=e}),t&&(a=f(a,function(e){return e==t.listener}),r&&r.emit(n,t.listener,t.id))},listeners:function(){return a},hasListener:function(e){return M(function e(t,r){return r&&(t(S(r))?S(r):e(t,U(r)))}(e?s(e):w,o))}}}var $=1,Y=$++,Q=$++,ee=$++,te=$++,re="fail",ne=$++,ie=$++,oe="start",ae="data",se="end",ue=$++,ce=$++,fe=$++,he=$++;function de(e,t,r){try{var n=u.parse(t)}catch(e){}return{statusCode:e,body:t,jsonBody:n,thrown:r}}function le(n,i){var o={node:n(Q),path:n(Y)};function a(t,r,o){var a=n(t).emit;r.on(function(e){var t,r,n,i=o(e);!1!==i&&(t=a,r=K(i),n=C(e),t(r,s(U(c(z,n))),s(c(K,n))))},t),n("removeListener").on(function(e){e==t&&(n(e).listeners()||r.un(t))})}n("newListener").on(function(e){var t=/(node|path):(.*)/.exec(e);if(t){var r=o[t[1]];r.hasListener(e)||a(e,r,i(t[2]))}})}function pe(o,e){var i,a=/^(node|path):./,s=o(ne),u=o(te).emit,c=o(ee).emit,t=l(function(e,t){if(i[e])d(t,i[e]);else{var r=o(e),n=t[0];a.test(e)?f(r,n):r.on(n)}return i});function f(t,e,r){r=r||e;var n=h(e);return t.on(function(){var e=!1;i.forget=function(){e=!0},d(arguments,n),delete i.forget,e&&t.un(r)},r),i}function h(e){return function(){try{return e.apply(i,arguments)}catch(e){setTimeout(function(){throw e})}}}function n(e,t,r){var n,i;"node"==e?(i=r,n=function(){var e=i.apply(this,arguments);M(e)&&(e==me.drop?u():c(e))}):n=r,f(o(e+":"+t),n,r)}function r(e,t,r){return x(t)?n(e,t,r):function(e,t){for(var r in t)n(e,r,t[r])}(e,t),i}return o(ie).on(function(e){var t;i.root=(t=e,function(){return t})}),o(oe).on(function(e,t){i.header=function(e){return e?t[e]:t}}),i={on:t,addListener:t,removeListener:function(e,t,r){if("done"==e)s.un(t);else if("node"==e||"path"==e)o.un(e+":"+t,r);else{var n=t;o(e).un(n)}return i},emit:o.emit,node:b(r,"node"),path:b(r,"path"),done:b(f,s),start:b(function(e,t){return o(e).on(h(t),t),i},oe),fail:o(re).on,abort:o(ue).emit,header:g,root:g,source:e}}function be(e,t,r,n,i){var o=function(){var t={},r=i("newListener"),n=i("removeListener");function i(e){return t[e]=Z(e,r,n)}function o(e){return t[e]||i(e)}return["emit","on","un"].forEach(function(r){o[r]=l(function(e,t){d(t,o(e)[r])})}),o}();return t&&function(t,n,e,r,i,o,a){var s,u=t(ae).emit,c=t(re).emit,f=0,h=!0;function d(){var e=n.responseText,t=e.substr(f);t&&u(t),f=A(e)}t(ue).on(function(){n.onreadystatechange=null,n.abort()}),"onprogress"in n&&(n.onprogress=d),n.onreadystatechange=function(){function e(){try{h&&t(oe).emit(n.status,(e=n.getAllResponseHeaders(),r={},e&&e.split("\r\n").forEach(function(e){var t=e.indexOf(": ");r[e.substring(0,t)]=e.substring(t+2)}),r)),h=!1}catch(e){}var e,r}switch(n.readyState){case 2:case 3:return e();case 4:e(),2==String(n.status)[0]?(d(),t(se).emit()):c(de(n.status,n.responseText))}};try{for(var l in n.open(e,r,!0),o)n.setRequestHeader(l,o[l]);(function(t,e){function r(e){return e.port||{"http:":80,"https:":443}[e.protocol||t.protocol]}return!!(e.protocol&&e.protocol!=t.protocol||e.host&&e.host!=t.host||e.host&&r(e)!=r(t))})(p.location,{protocol:(s=/(\w+:)?(?:\/\/)([\w.-]+)?(?::(\d+))?\/?/.exec(r)||[])[1]||"",host:s[2]||"",port:s[3]||""})||n.setRequestHeader("X-Requested-With","XMLHttpRequest"),n.withCredentials=a,n.send(i)}catch(e){p.setTimeout(b(c,de(J,J,e)),0)}}(o,new XMLHttpRequest,e,t,r,n,i),a(o),function(t,r){var i,n={};function e(t){return function(e){i=t(i,e)}}for(var o in r)t(o).on(e(r[o]),n);t(ee).on(function(e){var t=S(i),r=z(t),n=U(i);n&&(K(S(n))[r]=e)}),t(te).on(function(){var e=S(i),t=z(e),r=U(i);r&&delete K(S(r))[t]}),t(ue).on(function(){for(var e in r)t(e).un(n)})}(o,G(o)),le(o,W),pe(o,t)}function ye(e,t,r,n,i,o,a){return i=i?u.parse(u.stringify(i)):{},n?x(n)||(n=u.stringify(n),i["Content-Type"]=i["Content-Type"]||"application/json"):n=null,e(r||"GET",(s=t,!1===a&&(-1==s.indexOf("?")?s+="?":s+="&",s+="_="+(new Date).getTime()),s),n,i,o||!1);var s}function me(e){var t=I("resume","pause","pipe"),r=b(k,t);return e?r(e)||x(e)?ye(be,e):ye(be,e.url,e.method,e.body,e.headers,e.withCredentials,e.cached):be()}me.drop=function(){return me.drop},"function"==typeof define&&define.amd?define("oboe",[],function(){return me}):"object"===(void 0===ge?"undefined":_typeof(ge))?ve.exports=me:p.oboe=me}(function(){try{return window}catch(e){return self}}(),Object,Array,Error,JSON)},{}],403:[function(e,t,r){arguments[4][187][0].apply(r,arguments)},{dup:187}],404:[function(e,t,r){var i=e("underscore"),o=e("web3-core-helpers").errors,a=e("oboe"),n=function(e,t){var n=this;this.responseCallbacks={},this.notificationCallbacks=[],this.path=e,this.connected=!1,this.connection=t.connect({path:this.path}),this.addDefaultEvents();var r=function(t){var r=null;i.isArray(t)?t.forEach(function(e){n.responseCallbacks[e.id]&&(r=e.id)}):r=t.id,r||-1===t.method.indexOf("_subscription")?n.responseCallbacks[r]&&(n.responseCallbacks[r](null,t),delete n.responseCallbacks[r]):n.notificationCallbacks.forEach(function(e){i.isFunction(e)&&e(t)})};"Socket"===t.constructor.name?a(this.connection).done(r):this.connection.on("data",function(e){n._parseResponse(e.toString()).forEach(r)})};n.prototype.addDefaultEvents=function(){var e=this;this.connection.on("connect",function(){e.connected=!0}),this.connection.on("close",function(){e.connected=!1}),this.connection.on("error",function(){e._timeout()}),this.connection.on("end",function(){e._timeout()}),this.connection.on("timeout",function(){e._timeout()})},n.prototype._parseResponse=function(e){var r=this,n=[];return e.replace(/\}[\n\r]?\{/g,"}|--|{").replace(/\}\][\n\r]?\[\{/g,"}]|--|[{").replace(/\}[\n\r]?\[\{/g,"}|--|[{").replace(/\}\][\n\r]?\{/g,"}]|--|{").split("|--|").forEach(function(t){r.lastChunk&&(t=r.lastChunk+t);var e=null;try{e=JSON.parse(t)}catch(e){return r.lastChunk=t,clearTimeout(r.lastChunkTimeout),void(r.lastChunkTimeout=setTimeout(function(){throw r._timeout(),o.InvalidResponse(t)},15e3))}clearTimeout(r.lastChunkTimeout),r.lastChunk=null,e&&n.push(e)}),n},n.prototype._addResponseCallback=function(e,t){var r=e.id||e[0].id,n=e.method||e[0].method;this.responseCallbacks[r]=t,this.responseCallbacks[r].method=n},n.prototype._timeout=function(){for(var e in this.responseCallbacks)this.responseCallbacks.hasOwnProperty(e)&&(this.responseCallbacks[e](o.InvalidConnection("on IPC")),delete this.responseCallbacks[e])},n.prototype.reconnect=function(){this.connection.connect({path:this.path})},n.prototype.send=function(e,t){this.connection.writable||this.connection.connect({path:this.path}),this.connection.write(JSON.stringify(e)),this._addResponseCallback(e,t)},n.prototype.on=function(e,t){if("function"!=typeof t)throw new Error("The second parameter callback must be a function.");switch(e){case"data":this.notificationCallbacks.push(t);break;default:this.connection.on(e,t)}},n.prototype.once=function(e,t){if("function"!=typeof t)throw new Error("The second parameter callback must be a function.");this.connection.once(e,t)},n.prototype.removeListener=function(e,r){var n=this;switch(e){case"data":this.notificationCallbacks.forEach(function(e,t){e===r&&n.notificationCallbacks.splice(t,1)});break;default:this.connection.removeListener(e,r)}},n.prototype.removeAllListeners=function(e){switch(e){case"data":this.notificationCallbacks=[];break;default:this.connection.removeAllListeners(e)}},n.prototype.reset=function(){this._timeout(),this.notificationCallbacks=[],this.connection.removeAllListeners("error"),this.connection.removeAllListeners("end"),this.connection.removeAllListeners("timeout"),this.addDefaultEvents()},t.exports=n},{oboe:402,underscore:403,"web3-core-helpers":200}],405:[function(e,t,r){arguments[4][187][0].apply(r,arguments)},{dup:187}],406:[function(i,a,e){(function(t){var s=i("underscore"),o=i("web3-core-helpers").errors,u=null,c=null,f=null;if("undefined"!=typeof window&&void 0!==window.WebSocket)u=function(e,t){return new window.WebSocket(e,t)},c=btoa,f=function(e){return new URL(e)};else{u=i("websocket").w3cwebsocket,c=function(e){return t(e).toString("base64")};var e=i("url");if(e.URL){var r=e.URL;f=function(e){return new r(e)}}else f=i("url").parse}var n=function(e,t){var n=this;this.responseCallbacks={},this.notificationCallbacks=[],t=t||{},this._customTimeout=t.timeout;var r=f(e),i=t.headers||{},o=t.protocol||void 0;r.username&&r.password&&(i.authorization="Basic "+c(r.username+":"+r.password));var a=t.clientConfig||void 0;r.auth&&(i.authorization="Basic "+c(r.auth)),this.connection=new u(e,o,void 0,i,void 0,a),this.addDefaultEvents(),this.connection.onmessage=function(e){var t="string"==typeof e.data?e.data:"";n._parseResponse(t).forEach(function(t){var r=null;s.isArray(t)?t.forEach(function(e){n.responseCallbacks[e.id]&&(r=e.id)}):r=t.id,!r&&t&&t.method&&-1!==t.method.indexOf("_subscription")?n.notificationCallbacks.forEach(function(e){s.isFunction(e)&&e(t)}):n.responseCallbacks[r]&&(n.responseCallbacks[r](null,t),delete n.responseCallbacks[r])})},Object.defineProperty(this,"connected",{get:function(){return this.connection&&this.connection.readyState===this.connection.OPEN},enumerable:!0})};n.prototype.addDefaultEvents=function(){var e=this;this.connection.onerror=function(){e._timeout()},this.connection.onclose=function(){e._timeout(),e.reset()}},n.prototype._parseResponse=function(e){var r=this,n=[];return e.replace(/\}[\n\r]?\{/g,"}|--|{").replace(/\}\][\n\r]?\[\{/g,"}]|--|[{").replace(/\}[\n\r]?\[\{/g,"}|--|[{").replace(/\}\][\n\r]?\{/g,"}]|--|{").split("|--|").forEach(function(t){r.lastChunk&&(t=r.lastChunk+t);var e=null;try{e=JSON.parse(t)}catch(e){return r.lastChunk=t,clearTimeout(r.lastChunkTimeout),void(r.lastChunkTimeout=setTimeout(function(){throw r._timeout(),o.InvalidResponse(t)},15e3))}clearTimeout(r.lastChunkTimeout),r.lastChunk=null,e&&n.push(e)}),n},n.prototype._addResponseCallback=function(e,t){var r=e.id||e[0].id,n=e.method||e[0].method;this.responseCallbacks[r]=t,this.responseCallbacks[r].method=n;var i=this;this._customTimeout&&setTimeout(function(){i.responseCallbacks[r]&&(i.responseCallbacks[r](o.ConnectionTimeout(i._customTimeout)),delete i.responseCallbacks[r])},this._customTimeout)},n.prototype._timeout=function(){for(var e in this.responseCallbacks)this.responseCallbacks.hasOwnProperty(e)&&(this.responseCallbacks[e](o.InvalidConnection("on WS")),delete this.responseCallbacks[e])},n.prototype.send=function(e,t){var r=this;if(this.connection.readyState!==this.connection.CONNECTING){if(this.connection.readyState!==this.connection.OPEN)return console.error("connection not open on send()"),"function"==typeof this.connection.onerror?this.connection.onerror(new Error("connection not open")):console.error("no error callback"),void t(new Error("connection not open"));this.connection.send(JSON.stringify(e)),this._addResponseCallback(e,t)}else setTimeout(function(){r.send(e,t)},10)},n.prototype.on=function(e,t){if("function"!=typeof t)throw new Error("The second parameter callback must be a function.");switch(e){case"data":this.notificationCallbacks.push(t);break;case"connect":this.connection.onopen=t;break;case"end":this.connection.onclose=t;break;case"error":this.connection.onerror=t}},n.prototype.removeListener=function(e,r){var n=this;switch(e){case"data":this.notificationCallbacks.forEach(function(e,t){e===r&&n.notificationCallbacks.splice(t,1)})}},n.prototype.removeAllListeners=function(e){switch(e){case"data":this.notificationCallbacks=[];break;case"connect":this.connection.onopen=null;break;case"end":this.connection.onclose=null;break;case"error":this.connection.onerror=null}},n.prototype.reset=function(){this._timeout(),this.notificationCallbacks=[],this.addDefaultEvents()},n.prototype.disconnect=function(){this.connection&&this.connection.close()},a.exports=n}).call(this,i("buffer").Buffer)},{buffer:47,underscore:405,url:166,"web3-core-helpers":200,websocket:45}],407:[function(e,t,r){var n=e("web3-core"),i=e("web3-core-subscriptions").subscriptions,o=e("web3-core-method"),a=e("web3-net"),s=function(){var t=this;n.packageInit(this,arguments);var e=this.setProvider;this.setProvider=function(){e.apply(t,arguments),t.net.setProvider.apply(t,arguments)},this.clearSubscriptions=t._requestManager.clearSubscriptions,this.net=new a(this.currentProvider),[new i({name:"subscribe",type:"shh",subscriptions:{messages:{params:1}}}),new o({name:"getVersion",call:"shh_version",params:0}),new o({name:"getInfo",call:"shh_info",params:0}),new o({name:"setMaxMessageSize",call:"shh_setMaxMessageSize",params:1}),new o({name:"setMinPoW",call:"shh_setMinPoW",params:1}),new o({name:"markTrustedPeer",call:"shh_markTrustedPeer",params:1}),new o({name:"newKeyPair",call:"shh_newKeyPair",params:0}),new o({name:"addPrivateKey",call:"shh_addPrivateKey",params:1}),new o({name:"deleteKeyPair",call:"shh_deleteKeyPair",params:1}),new o({name:"hasKeyPair",call:"shh_hasKeyPair",params:1}),new o({name:"getPublicKey",call:"shh_getPublicKey",params:1}),new o({name:"getPrivateKey",call:"shh_getPrivateKey",params:1}),new o({name:"newSymKey",call:"shh_newSymKey",params:0}),new o({name:"addSymKey",call:"shh_addSymKey",params:1}),new o({name:"generateSymKeyFromPassword",call:"shh_generateSymKeyFromPassword",params:1}),new o({name:"hasSymKey",call:"shh_hasSymKey",params:1}),new o({name:"getSymKey",call:"shh_getSymKey",params:1}),new o({name:"deleteSymKey",call:"shh_deleteSymKey",params:1}),new o({name:"newMessageFilter",call:"shh_newMessageFilter",params:1}),new o({name:"getFilterMessages",call:"shh_getFilterMessages",params:1}),new o({name:"deleteMessageFilter",call:"shh_deleteMessageFilter",params:1}),new o({name:"post",call:"shh_post",params:1,inputFormatter:[null]}),new o({name:"unsubscribe",call:"shh_unsubscribe",params:1})].forEach(function(e){e.attachToObject(t),e.setRequestManager(t._requestManager)})};n.addProviders(s),t.exports=s},{"web3-core":218,"web3-core-method":202,"web3-core-subscriptions":215,"web3-net":393}],408:[function(e,t,r){arguments[4][387][0].apply(r,arguments)},{dup:387}],409:[function(e,t,r){arguments[4][174][0].apply(r,arguments)},{dup:174}],410:[function(e,t,r){var f=e("bn.js"),h=e("number-to-bn"),d=new f(0),l=new f(-1),p={noether:"0",wei:"1",kwei:"1000",Kwei:"1000",babbage:"1000",femtoether:"1000",mwei:"1000000",Mwei:"1000000",lovelace:"1000000",picoether:"1000000",gwei:"1000000000",Gwei:"1000000000",shannon:"1000000000",nanoether:"1000000000",nano:"1000000000",szabo:"1000000000000",microether:"1000000000000",micro:"1000000000000",finney:"1000000000000000",milliether:"1000000000000000",milli:"1000000000000000",ether:"1000000000000000000",kether:"1000000000000000000000",grand:"1000000000000000000000",mether:"1000000000000000000000000",gether:"1000000000000000000000000000",tether:"1000000000000000000000000000000"};function b(e){var t=e?e.toLowerCase():"ether",r=p[t];if("string"!=typeof r)throw new Error("[ethjs-unit] the unit provided "+e+" doesn't exists, please use the one of the following units "+JSON.stringify(p,null,2));return new f(r,10)}function y(e){if("string"==typeof e){if(!e.match(/^-?[0-9.]+$/))throw new Error("while converting number to string, invalid number value '"+e+"', should be a number matching (^-?[0-9.]+).");return e}if("number"==typeof e)return String(e);if("object"===(void 0===e?"undefined":_typeof(e))&&e.toString&&(e.toTwos||e.dividedToIntegerBy))return e.toPrecision?String(e.toPrecision()):e.toString(10);throw new Error("while converting number to string, invalid number value '"+e+"' type "+(void 0===e?"undefined":_typeof(e))+".")}t.exports={unitMap:p,numberToString:y,getValueOfUnit:b,fromWei:function(e,t,r){var n=h(e),i=n.lt(d),o=b(t),a=p[t].length-1||1,s=r||{};i&&(n=n.mul(l));for(var u=n.mod(o).toString(10);u.lengthi)throw new Error("[ethjs-unit] while converting number "+e+" to wei, too many decimal places");for(;u.length>t&63|128)}function h(e){if(0==(4294967168&e))return s(e);var t="";return 0==(4294965248&e)?t=s(e>>6&31|192):0==(4294901760&e)?(c(e),t=s(e>>12&15|224),t+=f(e,6)):0==(4292870144&e)&&(t=s(e>>18&7|240),t+=f(e,12),t+=f(e,6)),t+=s(63&e|128)}function d(){if(o<=a)throw Error("Invalid byte index");var e=255&i[a];if(a++,128==(192&e))return 63&e;throw Error("Invalid continuation byte")}function l(){var e,t;if(o>>10&1023|55296),t=56320|1023&t),i+=s(t);return i}(r)}};if("function"==typeof define&&"object"==_typeof(define.amd)&&define.amd)define(function(){return p});else if(t&&!t.nodeType)if(r)r.exports=p;else{var b={}.hasOwnProperty;for(var y in p)b.call(p,y)&&(t[y]=p[y])}else e.utf8=p}(this)}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],419:[function(e,t,r){var s=e("underscore"),n=e("ethjs-unit"),o=e("./utils.js"),i=e("./soliditySha3.js"),a=e("randomhex"),u=function i(o,e){var a=[];return e.forEach(function(e){if("object"===_typeof(e.components)){if("tuple"!==e.type.substring(0,5))throw new Error("components found but type is not tuple; report on GitHub");var t="",r=e.type.indexOf("[");0<=r&&(t=e.type.substring(r));var n=i(o,e.components);s.isArray(n)&&o?a.push("tuple("+n.join(",")+")"+t):o?a.push("("+n+")"):a.push("("+n.join(",")+")"+t)}else a.push(e.type)}),a},c=function(e){if(!o.isHexStrict(e))throw new Error("The parameter must be a valid HEX string.");var t="",r=0,n=e.length;for("0x"===e.substring(0,2)&&(r=2);rn)throw new Error("Supplied uint exceeds width: "+n+" vs "+i.bitLength());if(i.lt(new s(0)))throw new Error("Supplied uint "+i.toString()+" is negative");return n?u.leftPad(i.toString("hex"),n/8*2):i}if(e.startsWith("int")){if(n%8||n<8||256n)throw new Error("Supplied int exceeds width: "+n+" vs "+i.bitLength());return i.lt(new s(0))?i.toTwos(n).toString("hex"):n?u.leftPad(i.toString("hex"),n/8*2):i}throw new Error("Unsupported or invalid type: "+e)},n=function(e){if(o.isArray(e))throw new Error("Autodetection of array types is not supported.");var t,r,n,i="";if(o.isObject(e)&&(e.hasOwnProperty("v")||e.hasOwnProperty("t")||e.hasOwnProperty("value")||e.hasOwnProperty("type"))?(t=e.hasOwnProperty("t")?e.t:e.type,i=e.hasOwnProperty("v")?e.v:e.value):(t=u.toHex(e,!0),i=u.toHex(e),t.startsWith("int")||t.startsWith("uint")||(t="bytes")),!t.startsWith("int")&&!t.startsWith("uint")||"string"!=typeof i||/^(-)?0x/i.test(i)||(i=new s(i)),o.isArray(i)){if(n=/^\D+\d*\[(\d+)\]$/.exec(t),(r=n?parseInt(n[1],10):null)&&i.length!==r)throw new Error(t+" is not matching the given array "+JSON.stringify(i));r=i.length}return o.isArray(i)?i.map(function(e){return a(t,e,r).toString("hex").replace("0x","")}).join(""):a(t,i,r).toString("hex").replace("0x","")};t.exports=function(){var e=Array.prototype.slice.call(arguments),t=o.map(e,n);return u.sha3("0x"+t.join(""))}},{"./utils.js":421,"bn.js":408,underscore:417}],421:[function(e,t,r){var n=e("underscore"),i=e("bn.js"),o=e("number-to-bn"),a=e("utf8"),s=e("eth-lib/lib/hash"),u=function(e){return e instanceof i||e&&e.constructor&&"BN"===e.constructor.name},c=function(e){return e&&e.constructor&&"BigNumber"===e.constructor.name},f=function(t){try{return o.apply(null,arguments)}catch(e){throw new Error(e+' Given value: "'+t+'"')}},h=function(e){return!!/^(0x)?[0-9a-f]{40}$/i.test(e)&&(!(!/^(0x|0X)?[0-9a-f]{40}$/.test(e)&&!/^(0x|0X)?[0-9A-F]{40}$/.test(e))||d(e))},d=function(e){e=e.replace(/^0x/i,"");for(var t=m(e.toLowerCase()).replace(/^0x/i,""),r=0;r<40;r++)if(7>>4).toString(16)),t.push((15&e[r]).toString(16));return"0x"+t.join("")},isHex:function(e){return(n.isString(e)||n.isNumber(e))&&/^(-0x|0x)?[0-9a-f]*$/i.test(e)},isHexStrict:y,leftPad:function(e,t,r){var n=/^0x/i.test(e)||"number"==typeof e,i=0<=t-(e=e.toString(16).replace(/^0x/i,"")).length+1?t-e.length+1:0;return(n?"0x":"")+new Array(i).join(r||"0")+e},rightPad:function(e,t,r){var n=/^0x/i.test(e)||"number"==typeof e,i=0<=t-(e=e.toString(16).replace(/^0x/i,"")).length+1?t-e.length+1:0;return(n?"0x":"")+e+new Array(i).join(r||"0")},toTwosComplement:function(e){return"0x"+f(e).toTwos(256).toString(16,64)},sha3:m}},{"bn.js":408,"eth-lib/lib/hash":409,"number-to-bn":412,underscore:417,utf8:418}],422:[function(e,t,r){t.exports={name:"web3",namespace:"ethereum",version:"1.0.0-beta.36",description:"Ethereum JavaScript API",repository:"https://github.com/ethereum/web3.js/tree/master/packages/web3",license:"LGPL-3.0",main:"src/index.js",bugs:{url:"https://github.com/ethereum/web3.js/issues"},keywords:["Ethereum","JavaScript","API"],author:"ethereum.org",authors:[{name:"Fabian Vogelsteller",email:"fabian@ethereum.org",homepage:"http://frozeman.de"},{name:"Marek Kotewicz",email:"marek@parity.io",url:"https://github.com/debris"},{name:"Marian Oancea",url:"https://github.com/cubedro"},{name:"Gav Wood",email:"g@parity.io",homepage:"http://gavwood.com"},{name:"Jeffery Wilcke",email:"jeffrey.wilcke@ethereum.org",url:"https://github.com/obscuren"}],dependencies:{"web3-bzz":"1.0.0-beta.36","web3-core":"1.0.0-beta.36","web3-eth":"1.0.0-beta.36","web3-eth-personal":"1.0.0-beta.36","web3-net":"1.0.0-beta.36","web3-shh":"1.0.0-beta.36","web3-utils":"1.0.0-beta.36"}}},{}],BN:[function(e,t,r){arguments[4][219][0].apply(r,arguments)},{buffer:17,dup:219}],Web3:[function(e,t,r){var i=e("../package.json").version,o=e("web3-core"),a=e("web3-eth"),n=e("web3-net"),s=e("web3-eth-personal"),u=e("web3-shh"),c=e("web3-bzz"),f=e("web3-utils"),h=function(){var r=this;o.packageInit(this,arguments),this.version=i,this.utils=f,this.eth=new a(this),this.shh=new u(this),this.bzz=new c(this);var n=this.setProvider;this.setProvider=function(e,t){return n.apply(r,arguments),this.eth.setProvider(e,t),this.shh.setProvider(e,t),this.bzz.setProvider(e),!0}};h.version=i,h.utils=f,h.modules={Eth:a,Net:n,Personal:s,Shh:u,Bzz:c},o.addProviders(h),t.exports=h},{"../package.json":422,"web3-bzz":196,"web3-core":218,"web3-eth":392,"web3-eth-personal":389,"web3-net":393,"web3-shh":407,"web3-utils":419}]},{},["Web3"])("Web3")}); \ No newline at end of file diff --git a/runtime/.gitkeep b/runtime/.gitkeep deleted file mode 100644 index 8b13789..0000000 --- a/runtime/.gitkeep +++ /dev/null @@ -1 +0,0 @@ - diff --git a/thinkphp/.gitignore b/thinkphp/.gitignore new file mode 100644 index 0000000..7e31ef5 --- /dev/null +++ b/thinkphp/.gitignore @@ -0,0 +1,4 @@ +/composer.lock +/vendor +.idea +.DS_Store diff --git a/thinkphp/.htaccess b/thinkphp/.htaccess new file mode 100644 index 0000000..3418e55 --- /dev/null +++ b/thinkphp/.htaccess @@ -0,0 +1 @@ +deny from all \ No newline at end of file diff --git a/thinkphp/.travis.yml b/thinkphp/.travis.yml new file mode 100644 index 0000000..f74ffca --- /dev/null +++ b/thinkphp/.travis.yml @@ -0,0 +1,47 @@ +sudo: false + +language: php + +services: + - memcached + - mongodb + - mysql + - postgresql + - redis-server + +matrix: + fast_finish: true + include: + - php: 5.4 + - php: 5.5 + - php: 5.6 + - php: 7.0 + - php: hhvm + allow_failures: + - php: hhvm + +cache: + directories: + - $HOME/.composer/cache + +before_install: + - composer self-update + - mysql -e "create database IF NOT EXISTS test;" -uroot + - psql -c 'DROP DATABASE IF EXISTS test;' -U postgres + - psql -c 'create database test;' -U postgres + +install: + - ./tests/script/install.sh + +script: + ## LINT + - find . -path ./vendor -prune -o -type f -name \*.php -exec php -l {} \; + ## PHP Copy/Paste Detector + - vendor/bin/phpcpd --verbose --exclude vendor ./ || true + ## PHPLOC + - vendor/bin/phploc --exclude vendor ./ + ## PHPUNIT + - vendor/bin/phpunit --coverage-clover=coverage.xml --configuration=phpunit.xml + +after_success: + - bash <(curl -s https://codecov.io/bash) diff --git a/thinkphp/CONTRIBUTING.md b/thinkphp/CONTRIBUTING.md new file mode 100644 index 0000000..dc8e91c --- /dev/null +++ b/thinkphp/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 5.4 5.5 5.6 7.0 和 HHVM 上测试修改,其中 HHVM 下的测试容许报错,请确保你的修改符合 PHP 5.4 ~ 5.6 和 PHP 7.0 的语法规范; +* 管理员不会合并造成 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,不需要重新提交 `pull request`,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 5.4 ~ 5.6 +- 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/thinkphp/LICENSE.txt b/thinkphp/LICENSE.txt new file mode 100644 index 0000000..2cb9a8a --- /dev/null +++ b/thinkphp/LICENSE.txt @@ -0,0 +1,32 @@ + +ThinkPHP遵循Apache2开源协议发布,并提供免费使用。 +版权所有Copyright © 2006-2017 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/thinkphp/README.md b/thinkphp/README.md new file mode 100644 index 0000000..8ba439c --- /dev/null +++ b/thinkphp/README.md @@ -0,0 +1,69 @@ +# ThinkPHP 5.0(FastAdmin 团队长期维护) + +## 维护原因 + +FastAdmin 致力于服务开发者,为开发者节省时间,为了 FastAdmin 开源项目持续发展下去。 + +## 参与开源 + +[贡献代码](https://doc.fastadmin.net/doc/contributing.html) + + +## 使用方法 + +- 本框架已经应用在 FastAdmin 后台框架中,在 FastAdmin 项目中使用 `composer update topthink/framework -vvv` 命令即可更新。 + +- 非 FastAdmin 的 ThinkPHP5.0 项目使用此仓库,请修改 `composer.json` 添加以下配置,在执行`composer update topthink/framework -vvv` 命令,具体可以参考 FastAdmin 项目目录下的 `composer.json` 文件内容。 + ``` + "repositories": [ + { + "type": "git", + "url": "https://gitee.com/fastadminnet/framework.git" + } + ] + ``` + +## 环境要求 + +php 7.1+ + + + +## ThinkPHP 介绍 + +ThinkPHP5 在保持快速开发和大道至简的核心理念不变的同时,优化核心,减少依赖,基于全新的架构思想和命名空间实现,是 ThinkPHP 突破原有框架思路的颠覆之作,其主要特性包括: + + + 基于命名空间和众多PHP新特性 + + 核心功能组件化 + + 强化路由功能 + + 更灵活的控制器 + + 重构的模型和数据库类 + + 配置文件可分离 + + 重写的自动验证和完成 + + 简化扩展机制 + + API支持完善 + + 改进的Log类 + + 命令行访问支持 + + REST支持 + + 引导文件支持 + + 方便的自动生成定义 + + 真正惰性加载 + + 分布式环境支持 + + 支持Composer + + 支持MongoDb + +详细开发文档参考 [ThinkPHP5完全开发手册](http://www.kancloud.cn/manual/thinkphp5) 以及[ThinkPHP5入门系列教程](http://www.kancloud.cn/special/thinkphp5_quickstart) + +## 版权信息 + +ThinkPHP遵循Apache2开源协议发布,并提供免费使用。 + +本项目包含的第三方源码和二进制文件之版权信息另行标注。 + +版权所有Copyright © 2006-2022 by ThinkPHP (http://thinkphp.cn) + +All rights reserved。 + +ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。 + +更多细节参阅 [LICENSE.txt](LICENSE.txt) diff --git a/thinkphp/base.php b/thinkphp/base.php new file mode 100644 index 0000000..92c4fa5 --- /dev/null +++ b/thinkphp/base.php @@ -0,0 +1,65 @@ + +// +---------------------------------------------------------------------- + +define('THINK_VERSION', '5.0.24'); +define('THINK_START_TIME', microtime(true)); +define('THINK_START_MEM', memory_get_usage()); +define('EXT', '.php'); +define('DS', DIRECTORY_SEPARATOR); +defined('THINK_PATH') or define('THINK_PATH', __DIR__ . DS); +define('LIB_PATH', THINK_PATH . 'library' . DS); +define('CORE_PATH', LIB_PATH . 'think' . DS); +define('TRAIT_PATH', LIB_PATH . 'traits' . DS); +defined('APP_PATH') or define('APP_PATH', dirname($_SERVER['SCRIPT_FILENAME']) . DS); +defined('ROOT_PATH') or define('ROOT_PATH', dirname(realpath(APP_PATH)) . DS); +defined('EXTEND_PATH') or define('EXTEND_PATH', ROOT_PATH . 'extend' . DS); +defined('VENDOR_PATH') or define('VENDOR_PATH', ROOT_PATH . 'vendor' . DS); +defined('RUNTIME_PATH') or define('RUNTIME_PATH', ROOT_PATH . 'runtime' . DS); +defined('LOG_PATH') or define('LOG_PATH', RUNTIME_PATH . 'log' . DS); +defined('CACHE_PATH') or define('CACHE_PATH', RUNTIME_PATH . 'cache' . DS); +defined('TEMP_PATH') or define('TEMP_PATH', RUNTIME_PATH . 'temp' . DS); +defined('CONF_PATH') or define('CONF_PATH', APP_PATH); // 配置文件目录 +defined('CONF_EXT') or define('CONF_EXT', EXT); // 配置文件后缀 +defined('ENV_PREFIX') or define('ENV_PREFIX', 'PHP_'); // 环境变量的配置前缀 + +// 环境常量 +define('IS_CLI', PHP_SAPI == 'cli' ? true : false); +define('IS_WIN', strpos(PHP_OS, 'WIN') !== false); + +// 载入Loader类 +require CORE_PATH . 'Loader.php'; + +// 加载环境变量配置文件 +if (is_file(ROOT_PATH . '.env')) { + $env = parse_ini_file(ROOT_PATH . '.env', true); + + foreach ($env as $key => $val) { + $name = ENV_PREFIX . strtoupper($key); + + if (is_array($val)) { + foreach ($val as $k => $v) { + $item = $name . '_' . strtoupper($k); + putenv("$item=$v"); + } + } else { + putenv("$name=$val"); + } + } +} + +// 注册自动加载 +\think\Loader::register(); + +// 注册错误和异常处理机制 +\think\Error::register(); + +// 加载惯例配置文件 +\think\Config::set(include THINK_PATH . 'convention' . EXT); diff --git a/thinkphp/codecov.yml b/thinkphp/codecov.yml new file mode 100644 index 0000000..bef9d64 --- /dev/null +++ b/thinkphp/codecov.yml @@ -0,0 +1,12 @@ +comment: + layout: header, changes, diff +coverage: + ignore: + - base.php + - helper.php + - convention.php + - lang/zh-cn.php + - start.php + - console.php + status: + patch: false diff --git a/thinkphp/composer.json b/thinkphp/composer.json new file mode 100644 index 0000000..caf6c7e --- /dev/null +++ b/thinkphp/composer.json @@ -0,0 +1,35 @@ +{ + "name": "topthink/framework", + "description": "the new thinkphp framework", + "type": "think-framework", + "keywords": [ + "framework", + "thinkphp", + "ORM" + ], + "homepage": "http://thinkphp.cn/", + "license": "Apache-2.0", + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "require": { + "php": ">=7.1.0", + "topthink/think-installer": "~1.0" + }, + "require-dev": { + "phpunit/phpunit": "4.8.*", + "johnkary/phpunit-speedtrap": "^1.0", + "mikey179/vfsstream": "~1.6", + "phploc/phploc": "2.*", + "sebastian/phpcpd": "2.*", + "phpdocumentor/reflection-docblock": "^2.0" + }, + "autoload": { + "psr-4": { + "think\\": "library/think" + } + } +} diff --git a/thinkphp/console.php b/thinkphp/console.php new file mode 100644 index 0000000..578e4a7 --- /dev/null +++ b/thinkphp/console.php @@ -0,0 +1,20 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +// ThinkPHP 引导文件 +// 加载基础文件 +require __DIR__ . '/base.php'; + +// 执行应用 +App::initCommon(); +Console::init(); diff --git a/thinkphp/convention.php b/thinkphp/convention.php new file mode 100644 index 0000000..31a0a0c --- /dev/null +++ b/thinkphp/convention.php @@ -0,0 +1,298 @@ + '', + // 应用调试模式 + 'app_debug' => false, + // 应用Trace + 'app_trace' => false, + // 应用模式状态 + 'app_status' => '', + // 是否支持多模块 + 'app_multi_module' => true, + // 入口自动绑定模块 + 'auto_bind_module' => false, + // 注册的根命名空间 + 'root_namespace' => [], + // 扩展函数文件 + 'extra_file_list' => [THINK_PATH . 'helper' . EXT], + // 默认输出类型 + 'default_return_type' => 'html', + // 默认AJAX 数据返回格式,可选json xml ... + 'default_ajax_return' => 'json', + // 默认JSONP格式返回的处理方法 + 'default_jsonp_handler' => 'jsonpReturn', + // 默认JSONP处理方法 + 'var_jsonp_handler' => 'callback', + // 默认时区 + 'default_timezone' => 'PRC', + // 是否开启多语言 + 'lang_switch_on' => false, + // 默认全局过滤方法 用逗号分隔多个 + 'default_filter' => '', + // 默认语言 + 'default_lang' => 'zh-cn', + // 应用类库后缀 + 'class_suffix' => false, + // 控制器类后缀 + 'controller_suffix' => false, + + // +---------------------------------------------------------------------- + // | 模块设置 + // +---------------------------------------------------------------------- + + // 默认模块名 + 'default_module' => 'index', + // 禁止访问模块 + 'deny_module_list' => ['common'], + // 默认控制器名 + 'default_controller' => 'Index', + // 默认操作名 + 'default_action' => 'index', + // 默认验证器 + 'default_validate' => '', + // 默认的空控制器名 + 'empty_controller' => 'Error', + // 操作方法前缀 + 'use_action_prefix' => false, + // 操作方法后缀 + 'action_suffix' => '', + // 自动搜索控制器 + 'controller_auto_search' => false, + + // +---------------------------------------------------------------------- + // | URL设置 + // +---------------------------------------------------------------------- + + // PATHINFO变量名 用于兼容模式 + 'var_pathinfo' => 's', + // 兼容PATH_INFO获取 + 'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'], + // pathinfo分隔符 + 'pathinfo_depr' => '/', + // HTTPS代理标识 + 'https_agent_name' => '', + // URL伪静态后缀 + 'url_html_suffix' => 'html', + // URL普通方式参数 用于自动生成 + 'url_common_param' => false, + // URL参数方式 0 按名称成对解析 1 按顺序解析 + 'url_param_type' => 0, + // 是否开启路由 + 'url_route_on' => true, + // 路由配置文件(支持配置多个) + 'route_config_file' => ['route'], + // 路由使用完整匹配 + 'route_complete_match' => false, + // 是否强制使用路由 + 'url_route_must' => false, + // 域名部署 + 'url_domain_deploy' => false, + // 域名根,如thinkphp.cn + 'url_domain_root' => '', + // 是否自动转换URL中的控制器和操作名 + 'url_convert' => true, + // 默认的访问控制器层 + 'url_controller_layer' => 'controller', + // 表单请求类型伪装变量 + 'var_method' => '_method', + // 表单ajax伪装变量 + 'var_ajax' => '_ajax', + // 表单pjax伪装变量 + 'var_pjax' => '_pjax', + // 是否开启请求缓存 true自动缓存 支持设置请求缓存规则 + 'request_cache' => false, + // 请求缓存有效期 + 'request_cache_expire' => null, + // 全局请求缓存排除规则 + 'request_cache_except' => [], + + // +---------------------------------------------------------------------- + // | 模板设置 + // +---------------------------------------------------------------------- + + 'template' => [ + // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 + 'auto_rule' => 1, + // 模板引擎类型 支持 php think 支持扩展 + 'type' => 'Think', + // 视图基础目录,配置目录为所有模块的视图起始目录 + 'view_base' => '', + // 当前模板的视图目录 留空为自动获取 + 'view_path' => '', + // 模板后缀 + 'view_suffix' => 'html', + // 模板文件名分隔符 + 'view_depr' => DS, + // 模板引擎普通标签开始标记 + 'tpl_begin' => '{', + // 模板引擎普通标签结束标记 + 'tpl_end' => '}', + // 标签库标签开始标记 + 'taglib_begin' => '{', + // 标签库标签结束标记 + 'taglib_end' => '}', + ], + + // 视图输出字符串内容替换 + 'view_replace_str' => [], + // 默认跳转页面对应的模板文件 + 'dispatch_success_tmpl' => THINK_PATH . 'tpl' . DS . 'dispatch_jump.tpl', + 'dispatch_error_tmpl' => THINK_PATH . 'tpl' . DS . 'dispatch_jump.tpl', + + // +---------------------------------------------------------------------- + // | 异常及错误设置 + // +---------------------------------------------------------------------- + + // 异常页面的模板文件 + 'exception_tmpl' => THINK_PATH . 'tpl' . DS . 'think_exception.tpl', + + // 错误显示信息,非调试模式有效 + 'error_message' => '页面错误!请稍后再试~', + // 显示错误信息 + 'show_error_msg' => false, + // 异常处理handle类 留空使用 \think\exception\Handle + 'exception_handle' => '', + // 是否记录trace信息到日志 + 'record_trace' => false, + + // +---------------------------------------------------------------------- + // | 日志设置 + // +---------------------------------------------------------------------- + + 'log' => [ + // 日志记录方式,内置 file socket 支持扩展 + 'type' => 'File', + // 日志保存目录 + 'path' => LOG_PATH, + // 日志记录级别 + 'level' => [], + ], + + // +---------------------------------------------------------------------- + // | Trace设置 开启 app_trace 后 有效 + // +---------------------------------------------------------------------- + 'trace' => [ + // 内置Html Console 支持扩展 + 'type' => 'Html', + ], + + // +---------------------------------------------------------------------- + // | 缓存设置 + // +---------------------------------------------------------------------- + + 'cache' => [ + // 驱动方式 + 'type' => 'File', + // 缓存保存目录 + 'path' => CACHE_PATH, + // 缓存前缀 + 'prefix' => '', + // 缓存有效期 0表示永久缓存 + 'expire' => 0, + ], + + // +---------------------------------------------------------------------- + // | 会话设置 + // +---------------------------------------------------------------------- + + 'session' => [ + 'id' => '', + // SESSION_ID的提交变量,解决flash上传跨域 + 'var_session_id' => '', + // SESSION 前缀 + 'prefix' => 'think', + // 驱动方式 支持redis memcache memcached + 'type' => '', + // 是否自动开启 SESSION + 'auto_start' => true, + 'httponly' => true, + 'secure' => false, + ], + + // +---------------------------------------------------------------------- + // | Cookie设置 + // +---------------------------------------------------------------------- + 'cookie' => [ + // cookie 名称前缀 + 'prefix' => '', + // cookie 保存时间 + 'expire' => 0, + // cookie 保存路径 + 'path' => '/', + // cookie 有效域名 + 'domain' => '', + // cookie 启用安全传输 + 'secure' => false, + // httponly设置 + 'httponly' => '', + // 是否使用 setcookie + 'setcookie' => true, + ], + + // +---------------------------------------------------------------------- + // | 数据库设置 + // +---------------------------------------------------------------------- + + 'database' => [ + // 数据库类型 + 'type' => 'mysql', + // 数据库连接DSN配置 + 'dsn' => '', + // 服务器地址 + 'hostname' => '127.0.0.1', + // 数据库名 + 'database' => '', + // 数据库用户名 + 'username' => 'root', + // 数据库密码 + 'password' => '', + // 数据库连接端口 + 'hostport' => '', + // 数据库连接参数 + 'params' => [], + // 数据库编码默认采用utf8 + 'charset' => 'utf8', + // 数据库表前缀 + 'prefix' => '', + // 数据库调试模式 + 'debug' => false, + // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) + 'deploy' => 0, + // 数据库读写是否分离 主从式有效 + 'rw_separate' => false, + // 读写分离后 主服务器数量 + 'master_num' => 1, + // 指定从服务器序号 + 'slave_no' => '', + // 是否严格检查字段是否存在 + 'fields_strict' => true, + // 数据集返回类型 + 'resultset_type' => 'array', + // 自动写入时间戳字段 + 'auto_timestamp' => false, + // 时间字段取出后的默认时间格式 + 'datetime_format' => 'Y-m-d H:i:s', + // 是否需要进行SQL性能分析 + 'sql_explain' => false, + ], + + //分页配置 + 'paginate' => [ + 'type' => 'bootstrap', + 'var_page' => 'page', + 'list_rows' => 15, + ], + + //控制台配置 + 'console' => [ + 'name' => 'Think Console', + 'version' => '0.1', + 'user' => null, + ], + +]; diff --git a/thinkphp/helper.php b/thinkphp/helper.php new file mode 100644 index 0000000..12683cf --- /dev/null +++ b/thinkphp/helper.php @@ -0,0 +1,589 @@ + +// +---------------------------------------------------------------------- + +//------------------------ +// ThinkPHP 助手函数 +//------------------------- + +use think\Cache; +use think\Config; +use think\Cookie; +use think\Db; +use think\Debug; +use think\exception\HttpException; +use think\exception\HttpResponseException; +use think\Lang; +use think\Loader; +use think\Log; +use think\Model; +use think\Request; +use think\Response; +use think\Session; +use think\Url; +use think\View; + +if (!function_exists('load_trait')) { + /** + * 快速导入Traits PHP5.5以上无需调用 + * @param string $class trait库 + * @param string $ext 类库后缀 + * @return boolean + */ + function load_trait($class, $ext = EXT) + { + return Loader::import($class, TRAIT_PATH, $ext); + } +} + +if (!function_exists('exception')) { + /** + * 抛出异常处理 + * + * @param string $msg 异常消息 + * @param integer $code 异常代码 默认为0 + * @param string $exception 异常类 + * + * @throws Exception + */ + function exception($msg, $code = 0, $exception = '') + { + $e = $exception ?: '\think\Exception'; + throw new $e($msg, $code); + } +} + +if (!function_exists('debug')) { + /** + * 记录时间(微秒)和内存使用情况 + * @param string $start 开始标签 + * @param string $end 结束标签 + * @param integer|string $dec 小数位 如果是m 表示统计内存占用 + * @return mixed + */ + function debug($start, $end = '', $dec = 6) + { + if ('' == $end) { + Debug::remark($start); + } else { + return 'm' == $dec ? Debug::getRangeMem($start, $end) : Debug::getRangeTime($start, $end, $dec); + } + } +} + +if (!function_exists('lang')) { + /** + * 获取语言变量值 + * @param string $name 语言变量名 + * @param array $vars 动态变量值 + * @param string $lang 语言 + * @return mixed + */ + function lang($name, $vars = [], $lang = '') + { + return Lang::get($name, $vars, $lang); + } +} + +if (!function_exists('config')) { + /** + * 获取和设置配置参数 + * @param string|array $name 参数名 + * @param mixed $value 参数值 + * @param string $range 作用域 + * @return mixed + */ + function config($name = '', $value = null, $range = '') + { + if (is_null($value) && is_string($name)) { + return 0 === strpos($name, '?') ? Config::has(substr($name, 1), $range) : Config::get($name, $range); + } else { + return Config::set($name, $value, $range); + } + } +} + +if (!function_exists('input')) { + /** + * 获取输入数据 支持默认值和过滤 + * @param string $key 获取的变量名 + * @param mixed $default 默认值 + * @param string $filter 过滤方法 + * @return mixed + */ + function input($key = '', $default = null, $filter = '') + { + if (0 === strpos($key, '?')) { + $key = substr($key, 1); + $has = true; + } + if ($pos = strpos($key, '.')) { + // 指定参数来源 + list($method, $key) = explode('.', $key, 2); + if (!in_array($method, ['get', 'post', 'put', 'patch', 'delete', 'route', 'param', 'request', 'session', 'cookie', 'server', 'env', 'path', 'file'])) { + $key = $method . '.' . $key; + $method = 'param'; + } + } else { + // 默认为自动判断 + $method = 'param'; + } + if (isset($has)) { + return request()->has($key, $method, $default); + } else { + return request()->$method($key, $default, $filter); + } + } +} + +if (!function_exists('widget')) { + /** + * 渲染输出Widget + * @param string $name Widget名称 + * @param array $data 传入的参数 + * @return mixed + */ + function widget($name, $data = []) + { + return Loader::action($name, $data, 'widget'); + } +} + +if (!function_exists('model')) { + /** + * 实例化Model + * @param string $name Model名称 + * @param string $layer 业务层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return \think\Model + */ + function model($name = '', $layer = 'model', $appendSuffix = false) + { + return Loader::model($name, $layer, $appendSuffix); + } +} + +if (!function_exists('validate')) { + /** + * 实例化验证器 + * @param string $name 验证器名称 + * @param string $layer 业务层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return \think\Validate + */ + function validate($name = '', $layer = 'validate', $appendSuffix = false) + { + return Loader::validate($name, $layer, $appendSuffix); + } +} + +if (!function_exists('db')) { + /** + * 实例化数据库类 + * @param string $name 操作的数据表名称(不含前缀) + * @param array|string $config 数据库配置参数 + * @param bool $force 是否强制重新连接 + * @return \think\db\Query + */ + function db($name = '', $config = [], $force = false) + { + return Db::connect($config, $force)->name($name); + } +} + +if (!function_exists('controller')) { + /** + * 实例化控制器 格式:[模块/]控制器 + * @param string $name 资源地址 + * @param string $layer 控制层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return \think\Controller + */ + function controller($name, $layer = 'controller', $appendSuffix = false) + { + return Loader::controller($name, $layer, $appendSuffix); + } +} + +if (!function_exists('action')) { + /** + * 调用模块的操作方法 参数格式 [模块/控制器/]操作 + * @param string $url 调用地址 + * @param string|array $vars 调用参数 支持字符串和数组 + * @param string $layer 要调用的控制层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return mixed + */ + function action($url, $vars = [], $layer = 'controller', $appendSuffix = false) + { + return Loader::action($url, $vars, $layer, $appendSuffix); + } +} + +if (!function_exists('import')) { + /** + * 导入所需的类库 同java的Import 本函数有缓存功能 + * @param string $class 类库命名空间字符串 + * @param string $baseUrl 起始路径 + * @param string $ext 导入的文件扩展名 + * @return boolean + */ + function import($class, $baseUrl = '', $ext = EXT) + { + return Loader::import($class, $baseUrl, $ext); + } +} + +if (!function_exists('vendor')) { + /** + * 快速导入第三方框架类库 所有第三方框架的类库文件统一放到 系统的Vendor目录下面 + * @param string $class 类库 + * @param string $ext 类库后缀 + * @return boolean + */ + function vendor($class, $ext = EXT) + { + return Loader::import($class, VENDOR_PATH, $ext); + } +} + +if (!function_exists('dump')) { + /** + * 浏览器友好的变量输出 + * @param mixed $var 变量 + * @param boolean $echo 是否输出 默认为true 如果为false 则返回输出字符串 + * @param string $label 标签 默认为空 + * @return void|string + */ + function dump($var, $echo = true, $label = null) + { + return Debug::dump($var, $echo, $label); + } +} + +if (!function_exists('url')) { + /** + * Url生成 + * @param string $url 路由地址 + * @param string|array $vars 变量 + * @param bool|string $suffix 生成的URL后缀 + * @param bool|string $domain 域名 + * @return string + */ + function url($url = '', $vars = '', $suffix = true, $domain = false) + { + return Url::build($url, $vars, $suffix, $domain); + } +} + +if (!function_exists('session')) { + /** + * Session管理 + * @param string|array $name session名称,如果为数组表示进行session设置 + * @param mixed $value session值 + * @param string $prefix 前缀 + * @return mixed + */ + function session($name, $value = '', $prefix = null) + { + if (is_array($name)) { + // 初始化 + Session::init($name); + } elseif (is_null($name)) { + // 清除 + Session::clear('' === $value ? null : $value); + } elseif ('' === $value) { + // 判断或获取 + return 0 === strpos($name, '?') ? Session::has(substr($name, 1), $prefix) : Session::get($name, $prefix); + } elseif (is_null($value)) { + // 删除 + return Session::delete($name, $prefix); + } else { + // 设置 + return Session::set($name, $value, $prefix); + } + } +} + +if (!function_exists('cookie')) { + /** + * Cookie管理 + * @param string|array $name cookie名称,如果为数组表示进行cookie设置 + * @param mixed $value cookie值 + * @param mixed $option 参数 + * @return mixed + */ + function cookie($name, $value = '', $option = null) + { + if (is_array($name)) { + // 初始化 + Cookie::init($name); + } elseif (is_null($name)) { + // 清除 + Cookie::clear($value); + } elseif ('' === $value) { + // 获取 + return 0 === strpos($name, '?') ? Cookie::has(substr($name, 1), $option) : Cookie::get($name, $option); + } elseif (is_null($value)) { + // 删除 + return Cookie::delete($name); + } else { + // 设置 + return Cookie::set($name, $value, $option); + } + } +} + +if (!function_exists('cache')) { + /** + * 缓存管理 + * @param mixed $name 缓存名称,如果为数组表示进行缓存设置 + * @param mixed $value 缓存值 + * @param mixed $options 缓存参数 + * @param string $tag 缓存标签 + * @return mixed + */ + function cache($name, $value = '', $options = null, $tag = null) + { + if (is_array($options)) { + // 缓存操作的同时初始化 + $cache = Cache::connect($options); + } elseif (is_array($name)) { + // 缓存初始化 + return Cache::connect($name); + } else { + $cache = Cache::init(); + } + + if (is_null($name)) { + return $cache->clear($value); + } elseif ('' === $value) { + // 获取缓存 + return 0 === strpos($name, '?') ? $cache->has(substr($name, 1)) : $cache->get($name); + } elseif (is_null($value)) { + // 删除缓存 + return $cache->rm($name); + } elseif (0 === strpos($name, '?') && '' !== $value) { + $expire = is_numeric($options) ? $options : null; + return $cache->remember(substr($name, 1), $value, $expire); + } else { + // 缓存数据 + if (is_array($options)) { + $expire = isset($options['expire']) ? $options['expire'] : null; //修复查询缓存无法设置过期时间 + } else { + $expire = is_numeric($options) ? $options : null; //默认快捷缓存设置过期时间 + } + if (is_null($tag)) { + return $cache->set($name, $value, $expire); + } else { + return $cache->tag($tag)->set($name, $value, $expire); + } + } + } +} + +if (!function_exists('trace')) { + /** + * 记录日志信息 + * @param mixed $log log信息 支持字符串和数组 + * @param string $level 日志级别 + * @return void|array + */ + function trace($log = '[think]', $level = 'log') + { + if ('[think]' === $log) { + return Log::getLog(); + } else { + Log::record($log, $level); + } + } +} + +if (!function_exists('request')) { + /** + * 获取当前Request对象实例 + * @return Request + */ + function request() + { + return Request::instance(); + } +} + +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') + { + return Response::create($data, $type, $code, $header); + } +} + +if (!function_exists('view')) { + /** + * 渲染模板输出 + * @param string $template 模板文件 + * @param array $vars 模板变量 + * @param array $replace 模板替换 + * @param integer $code 状态码 + * @return \think\response\View + */ + function view($template = '', $vars = [], $replace = [], $code = 200) + { + return Response::create($template, 'view', $code)->replace($replace)->assign($vars); + } +} + +if (!function_exists('json')) { + /** + * 获取\think\response\Json对象实例 + * @param mixed $data 返回的数据 + * @param integer $code 状态码 + * @param array $header 头部 + * @param array $options 参数 + * @return \think\response\Json + */ + function json($data = [], $code = 200, $header = [], $options = []) + { + return Response::create($data, 'json', $code, $header, $options); + } +} + +if (!function_exists('jsonp')) { + /** + * 获取\think\response\Jsonp对象实例 + * @param mixed $data 返回的数据 + * @param integer $code 状态码 + * @param array $header 头部 + * @param array $options 参数 + * @return \think\response\Jsonp + */ + function jsonp($data = [], $code = 200, $header = [], $options = []) + { + return Response::create($data, 'jsonp', $code, $header, $options); + } +} + +if (!function_exists('xml')) { + /** + * 获取\think\response\Xml对象实例 + * @param mixed $data 返回的数据 + * @param integer $code 状态码 + * @param array $header 头部 + * @param array $options 参数 + * @return \think\response\Xml + */ + function xml($data = [], $code = 200, $header = [], $options = []) + { + return Response::create($data, 'xml', $code, $header, $options); + } +} + +if (!function_exists('redirect')) { + /** + * 获取\think\response\Redirect对象实例 + * @param mixed $url 重定向地址 支持Url::build方法的地址 + * @param array|integer $params 额外参数 + * @param integer $code 状态码 + * @param array $with 隐式传参 + * @return \think\response\Redirect + */ + function redirect($url = [], $params = [], $code = 302, $with = []) + { + if (is_integer($params)) { + $code = $params; + $params = []; + } + return Response::create($url, 'redirect', $code)->params($params)->with($with); + } +} + +if (!function_exists('abort')) { + /** + * 抛出HTTP异常 + * @param integer|Response $code 状态码 或者 Response对象实例 + * @param string $message 错误信息 + * @param array $header 参数 + */ + function abort($code, $message = null, $header = []) + { + if ($code instanceof Response) { + throw new HttpResponseException($code); + } else { + throw new HttpException($code, $message, null, $header); + } + } +} + +if (!function_exists('halt')) { + /** + * 调试变量并且中断输出 + * @param mixed $var 调试变量或者信息 + */ + function halt($var) + { + dump($var); + throw new HttpResponseException(new Response); + } +} + +if (!function_exists('token')) { + /** + * 生成表单令牌 + * @param string $name 令牌名称 + * @param mixed $type 令牌生成方法 + * @return string + */ + function token($name = '__token__', $type = 'md5') + { + $token = Request::instance()->token($name, $type); + return ''; + } +} + +if (!function_exists('load_relation')) { + /** + * 延迟预载入关联查询 + * @param mixed $resultSet 数据集 + * @param mixed $relation 关联 + * @return array + */ + function load_relation($resultSet, $relation) + { + $item = current($resultSet); + if ($item instanceof Model) { + $item->eagerlyResultSet($resultSet, $relation); + } + return $resultSet; + } +} + +if (!function_exists('collection')) { + /** + * 数组转换为数据集对象 + * @param array $resultSet 数据集数组 + * @return \think\model\Collection|\think\Collection + */ + function collection($resultSet) + { + $item = current($resultSet); + if ($item instanceof Model) { + return \think\model\Collection::make($resultSet); + } else { + return \think\Collection::make($resultSet); + } + } +} diff --git a/thinkphp/lang/zh-cn.php b/thinkphp/lang/zh-cn.php new file mode 100644 index 0000000..eb7a914 --- /dev/null +++ b/thinkphp/lang/zh-cn.php @@ -0,0 +1,136 @@ + +// +---------------------------------------------------------------------- + +// 核心中文语言包 +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' => '方法不存在', + 'module not exists' => '模块不存在', + 'controller not exists' => '控制器不存在', + 'class not exists' => '类不存在', + 'property not exists' => '类的属性不存在', + 'template not exists' => '模板文件不存在', + 'illegal controller name' => '非法的控制器名称', + 'illegal action name' => '非法的操作名称', + 'url suffix deny' => '禁止的URL后缀访问', + 'Route Not Found' => '当前访问路由未定义', + 'Undefined db type' => '未定义数据库类型', + 'variable type error' => '变量类型错误', + 'PSR-4 error' => 'PSR-4 规范错误', + 'not support total' => '简洁模式下不能获取数据总数', + 'not support last' => '简洁模式下不能获取最后一页', + 'error session handler' => '错误的SESSION处理器类', + 'not allow php tag' => '模板不允许使用PHP语法', + 'not support' => '不支持', + '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' => '查询表达式错误', + 'not support data' => '不支持的数据表达式', + '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方法', + '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} 创建失败!', + + // Validate Error Message + ':attribute require' => ':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' => '规则错误', +]; diff --git a/thinkphp/library/think/App.php b/thinkphp/library/think/App.php new file mode 100644 index 0000000..f572b90 --- /dev/null +++ b/thinkphp/library/think/App.php @@ -0,0 +1,677 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\ClassNotFoundException; +use think\exception\HttpException; +use think\exception\HttpResponseException; +use think\exception\RouteNotFoundException; + +/** + * App 应用管理 + * @author liu21st + */ +class App +{ + /** + * @var bool 是否初始化过 + */ + protected static $init = false; + + /** + * @var string 当前模块路径 + */ + public static $modulePath; + + /** + * @var bool 应用调试模式 + */ + public static $debug = true; + + /** + * @var string 应用类库命名空间 + */ + public static $namespace = 'app'; + + /** + * @var bool 应用类库后缀 + */ + public static $suffix = false; + + /** + * @var bool 应用路由检测 + */ + protected static $routeCheck; + + /** + * @var bool 严格路由检测 + */ + protected static $routeMust; + + /** + * @var array 请求调度分发 + */ + protected static $dispatch; + + /** + * @var array 额外加载文件 + */ + protected static $file = []; + + /** + * 执行应用程序 + * @access public + * @param Request $request 请求对象 + * @return Response + * @throws Exception + */ + public static function run(Request $request = null) + { + $request = is_null($request) ? Request::instance() : $request; + + try { + $config = self::initCommon(); + + // 模块/控制器绑定 + if (defined('BIND_MODULE')) { + BIND_MODULE && Route::bind(BIND_MODULE); + } elseif ($config['auto_bind_module']) { + // 入口自动绑定 + $name = pathinfo($request->baseFile(), PATHINFO_FILENAME); + if ($name && 'index' != $name && is_dir(APP_PATH . $name)) { + Route::bind($name); + } + } + + $request->filter($config['default_filter']); + + // 默认语言 + Lang::range($config['default_lang']); + // 开启多语言机制 检测当前语言 + $config['lang_switch_on'] && Lang::detect(); + $request->langset(Lang::range()); + + // 加载系统语言包 + Lang::load([ + THINK_PATH . 'lang' . DS . $request->langset() . EXT, + APP_PATH . 'lang' . DS . $request->langset() . EXT, + ]); + + // 监听 app_dispatch + Hook::listen('app_dispatch', self::$dispatch); + // 获取应用调度信息 + $dispatch = self::$dispatch; + + // 未设置调度信息则进行 URL 路由检测 + if (empty($dispatch)) { + $dispatch = self::routeCheck($request, $config); + } + + // 记录当前调度信息 + $request->dispatch($dispatch); + + // 记录路由和请求信息 + if (self::$debug) { + Log::record('[ ROUTE ] ' . var_export($dispatch, true), 'info'); + Log::record('[ HEADER ] ' . var_export($request->header(), true), 'info'); + Log::record('[ PARAM ] ' . var_export($request->param(), true), 'info'); + } + + // 监听 app_begin + Hook::listen('app_begin', $dispatch); + + // 请求缓存检查 + $request->cache( + $config['request_cache'], + $config['request_cache_expire'], + $config['request_cache_except'] + ); + + $data = self::exec($dispatch, $config); + } catch (HttpResponseException $exception) { + $data = $exception->getResponse(); + } + + // 清空类的实例化 + Loader::clearInstance(); + + // 输出数据到客户端 + if ($data instanceof Response) { + $response = $data; + } elseif (!is_null($data)) { + // 默认自动识别响应输出类型 + $type = $request->isAjax() ? + Config::get('default_ajax_return') : + Config::get('default_return_type'); + + $response = Response::create($data, $type); + } else { + $response = Response::create(); + } + + // 监听 app_end + Hook::listen('app_end', $response); + + return $response; + } + + /** + * 初始化应用,并返回配置信息 + * @access public + * @return array + */ + public static function initCommon() + { + if (empty(self::$init)) { + if (defined('APP_NAMESPACE')) { + self::$namespace = APP_NAMESPACE; + } + + Loader::addNamespace(self::$namespace, APP_PATH); + + // 初始化应用 + $config = self::init(); + self::$suffix = $config['class_suffix']; + + // 应用调试模式 + self::$debug = Env::get('app_debug', Config::get('app_debug')); + + if (!self::$debug) { + ini_set('display_errors', 'Off'); + } elseif (!IS_CLI) { + // 重新申请一块比较大的 buffer + if (ob_get_level() > 0) { + $output = ob_get_clean(); + } + + ob_start(); + + if (!empty($output)) { + echo $output; + } + + } + + if (!empty($config['root_namespace'])) { + Loader::addNamespace($config['root_namespace']); + } + + // 加载额外文件 + if (!empty($config['extra_file_list'])) { + foreach ($config['extra_file_list'] as $file) { + $file = strpos($file, '.') ? $file : APP_PATH . $file . EXT; + if (is_file($file) && !isset(self::$file[$file])) { + include $file; + self::$file[$file] = true; + } + } + } + + // 设置系统时区 + date_default_timezone_set($config['default_timezone']); + + // 监听 app_init + Hook::listen('app_init'); + + self::$init = true; + } + + return Config::get(); + } + + /** + * 初始化应用或模块 + * @access public + * @param string $module 模块名 + * @return array + */ + private static function init($module = '') + { + // 定位模块目录 + $module = $module ? $module . DS : ''; + + // 加载初始化文件 + if (is_file(APP_PATH . $module . 'init' . EXT)) { + include APP_PATH . $module . 'init' . EXT; + } elseif (is_file(RUNTIME_PATH . $module . 'init' . EXT)) { + include RUNTIME_PATH . $module . 'init' . EXT; + } else { + // 加载模块配置 + $config = Config::load(CONF_PATH . $module . 'config' . CONF_EXT); + + // 读取数据库配置文件 + $filename = CONF_PATH . $module . 'database' . CONF_EXT; + Config::load($filename, 'database'); + + // 读取扩展配置文件 + if (is_dir(CONF_PATH . $module . 'extra')) { + $dir = CONF_PATH . $module . 'extra'; + $files = scandir($dir); + foreach ($files as $file) { + if ('.' . pathinfo($file, PATHINFO_EXTENSION) === CONF_EXT) { + $filename = $dir . DS . $file; + Config::load($filename, pathinfo($file, PATHINFO_FILENAME)); + } + } + } + + // 加载应用状态配置 + if ($config['app_status']) { + Config::load(CONF_PATH . $module . $config['app_status'] . CONF_EXT); + } + + // 加载行为扩展文件 + if (is_file(CONF_PATH . $module . 'tags' . EXT)) { + Hook::import(include CONF_PATH . $module . 'tags' . EXT); + } + + // 加载公共文件 + $path = APP_PATH . $module; + if (is_file($path . 'common' . EXT)) { + include $path . 'common' . EXT; + } + + // 加载当前模块语言包 + if ($module) { + Lang::load($path . 'lang' . DS . Request::instance()->langset() . EXT); + } + } + + return Config::get(); + } + + /** + * 设置当前请求的调度信息 + * @access public + * @param array|string $dispatch 调度信息 + * @param string $type 调度类型 + * @return void + */ + public static function dispatch($dispatch, $type = 'module') + { + self::$dispatch = ['type' => $type, $type => $dispatch]; + } + + /** + * 执行函数或者闭包方法 支持参数调用 + * @access public + * @param string|array|\Closure $function 函数或者闭包 + * @param array $vars 变量 + * @return mixed + */ + public static function invokeFunction($function, $vars = []) + { + $reflect = new \ReflectionFunction($function); + $args = self::bindParams($reflect, $vars); + + // 记录执行信息 + self::$debug && Log::record('[ RUN ] ' . $reflect->__toString(), 'info'); + + return $reflect->invokeArgs($args); + } + + /** + * 调用反射执行类的方法 支持参数绑定 + * @access public + * @param string|array $method 方法 + * @param array $vars 变量 + * @return mixed + */ + public static function invokeMethod($method, $vars = []) + { + if (is_array($method)) { + $class = is_object($method[0]) ? $method[0] : self::invokeClass($method[0]); + $reflect = new \ReflectionMethod($class, $method[1]); + } else { + // 静态方法 + $reflect = new \ReflectionMethod($method); + } + + $args = self::bindParams($reflect, $vars); + + self::$debug && Log::record('[ RUN ] ' . $reflect->class . '->' . $reflect->name . '[ ' . $reflect->getFileName() . ' ]', 'info'); + + return $reflect->invokeArgs(isset($class) ? $class : null, $args); + } + + /** + * 调用反射执行类的实例化 支持依赖注入 + * @access public + * @param string $class 类名 + * @param array $vars 变量 + * @return mixed + */ + public static function invokeClass($class, $vars = []) + { + $reflect = new \ReflectionClass($class); + $constructor = $reflect->getConstructor(); + $args = $constructor ? self::bindParams($constructor, $vars) : []; + + return $reflect->newInstanceArgs($args); + } + + /** + * 绑定参数 + * @access private + * @param \ReflectionMethod|\ReflectionFunction $reflect 反射类 + * @param array $vars 变量 + * @return array + */ + private static function bindParams($reflect, $vars = []) + { + // 自动获取请求变量 + if (empty($vars)) { + $vars = Config::get('url_param_type') ? + Request::instance()->route() : + Request::instance()->param(); + } + + $args = []; + if ($reflect->getNumberOfParameters() > 0) { + // 判断数组类型 数字数组时按顺序绑定参数 + reset($vars); + $type = key($vars) === 0 ? 1 : 0; + + foreach ($reflect->getParameters() as $param) { + $args[] = self::getParamValue($param, $vars, $type); + } + } + + return $args; + } + + /** + * 获取参数值 + * @access private + * @param \ReflectionParameter $param 参数 + * @param array $vars 变量 + * @param string $type 类别 + * @return array + */ + private static function getParamValue($param, &$vars, $type) + { + $name = $param->getName(); + $class = $param->getClass(); + + if ($class) { + $className = $class->getName(); + $bind = Request::instance()->$name; + + if ($bind instanceof $className) { + $result = $bind; + } else { + if (method_exists($className, 'invoke')) { + $method = new \ReflectionMethod($className, 'invoke'); + + if ($method->isPublic() && $method->isStatic()) { + return $className::invoke(Request::instance()); + } + } + + $result = method_exists($className, 'instance') ? + $className::instance() : + new $className; + } + } elseif (1 == $type && !empty($vars)) { + $result = array_shift($vars); + } elseif (0 == $type && isset($vars[$name])) { + $result = $vars[$name]; + } elseif ($param->isDefaultValueAvailable()) { + $result = $param->getDefaultValue(); + } else { + throw new \InvalidArgumentException('method param miss:' . $name); + } + + return $result; + } + + /** + * 执行调用分发 + * @access protected + * @param array $dispatch 调用信息 + * @param array $config 配置信息 + * @return Response|mixed + * @throws \InvalidArgumentException + */ + protected static function exec($dispatch, $config) + { + switch ($dispatch['type']) { + case 'redirect': // 重定向跳转 + $data = Response::create($dispatch['url'], 'redirect') + ->code($dispatch['status']); + break; + case 'module': // 模块/控制器/操作 + $data = self::module( + $dispatch['module'], + $config, + isset($dispatch['convert']) ? $dispatch['convert'] : null + ); + break; + case 'controller': // 执行控制器操作 + $vars = array_merge(Request::instance()->param(), $dispatch['var']); + $data = Loader::action( + $dispatch['controller'], + $vars, + $config['url_controller_layer'], + $config['controller_suffix'] + ); + break; + case 'method': // 回调方法 + $vars = array_merge(Request::instance()->param(), $dispatch['var']); + $data = self::invokeMethod($dispatch['method'], $vars); + break; + case 'function': // 闭包 + $data = self::invokeFunction($dispatch['function']); + break; + case 'response': // Response 实例 + $data = $dispatch['response']; + break; + default: + throw new \InvalidArgumentException('dispatch type not support'); + } + + return $data; + } + + /** + * 执行模块 + * @access public + * @param array $result 模块/控制器/操作 + * @param array $config 配置参数 + * @param bool $convert 是否自动转换控制器和操作名 + * @return mixed + * @throws HttpException + */ + public static function module($result, $config, $convert = null) + { + if (is_string($result)) { + $result = explode('/', $result); + } + + $request = Request::instance(); + + if ($config['app_multi_module']) { + // 多模块部署 + $module = strip_tags(strtolower($result[0] ?: $config['default_module'])); + $bind = Route::getBind('module'); + $available = false; + + if ($bind) { + // 绑定模块 + list($bindModule) = explode('/', $bind); + + if (empty($result[0])) { + $module = $bindModule; + $available = true; + } elseif ($module == $bindModule) { + $available = true; + } + } elseif (!in_array($module, $config['deny_module_list']) && is_dir(APP_PATH . $module)) { + $available = true; + } + + // 模块初始化 + if ($module && $available) { + // 初始化模块 + $request->module($module); + $config = self::init($module); + + // 模块请求缓存检查 + $request->cache( + $config['request_cache'], + $config['request_cache_expire'], + $config['request_cache_except'] + ); + } else { + throw new HttpException(404, 'module not exists:' . $module); + } + } else { + // 单一模块部署 + $module = ''; + $request->module($module); + } + + // 设置默认过滤机制 + $request->filter($config['default_filter']); + + // 当前模块路径 + App::$modulePath = APP_PATH . ($module ? $module . DS : ''); + + // 是否自动转换控制器和操作名 + $convert = is_bool($convert) ? $convert : $config['url_convert']; + + // 获取控制器名 + $controller = strip_tags($result[1] ?: $config['default_controller']); + + if (!preg_match('/^[A-Za-z](\w|\.)*$/', $controller)) { + throw new HttpException(404, 'controller not exists:' . $controller); + } + + $controller = $convert ? strtolower($controller) : $controller; + + // 获取操作名 + $actionName = strip_tags($result[2] ?: $config['default_action']); + if (!empty($config['action_convert'])) { + $actionName = Loader::parseName($actionName, 1); + } else { + $actionName = $convert ? strtolower($actionName) : $actionName; + } + + // 设置当前请求的控制器、操作 + $request->controller(Loader::parseName($controller, 1))->action($actionName); + + // 监听module_init + Hook::listen('module_init', $request); + + try { + $instance = Loader::controller( + $controller, + $config['url_controller_layer'], + $config['controller_suffix'], + $config['empty_controller'] + ); + } catch (ClassNotFoundException $e) { + throw new HttpException(404, 'controller not exists:' . $e->getClass()); + } + + // 获取当前操作名 + $action = $actionName . $config['action_suffix']; + + $vars = []; + if (is_callable([$instance, $action])) { + // 执行操作方法 + $call = [$instance, $action]; + // 严格获取当前操作方法名 + $reflect = new \ReflectionMethod($instance, $action); + $methodName = $reflect->getName(); + $suffix = $config['action_suffix']; + $actionName = $suffix ? substr($methodName, 0, -strlen($suffix)) : $methodName; + $request->action($actionName); + + } elseif (is_callable([$instance, '_empty'])) { + // 空操作 + $call = [$instance, '_empty']; + $vars = [$actionName]; + } else { + // 操作不存在 + throw new HttpException(404, 'method not exists:' . get_class($instance) . '->' . $action . '()'); + } + + Hook::listen('action_begin', $call); + + return self::invokeMethod($call, $vars); + } + + /** + * URL路由检测(根据PATH_INFO) + * @access public + * @param \think\Request $request 请求实例 + * @param array $config 配置信息 + * @return array + * @throws \think\Exception + */ + public static function routeCheck($request, array $config) + { + $path = $request->path(); + $depr = $config['pathinfo_depr']; + $result = false; + + // 路由检测 + $check = !is_null(self::$routeCheck) ? self::$routeCheck : $config['url_route_on']; + if ($check) { + // 开启路由 + if (is_file(RUNTIME_PATH . 'route.php')) { + // 读取路由缓存 + $rules = include RUNTIME_PATH . 'route.php'; + is_array($rules) && Route::rules($rules); + } else { + $files = $config['route_config_file']; + foreach ($files as $file) { + if (is_file(CONF_PATH . $file . CONF_EXT)) { + // 导入路由配置 + $rules = include CONF_PATH . $file . CONF_EXT; + is_array($rules) && Route::import($rules); + } + } + } + + // 路由检测(根据路由定义返回不同的URL调度) + $result = Route::check($request, $path, $depr, $config['url_domain_deploy']); + $must = !is_null(self::$routeMust) ? self::$routeMust : $config['url_route_must']; + + if ($must && false === $result) { + // 路由无效 + throw new RouteNotFoundException(); + } + } + + // 路由无效 解析模块/控制器/操作/参数... 支持控制器自动搜索 + if (false === $result) { + $result = Route::parseUrl($path, $depr, $config['controller_auto_search']); + } + + return $result; + } + + /** + * 设置应用的路由检测机制 + * @access public + * @param bool $route 是否需要检测路由 + * @param bool $must 是否强制检测路由 + * @return void + */ + public static function route($route, $must = false) + { + self::$routeCheck = $route; + self::$routeMust = $must; + } +} diff --git a/thinkphp/library/think/Build.php b/thinkphp/library/think/Build.php new file mode 100644 index 0000000..de7c327 --- /dev/null +++ b/thinkphp/library/think/Build.php @@ -0,0 +1,235 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class Build +{ + /** + * 根据传入的 build 资料创建目录和文件 + * @access public + * @param array $build build 列表 + * @param string $namespace 应用类库命名空间 + * @param bool $suffix 类库后缀 + * @return void + * @throws Exception + */ + public static function run(array $build = [], $namespace = 'app', $suffix = false) + { + // 锁定 + $lock = APP_PATH . 'build.lock'; + + // 如果锁定文件不可写(不存在)则进行处理,否则表示已经有程序在处理了 + if (!is_writable($lock)) { + if (!touch($lock)) { + throw new Exception( + '应用目录[' . APP_PATH . ']不可写,目录无法自动生成!
请手动生成项目目录~', + 10006 + ); + } + + foreach ($build as $module => $list) { + if ('__dir__' == $module) { + // 创建目录列表 + self::buildDir($list); + } elseif ('__file__' == $module) { + // 创建文件列表 + self::buildFile($list); + } else { + // 创建模块 + self::module($module, $list, $namespace, $suffix); + } + } + + // 解除锁定 + unlink($lock); + } + } + + /** + * 创建目录 + * @access protected + * @param array $list 目录列表 + * @return void + */ + protected static function buildDir($list) + { + foreach ($list as $dir) { + // 目录不存在则创建目录 + !is_dir(APP_PATH . $dir) && mkdir(APP_PATH . $dir, 0755, true); + } + } + + /** + * 创建文件 + * @access protected + * @param array $list 文件列表 + * @return void + */ + protected static function buildFile($list) + { + foreach ($list as $file) { + // 先创建目录 + if (!is_dir(APP_PATH . dirname($file))) { + mkdir(APP_PATH . dirname($file), 0755, true); + } + + // 再创建文件 + if (!is_file(APP_PATH . $file)) { + file_put_contents( + APP_PATH . $file, + 'php' == pathinfo($file, PATHINFO_EXTENSION) ? " ['config.php', 'common.php'], + '__dir__' => ['controller', 'model', 'view'], + ]; + } + + // 创建子目录和文件 + foreach ($list as $path => $file) { + $modulePath = APP_PATH . $module . DS; + + if ('__dir__' == $path) { + // 生成子目录 + foreach ($file as $dir) { + self::checkDirBuild($modulePath . $dir); + } + } elseif ('__file__' == $path) { + // 生成(空白)文件 + foreach ($file as $name) { + if (!is_file($modulePath . $name)) { + file_put_contents( + $modulePath . $name, + 'php' == pathinfo($name, PATHINFO_EXTENSION) ? " +// +---------------------------------------------------------------------- + +namespace think; + +use think\cache\Driver; + +class Cache +{ + /** + * @var array 缓存的实例 + */ + public static $instance = []; + + /** + * @var int 缓存读取次数 + */ + public static $readTimes = 0; + + /** + * @var int 缓存写入次数 + */ + public static $writeTimes = 0; + + /** + * @var object 操作句柄 + */ + public static $handler; + + /** + * 连接缓存驱动 + * @access public + * @param array $options 配置数组 + * @param bool|string $name 缓存连接标识 true 强制重新连接 + * @return Driver + */ + public static function connect(array $options = [], $name = false) + { + $type = !empty($options['type']) ? $options['type'] : 'File'; + + if (false === $name) { + $name = md5(serialize($options)); + } + + if (true === $name || !isset(self::$instance[$name])) { + $class = false === strpos($type, '\\') ? + '\\think\\cache\\driver\\' . ucwords($type) : + $type; + + // 记录初始化信息 + App::$debug && Log::record('[ CACHE ] INIT ' . $type, 'info'); + + if (true === $name) { + return new $class($options); + } + + self::$instance[$name] = new $class($options); + } + + return self::$instance[$name]; + } + + /** + * 自动初始化缓存 + * @access public + * @param array $options 配置数组 + * @return Driver + */ + public static function init(array $options = []) + { + if (is_null(self::$handler)) { + if (empty($options) && 'complex' == Config::get('cache.type')) { + $default = Config::get('cache.default'); + // 获取默认缓存配置,并连接 + $options = Config::get('cache.' . $default['type']) ?: $default; + } elseif (empty($options)) { + $options = Config::get('cache'); + } + + self::$handler = self::connect($options); + } + + return self::$handler; + } + + /** + * 切换缓存类型 需要配置 cache.type 为 complex + * @access public + * @param string $name 缓存标识 + * @return Driver + */ + public static function store($name = '') + { + if ('' !== $name && 'complex' == Config::get('cache.type')) { + return self::connect(Config::get('cache.' . $name), strtolower($name)); + } + + return self::init(); + } + + /** + * 判断缓存是否存在 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public static function has($name) + { + self::$readTimes++; + + return self::init()->has($name); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存标识 + * @param mixed $default 默认值 + * @return mixed + */ + public static function get($name, $default = false) + { + self::$readTimes++; + + return self::init()->get($name, $default); + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存标识 + * @param mixed $value 存储数据 + * @param int|null $expire 有效时间 0为永久 + * @return boolean + */ + public static function set($name, $value, $expire = null) + { + self::$writeTimes++; + + return self::init()->set($name, $value, $expire); + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public static function inc($name, $step = 1) + { + self::$writeTimes++; + + return self::init()->inc($name, $step); + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public static function dec($name, $step = 1) + { + self::$writeTimes++; + + return self::init()->dec($name, $step); + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存标识 + * @return boolean + */ + public static function rm($name) + { + self::$writeTimes++; + + return self::init()->rm($name); + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return boolean + */ + public static function clear($tag = null) + { + self::$writeTimes++; + + return self::init()->clear($tag); + } + + /** + * 读取缓存并删除 + * @access public + * @param string $name 缓存变量名 + * @return mixed + */ + public static function pull($name) + { + self::$readTimes++; + self::$writeTimes++; + + return self::init()->pull($name); + } + + /** + * 如果不存在则写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int $expire 有效时间 0为永久 + * @return mixed + */ + public static function remember($name, $value, $expire = null) + { + self::$readTimes++; + + return self::init()->remember($name, $value, $expire); + } + + /** + * 缓存标签 + * @access public + * @param string $name 标签名 + * @param string|array $keys 缓存标识 + * @param bool $overlay 是否覆盖 + * @return Driver + */ + public static function tag($name, $keys = null, $overlay = false) + { + return self::init()->tag($name, $keys, $overlay); + } + +} diff --git a/thinkphp/library/think/Collection.php b/thinkphp/library/think/Collection.php new file mode 100644 index 0000000..f872476 --- /dev/null +++ b/thinkphp/library/think/Collection.php @@ -0,0 +1,467 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use ArrayAccess; +use ArrayIterator; +use Countable; +use IteratorAggregate; +use JsonSerializable; + +class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable +{ + /** + * @var array 数据 + */ + protected $items = []; + + /** + * Collection constructor. + * @access public + * @param array $items 数据 + */ + public function __construct($items = []) + { + $this->items = $this->convertToArray($items); + } + + /** + * 创建 Collection 实例 + * @access public + * @param array $items 数据 + * @return static + */ + public static function make($items = []) + { + return new static($items); + } + + /** + * 判断数据是否为空 + * @access public + * @return bool + */ + public function isEmpty() + { + return empty($this->items); + } + + /** + * 将数据转成数组 + * @access public + * @return array + */ + public function toArray() + { + return array_map(function ($value) { + return ($value instanceof Model || $value instanceof self) ? + $value->toArray() : + $value; + }, $this->items); + } + + /** + * 获取全部的数据 + * @access public + * @return array + */ + public function all() + { + return $this->items; + } + + /** + * 交换数组中的键和值 + * @access public + * @return static + */ + public function flip() + { + return new static(array_flip($this->items)); + } + + /** + * 返回数组中所有的键名组成的新 Collection 实例 + * @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)); + } + + /** + * 合并数组并返回一个新的 Collection 实例 + * @access public + * @param mixed $items 新的数据 + * @return static + */ + public function merge($items) + { + return new static(array_merge($this->items, $this->convertToArray($items))); + } + + /** + * 比较数组,返回差集生成的新 Collection 实例 + * @access public + * @param mixed $items 做比较的数据 + * @return static + */ + public function diff($items) + { + return new static(array_diff($this->items, $this->convertToArray($items))); + } + + /** + * 比较数组,返回交集组成的 Collection 新实例 + * @access public + * @param mixed $items 比较数据 + * @return static + */ + public function intersect($items) + { + return new static(array_intersect($this->items, $this->convertToArray($items))); + } + + /** + * 返回并删除数据中的的最后一个元素(出栈) + * @access public + * @return mixed + */ + public function pop() + { + return array_pop($this->items); + } + + /** + * 返回并删除数据中首个元素 + * @access public + * @return mixed + */ + public function shift() + { + return array_shift($this->items); + } + + /** + * 在数组开头插入一个元素 + * @access public + * @param mixed $value 值 + * @param mixed $key 键名 + * @return void + */ + public function unshift($value, $key = null) + { + if (is_null($key)) { + array_unshift($this->items, $value); + } else { + $this->items = [$key => $value] + $this->items; + } + } + + /** + * 在数组结尾插入一个元素 + * @access public + * @param mixed $value 值 + * @param mixed $key 键名 + * @return void + */ + public function push($value, $key = null) + { + if (is_null($key)) { + $this->items[] = $value; + } else { + $this->items[$key] = $value; + } + } + + /** + * 通过使用用户自定义函数,以字符串返回数组 + * @access public + * @param callable $callback 回调函数 + * @param mixed $initial 初始值 + * @return mixed + */ + public function reduce(callable $callback, $initial = null) + { + return array_reduce($this->items, $callback, $initial); + } + + /** + * 以相反的顺序创建一个新的 Collection 实例 + * @access public + * @return static + */ + public function reverse() + { + return new static(array_reverse($this->items)); + } + + /** + * 把数据分割为新的数组块 + * @access public + * @param int $size 分隔长度 + * @param bool $preserveKeys 是否保持原数据索引 + * @return static + */ + public function chunk($size, $preserveKeys = false) + { + $chunks = []; + + foreach (array_chunk($this->items, $size, $preserveKeys) as $chunk) { + $chunks[] = new static($chunk); + } + + return new static($chunks); + } + + /** + * 给数据中的每个元素执行回调 + * @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; + } + + if (!is_object($item)) { + $this->items[$key] = $result; + } + } + + return $this; + } + + /** + * 用回调函数过滤数据中的元素 + * @access public + * @param callable|null $callback 回调函数 + * @return static + */ + public function filter(callable $callback = null) + { + return new static(array_filter($this->items, $callback ?: null)); + } + + /** + * 返回数据中指定的一列 + * @access public + * @param mixed $columnKey 键名 + * @param null $indexKey 作为索引值的列 + * @return array + */ + public function column($columnKey, $indexKey = null) + { + if (function_exists('array_column')) { + return array_column($this->items, $columnKey, $indexKey); + } + + $result = []; + foreach ($this->items as $row) { + $key = $value = null; + $keySet = $valueSet = false; + + if (null !== $indexKey && array_key_exists($indexKey, $row)) { + $key = (string) $row[$indexKey]; + $keySet = true; + } + + if (null === $columnKey) { + $valueSet = true; + $value = $row; + } elseif (is_array($row) && array_key_exists($columnKey, $row)) { + $valueSet = true; + $value = $row[$columnKey]; + } + + if ($valueSet) { + if ($keySet) { + $result[$key] = $value; + } else { + $result[] = $value; + } + } + } + + return $result; + } + + /** + * 对数据排序,并返回排序后的数据组成的新 Collection 实例 + * @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); + } + + /** + * 将数据打乱后组成新的 Collection 实例 + * @access public + * @return static + */ + public function shuffle() + { + $items = $this->items; + + shuffle($items); + return new static($items); + } + + /** + * 截取数据并返回新的 Collection 实例 + * @access public + * @param int $offset 起始位置 + * @param int $length 截取长度 + * @param bool $preserveKeys 是否保持原先的键名 + * @return static + */ + public function slice($offset, $length = null, $preserveKeys = false) + { + return new static(array_slice($this->items, $offset, $length, $preserveKeys)); + } + + /** + * 指定的键是否存在 + * @access public + * @param mixed $offset 键名 + * @return bool + */ + public function offsetExists($offset) + { + return array_key_exists($offset, $this->items); + } + + /** + * 获取指定键对应的值 + * @access public + * @param mixed $offset 键名 + * @return mixed + */ + public function offsetGet($offset) + { + return $this->items[$offset]; + } + + /** + * 设置键值 + * @access public + * @param mixed $offset 键名 + * @param mixed $value 值 + * @return void + */ + public function offsetSet($offset, $value) + { + if (is_null($offset)) { + $this->items[] = $value; + } else { + $this->items[$offset] = $value; + } + } + + /** + * 删除指定键值 + * @access public + * @param mixed $offset 键名 + * @return void + */ + public function offsetUnset($offset) + { + unset($this->items[$offset]); + } + + /** + * 统计数据的个数 + * @access public + * @return int + */ + public function count() + { + return count($this->items); + } + + /** + * 获取数据的迭代器 + * @access public + * @return ArrayIterator + */ + public function getIterator() + { + return new ArrayIterator($this->items); + } + + /** + * 将数据反序列化成数组 + * @access public + * @return array + */ + public function jsonSerialize() + { + return $this->toArray(); + } + + /** + * 转换当前数据集为 JSON 字符串 + * @access public + * @param integer $options json 参数 + * @return string + */ + public function toJson($options = JSON_UNESCAPED_UNICODE) + { + return json_encode($this->toArray(), $options); + } + + /** + * 将数据转换成字符串 + * @access public + * @return string + */ + public function __toString() + { + return $this->toJson(); + } + + /** + * 将数据转换成数组 + * @access protected + * @param mixed $items 数据 + * @return array + */ + protected function convertToArray($items) + { + return $items instanceof self ? $items->all() : (array) $items; + } +} diff --git a/thinkphp/library/think/Config.php b/thinkphp/library/think/Config.php new file mode 100644 index 0000000..8fa668d --- /dev/null +++ b/thinkphp/library/think/Config.php @@ -0,0 +1,214 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class Config +{ + /** + * @var array 配置参数 + */ + private static $config = []; + + /** + * @var string 参数作用域 + */ + private static $range = '_sys_'; + + /** + * 设定配置参数的作用域 + * @access public + * @param string $range 作用域 + * @return void + */ + public static function range($range) + { + self::$range = $range; + + if (!isset(self::$config[$range])) self::$config[$range] = []; + } + + /** + * 解析配置文件或内容 + * @access public + * @param string $config 配置文件路径或内容 + * @param string $type 配置解析类型 + * @param string $name 配置名(如设置即表示二级配置) + * @param string $range 作用域 + * @return mixed + */ + public static function parse($config, $type = '', $name = '', $range = '') + { + $range = $range ?: self::$range; + + if (empty($type)) $type = pathinfo($config, PATHINFO_EXTENSION); + + $class = false !== strpos($type, '\\') ? + $type : + '\\think\\config\\driver\\' . ucwords($type); + + return self::set((new $class())->parse($config), $name, $range); + } + + /** + * 加载配置文件(PHP格式) + * @access public + * @param string $file 配置文件名 + * @param string $name 配置名(如设置即表示二级配置) + * @param string $range 作用域 + * @return mixed + */ + public static function load($file, $name = '', $range = '') + { + $range = $range ?: self::$range; + + if (!isset(self::$config[$range])) self::$config[$range] = []; + + if (is_file($file)) { + $name = strtolower($name); + $type = pathinfo($file, PATHINFO_EXTENSION); + + if ('php' == $type) { + return self::set(include $file, $name, $range); + } + + if ('yaml' == $type && function_exists('yaml_parse_file')) { + return self::set(yaml_parse_file($file), $name, $range); + } + + return self::parse($file, $type, $name, $range); + } + + return self::$config[$range]; + } + + /** + * 检测配置是否存在 + * @access public + * @param string $name 配置参数名(支持二级配置 . 号分割) + * @param string $range 作用域 + * @return bool + */ + public static function has($name, $range = '') + { + $range = $range ?: self::$range; + + if (!strpos($name, '.')) { + return isset(self::$config[$range][strtolower($name)]); + } + + // 二维数组设置和获取支持 + $name = explode('.', $name, 2); + return isset(self::$config[$range][strtolower($name[0])][$name[1]]); + } + + /** + * 获取配置参数 为空则获取所有配置 + * @access public + * @param string $name 配置参数名(支持二级配置 . 号分割) + * @param string $range 作用域 + * @return mixed + */ + public static function get($name = null, $range = '') + { + $range = $range ?: self::$range; + + // 无参数时获取所有 + if (empty($name) && isset(self::$config[$range])) { + return self::$config[$range]; + } + + // 非二级配置时直接返回 + if (!strpos($name, '.')) { + $name = strtolower($name); + return isset(self::$config[$range][$name]) ? self::$config[$range][$name] : null; + } + + // 二维数组设置和获取支持 + $name = explode('.', $name, 2); + $name[0] = strtolower($name[0]); + + if (!isset(self::$config[$range][$name[0]])) { + // 动态载入额外配置 + $module = Request::instance()->module(); + $file = CONF_PATH . ($module ? $module . DS : '') . 'extra' . DS . $name[0] . CONF_EXT; + + is_file($file) && self::load($file, $name[0]); + } + + return isset(self::$config[$range][$name[0]][$name[1]]) ? + self::$config[$range][$name[0]][$name[1]] : + null; + } + + /** + * 设置配置参数 name 为数组则为批量设置 + * @access public + * @param string|array $name 配置参数名(支持二级配置 . 号分割) + * @param mixed $value 配置值 + * @param string $range 作用域 + * @return mixed + */ + public static function set($name, $value = null, $range = '') + { + $range = $range ?: self::$range; + + if (!isset(self::$config[$range])) self::$config[$range] = []; + + // 字符串则表示单个配置设置 + if (is_string($name)) { + if (!strpos($name, '.')) { + self::$config[$range][strtolower($name)] = $value; + } else { + // 二维数组 + $name = explode('.', $name, 2); + self::$config[$range][strtolower($name[0])][$name[1]] = $value; + } + + return $value; + } + + // 数组则表示批量设置 + if (is_array($name)) { + if (!empty($value)) { + self::$config[$range][$value] = isset(self::$config[$range][$value]) ? + array_merge(self::$config[$range][$value], $name) : + $name; + + return self::$config[$range][$value]; + } + + return self::$config[$range] = array_merge( + self::$config[$range], array_change_key_case($name) + ); + } + + // 为空直接返回已有配置 + return self::$config[$range]; + } + + /** + * 重置配置参数 + * @access public + * @param string $range 作用域 + * @return void + */ + public static function reset($range = '') + { + $range = $range ?: self::$range; + + if (true === $range) { + self::$config = []; + } else { + self::$config[$range] = []; + } + } +} diff --git a/thinkphp/library/think/Console.php b/thinkphp/library/think/Console.php new file mode 100644 index 0000000..32b2572 --- /dev/null +++ b/thinkphp/library/think/Console.php @@ -0,0 +1,863 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\console\Command; +use think\console\command\Help as HelpCommand; +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 +{ + /** + * @var string 命令名称 + */ + private $name; + + /** + * @var string 命令版本 + */ + private $version; + + /** + * @var Command[] 命令 + */ + private $commands = []; + + /** + * @var bool 是否需要帮助信息 + */ + private $wantHelps = false; + + /** + * @var bool 是否捕获异常 + */ + private $catchExceptions = true; + + /** + * @var bool 是否自动退出执行 + */ + private $autoExit = true; + + /** + * @var InputDefinition 输入定义 + */ + private $definition; + + /** + * @var string 默认执行的命令 + */ + private $defaultCommand; + + /** + * @var array 默认提供的命令 + */ + private static $defaultCommands = [ + "think\\console\\command\\Help", + "think\\console\\command\\Lists", + "think\\console\\command\\Build", + "think\\console\\command\\Clear", + "think\\console\\command\\make\\Controller", + "think\\console\\command\\make\\Model", + "think\\console\\command\\optimize\\Autoload", + "think\\console\\command\\optimize\\Config", + "think\\console\\command\\optimize\\Route", + "think\\console\\command\\optimize\\Schema", + ]; + + /** + * Console constructor. + * @access public + * @param string $name 名称 + * @param string $version 版本 + * @param null|string $user 执行用户 + */ + public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN', $user = null) + { + $this->name = $name; + $this->version = $version; + + if ($user) { + $this->setUser($user); + } + + $this->defaultCommand = 'list'; + $this->definition = $this->getDefaultInputDefinition(); + + foreach ($this->getDefaultCommands() as $command) { + $this->add($command); + } + } + + /** + * 设置执行用户 + * @param $user + */ + public function setUser($user) + { + $user = posix_getpwnam($user); + if ($user) { + posix_setuid($user['uid']); + posix_setgid($user['gid']); + } + } + + /** + * 初始化 Console + * @access public + * @param bool $run 是否运行 Console + * @return int|Console + */ + public static function init($run = true) + { + static $console; + + if (!$console) { + $config = Config::get('console'); + // 实例化 console + $console = new self($config['name'], $config['version'], $config['user']); + + // 读取指令集 + if (is_file(CONF_PATH . 'command' . EXT)) { + $commands = include CONF_PATH . 'command' . EXT; + + if (is_array($commands)) { + foreach ($commands as $command) { + class_exists($command) && + is_subclass_of($command, "\\think\\console\\Command") && + $console->add(new $command()); // 注册指令 + } + } + } + } + + return $run ? $console->run() : $console; + } + + /** + * 调用命令 + * @access public + * @param string $command + * @param array $parameters + * @param string $driver + * @return Output + */ + public static function call($command, array $parameters = [], $driver = 'buffer') + { + $console = self::init(false); + + array_unshift($parameters, $command); + + $input = new Input($parameters); + $output = new Output($driver); + + $console->setCatchExceptions(false); + $console->find($command)->run($input, $output); + + return $output; + } + + /** + * 执行当前的指令 + * @access public + * @return int + * @throws \Exception + */ + 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) ?: 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]); + } + + return $this->doRunCommand($this->find($name), $input, $output); + } + + /** + * 设置输入参数定义 + * @access public + * @param InputDefinition $definition 输入定义 + * @return $this; + */ + public function setDefinition(InputDefinition $definition) + { + $this->definition = $definition; + + return $this; + } + + /** + * 获取输入参数定义 + * @access public + * @return InputDefinition + */ + public function getDefinition() + { + return $this->definition; + } + + /** + * 获取帮助信息 + * @access public + * @return string + */ + public function getHelp() + { + return $this->getLongVersion(); + } + + /** + * 设置是否捕获异常 + * @access public + * @param bool $boolean 是否捕获 + * @return $this + */ + public function setCatchExceptions($boolean) + { + $this->catchExceptions = (bool) $boolean; + + return $this; + } + + /** + * 设置是否自动退出 + * @access public + * @param bool $boolean 是否自动退出 + * @return $this + */ + public function setAutoExit($boolean) + { + $this->autoExit = (bool) $boolean; + + return $this; + } + + /** + * 获取名称 + * @access public + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * 设置名称 + * @access public + * @param string $name 名称 + * @return $this + */ + public function setName($name) + { + $this->name = $name; + + return $this; + } + + /** + * 获取版本 + * @access public + * @return string + */ + public function getVersion() + { + return $this->version; + } + + /** + * 设置版本 + * @access public + * @param string $version 版本信息 + * @return $this + */ + public function setVersion($version) + { + $this->version = $version; + + return $this; + } + + /** + * 获取完整的版本号 + * @access public + * @return string + */ + public function getLongVersion() + { + if ('UNKNOWN' !== $this->getName() && 'UNKNOWN' !== $this->getVersion()) { + return sprintf( + '%s version %s', + $this->getName(), + $this->getVersion() + ); + } + + return 'Console Tool'; + } + + /** + * 注册一个指令 + * @access public + * @param string $name 指令名称 + * @return Command + */ + public function register($name) + { + return $this->add(new Command($name)); + } + + /** + * 批量添加指令 + * @access public + * @param Command[] $commands 指令实例 + * @return $this + */ + public function addCommands(array $commands) + { + foreach ($commands as $command) $this->add($command); + + return $this; + } + + /** + * 添加一个指令 + * @access public + * @param Command $command 命令实例 + * @return Command|bool + */ + public function add(Command $command) + { + if (!$command->isEnabled()) { + $command->setConsole(null); + return false; + } + + $command->setConsole($this); + + 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 get($name) + { + if (!isset($this->commands[$name])) { + throw new \InvalidArgumentException( + sprintf('The command "%s" does not exist.', $name) + ); + } + + $command = $this->commands[$name]; + + if ($this->wantHelps) { + $this->wantHelps = false; + + /** @var HelpCommand $helpCommand */ + $helpCommand = $this->get('help'); + $helpCommand->setCommand($command); + + return $helpCommand; + } + + return $command; + } + + /** + * 某个指令是否存在 + * @access public + * @param string $name 指令名称 + * @return bool + */ + public function has($name) + { + return isset($this->commands[$name]); + } + + /** + * 获取所有的命名空间 + * @access public + * @return array + */ + public function getNamespaces() + { + $namespaces = []; + + foreach ($this->commands as $command) { + $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($namespace) + { + $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { + return preg_quote($matches[1]) . '[^:]*'; + }, $namespace); + + $allNamespaces = $this->getNamespaces(); + $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($name) + { + $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { + return preg_quote($matches[1]) . '[^:]*'; + }, $name); + + $allCommands = array_keys($this->commands); + $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); + } + + if (count($commands) > 1) { + $commandList = $this->commands; + $commands = array_filter($commands, function ($nameOrAlias) use ($commandList, $commands) { + $commandName = $commandList[$nameOrAlias]->getName(); + + return $commandName === $nameOrAlias || !in_array($commandName, $commands); + }); + } + + $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->get($exact ? $name : reset($commands)); + } + + /** + * 获取所有的指令 + * @access public + * @param string $namespace 命名空间 + * @return Command[] + */ + public function all($namespace = null) + { + if (null === $namespace) return $this->commands; + + $commands = []; + + foreach ($this->commands as $name => $command) { + $ext = $this->extractNamespace($name, substr_count($namespace, ':') + 1); + + if ($ext === $namespace) $commands[$name] = $command; + } + + return $commands; + } + + /** + * 获取可能的指令名 + * @access public + * @param array $names 指令名 + * @return array + */ + public static function getAbbreviations($names) + { + $abbrevs = []; + foreach ($names as $name) { + for ($len = strlen($name); $len > 0; --$len) { + $abbrev = substr($name, 0, $len); + $abbrevs[$abbrev][] = $name; + } + } + + return $abbrevs; + } + + /** + * 配置基于用户的参数和选项的输入和输出实例 + * @access protected + * @param Input $input 输入实例 + * @param Output $output 输出实例 + * @return void + */ + protected function configureIO(Input $input, Output $output) + { + 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); + } else { + if ($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) + { + return $input->getFirstArgument(); + } + + /** + * 获取默认输入定义 + * @access protected + * @return InputDefinition + */ + protected function getDefaultInputDefinition() + { + 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 protected + * @return Command[] + */ + protected function getDefaultCommands() + { + $defaultCommands = []; + + foreach (self::$defaultCommands as $class) { + if (class_exists($class) && is_subclass_of($class, "think\\console\\Command")) { + $defaultCommands[] = new $class(); + } + } + + return $defaultCommands; + } + + /** + * 添加默认指令 + * @access public + * @param array $classes 指令 + * @return void + */ + public static function addDefaultCommands(array $classes) + { + self::$defaultCommands = array_merge(self::$defaultCommands, $classes); + } + + /** + * 获取可能的建议 + * @access private + * @param array $abbrevs + * @return string + */ + private function getAbbreviationSuggestions($abbrevs) + { + 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 string $limit 部分的命名空间的最大数量 + * @return string + */ + public function extractNamespace($name, $limit = null) + { + $parts = explode(':', $name); + array_pop($parts); + + return implode(':', null === $limit ? $parts : array_slice($parts, 0, $limit)); + } + + /** + * 查找可替代的建议 + * @access private + * @param string $name 指令名称 + * @param array|\Traversable $collection 建议集合 + * @return array + */ + private function findAlternatives($name, $collection) + { + $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 public + * @param string $commandName 指令名称 + * @return $this + */ + public function setDefaultCommand($commandName) + { + $this->defaultCommand = $commandName; + + return $this; + } + + /** + * 返回所有的命名空间 + * @access private + * @param string $name 指令名称 + * @return array + */ + private function extractAllNamespaces($name) + { + $namespaces = []; + + foreach (explode(':', $name, -1) as $part) { + if (count($namespaces)) { + $namespaces[] = end($namespaces) . ':' . $part; + } else { + $namespaces[] = $part; + } + } + + return $namespaces; + } + +} diff --git a/thinkphp/library/think/Controller.php b/thinkphp/library/think/Controller.php new file mode 100644 index 0000000..77225b7 --- /dev/null +++ b/thinkphp/library/think/Controller.php @@ -0,0 +1,229 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\ValidateException; +use traits\controller\Jump; + +Loader::import('controller/Jump', TRAIT_PATH, EXT); + +class Controller +{ + use Jump; + + /** + * @var \think\View 视图类实例 + */ + protected $view; + + /** + * @var \think\Request Request 实例 + */ + protected $request; + + /** + * @var bool 验证失败是否抛出异常 + */ + protected $failException = false; + + /** + * @var bool 是否批量验证 + */ + protected $batchValidate = false; + + /** + * @var array 前置操作方法列表 + */ + protected $beforeActionList = []; + + /** + * 构造方法 + * @access public + * @param Request $request Request 对象 + */ + public function __construct(Request $request = null) + { + $this->view = View::instance(Config::get('template'), Config::get('view_replace_str')); + $this->request = is_null($request) ? Request::instance() : $request; + + // 控制器初始化 + $this->_initialize(); + + // 前置操作方法 + if ($this->beforeActionList) { + foreach ($this->beforeActionList as $method => $options) { + is_numeric($method) ? + $this->beforeAction($options) : + $this->beforeAction($method, $options); + } + } + } + + /** + * 初始化操作 + * @access protected + */ + protected function _initialize() + { + } + + /** + * 前置操作 + * @access protected + * @param string $method 前置操作方法名 + * @param array $options 调用参数 ['only'=>[...]] 或者 ['except'=>[...]] + * @return void + */ + protected function beforeAction($method, $options = []) + { + if (isset($options['only'])) { + if (is_string($options['only'])) { + $options['only'] = explode(',', $options['only']); + } + + if (!in_array($this->request->action(), $options['only'])) { + return; + } + } elseif (isset($options['except'])) { + if (is_string($options['except'])) { + $options['except'] = explode(',', $options['except']); + } + + if (in_array($this->request->action(), $options['except'])) { + return; + } + } + + call_user_func([$this, $method]); + } + + /** + * 加载模板输出 + * @access protected + * @param string $template 模板文件名 + * @param array $vars 模板输出变量 + * @param array $replace 模板替换 + * @param array $config 模板参数 + * @return mixed + */ + protected function fetch($template = '', $vars = [], $replace = [], $config = []) + { + return $this->view->fetch($template, $vars, $replace, $config); + } + + /** + * 渲染内容输出 + * @access protected + * @param string $content 模板内容 + * @param array $vars 模板输出变量 + * @param array $replace 替换内容 + * @param array $config 模板参数 + * @return mixed + */ + protected function display($content = '', $vars = [], $replace = [], $config = []) + { + return $this->view->display($content, $vars, $replace, $config); + } + + /** + * 模板变量赋值 + * @access protected + * @param mixed $name 要显示的模板变量 + * @param mixed $value 变量的值 + * @return $this + */ + protected function assign($name, $value = '') + { + $this->view->assign($name, $value); + + return $this; + } + + /** + * 初始化模板引擎 + * @access protected + * @param array|string $engine 引擎参数 + * @return $this + */ + protected function engine($engine) + { + $this->view->engine($engine); + + return $this; + } + + /** + * 设置验证失败后是否抛出异常 + * @access protected + * @param bool $fail 是否抛出异常 + * @return $this + */ + protected function validateFailException($fail = true) + { + $this->failException = $fail; + + return $this; + } + + /** + * 验证数据 + * @access protected + * @param array $data 数据 + * @param string|array $validate 验证器名或者验证规则数组 + * @param array $message 提示信息 + * @param bool $batch 是否批量验证 + * @param mixed $callback 回调方法(闭包) + * @return array|string|true + * @throws ValidateException + */ + protected function validate($data, $validate, $message = [], $batch = false, $callback = null) + { + if (is_array($validate)) { + $v = Loader::validate(); + $v->rule($validate); + } else { + // 支持场景 + if (strpos($validate, '.')) { + list($validate, $scene) = explode('.', $validate); + } + + $v = Loader::validate($validate); + + !empty($scene) && $v->scene($scene); + } + + // 批量验证 + if ($batch || $this->batchValidate) { + $v->batch(true); + } + + // 设置错误信息 + if (is_array($message)) { + $v->message($message); + } + + // 使用回调验证 + if ($callback && is_callable($callback)) { + call_user_func_array($callback, [$v, &$data]); + } + + if (!$v->check($data)) { + if ($this->failException) { + throw new ValidateException($v->getError()); + } + + return $v->getError(); + } + + return true; + } +} diff --git a/thinkphp/library/think/Cookie.php b/thinkphp/library/think/Cookie.php new file mode 100644 index 0000000..61b47cc --- /dev/null +++ b/thinkphp/library/think/Cookie.php @@ -0,0 +1,268 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class Cookie +{ + /** + * @var array cookie 设置参数 + */ + protected static $config = [ + 'prefix' => '', // cookie 名称前缀 + 'expire' => 0, // cookie 保存时间 + 'path' => '/', // cookie 保存路径 + 'domain' => '', // cookie 有效域名 + 'secure' => false, // cookie 启用安全传输 + 'httponly' => false, // httponly 设置 + 'setcookie' => true, // 是否使用 setcookie + ]; + + /** + * @var bool 是否完成初始化了 + */ + protected static $init; + + /** + * Cookie初始化 + * @access public + * @param array $config 配置参数 + * @return void + */ + public static function init(array $config = []) + { + if (empty($config)) { + $config = Config::get('cookie'); + } + + self::$config = array_merge(self::$config, array_change_key_case($config)); + + if (!empty(self::$config['httponly'])) { + ini_set('session.cookie_httponly', 1); + } + + self::$init = true; + } + + /** + * 设置或者获取 cookie 作用域(前缀) + * @access public + * @param string $prefix 前缀 + * @return string| + */ + public static function prefix($prefix = '') + { + if (empty($prefix)) { + return self::$config['prefix']; + } + + return self::$config['prefix'] = $prefix; + } + + /** + * Cookie 设置、获取、删除 + * @access public + * @param string $name cookie 名称 + * @param mixed $value cookie 值 + * @param mixed $option 可选参数 可能会是 null|integer|string + * @return void + */ + public static function set($name, $value = '', $option = null) + { + !isset(self::$init) && self::init(); + + // 参数设置(会覆盖黙认设置) + if (!is_null($option)) { + if (is_numeric($option)) { + $option = ['expire' => $option]; + } elseif (is_string($option)) { + parse_str($option, $option); + } + + $config = array_merge(self::$config, array_change_key_case($option)); + } else { + $config = self::$config; + } + + $name = $config['prefix'] . $name; + + // 设置 cookie + if (is_array($value)) { + array_walk_recursive($value, 'self::jsonFormatProtect', 'encode'); + $value = 'think:' . json_encode($value); + } + + $expire = !empty($config['expire']) ? + $_SERVER['REQUEST_TIME'] + intval($config['expire']) : + 0; + + if ($config['setcookie']) { + setcookie( + $name, $value, $expire, $config['path'], $config['domain'], + $config['secure'], $config['httponly'] + ); + } + + $_COOKIE[$name] = $value; + } + + /** + * 永久保存 Cookie 数据 + * @access public + * @param string $name cookie 名称 + * @param mixed $value cookie 值 + * @param mixed $option 可选参数 可能会是 null|integer|string + * @return void + */ + public static function forever($name, $value = '', $option = null) + { + if (is_null($option) || is_numeric($option)) { + $option = []; + } + + $option['expire'] = 315360000; + + self::set($name, $value, $option); + } + + /** + * 判断是否有 Cookie 数据 + * @access public + * @param string $name cookie 名称 + * @param string|null $prefix cookie 前缀 + * @return bool + */ + public static function has($name, $prefix = null) + { + !isset(self::$init) && self::init(); + + $prefix = !is_null($prefix) ? $prefix : self::$config['prefix']; + + return isset($_COOKIE[$prefix . $name]); + } + + /** + * 获取 Cookie 的值 + * @access public + * @param string $name cookie 名称 + * @param string|null $prefix cookie 前缀 + * @return mixed + */ + public static function get($name = '', $prefix = null) + { + !isset(self::$init) && self::init(); + + $prefix = !is_null($prefix) ? $prefix : self::$config['prefix']; + $key = $prefix . $name; + + if ('' == $name) { + // 获取全部 + if ($prefix) { + $value = []; + + foreach ($_COOKIE as $k => $val) { + if (0 === strpos($k, $prefix)) { + $value[$k] = $val; + } + + } + } else { + $value = $_COOKIE; + } + } elseif (isset($_COOKIE[$key])) { + $value = $_COOKIE[$key]; + + if (0 === strpos($value, 'think:')) { + $value = json_decode(substr($value, 6), true); + array_walk_recursive($value, 'self::jsonFormatProtect', 'decode'); + } + } else { + $value = null; + } + + return $value; + } + + /** + * 删除 Cookie + * @access public + * @param string $name cookie 名称 + * @param string|null $prefix cookie 前缀 + * @return void + */ + public static function delete($name, $prefix = null) + { + !isset(self::$init) && self::init(); + + $config = self::$config; + $prefix = !is_null($prefix) ? $prefix : $config['prefix']; + $name = $prefix . $name; + + if ($config['setcookie']) { + setcookie( + $name, '', $_SERVER['REQUEST_TIME'] - 3600, $config['path'], + $config['domain'], $config['secure'], $config['httponly'] + ); + } + + // 删除指定 cookie + unset($_COOKIE[$name]); + } + + /** + * 清除指定前缀的所有 cookie + * @access public + * @param string|null $prefix cookie 前缀 + * @return void + */ + public static function clear($prefix = null) + { + if (empty($_COOKIE)) { + return; + } + + !isset(self::$init) && self::init(); + + // 要删除的 cookie 前缀,不指定则删除 config 设置的指定前缀 + $config = self::$config; + $prefix = !is_null($prefix) ? $prefix : $config['prefix']; + + if ($prefix) { + foreach ($_COOKIE as $key => $val) { + if (0 === strpos($key, $prefix)) { + if ($config['setcookie']) { + setcookie( + $key, '', $_SERVER['REQUEST_TIME'] - 3600, $config['path'], + $config['domain'], $config['secure'], $config['httponly'] + ); + } + + unset($_COOKIE[$key]); + } + } + } + } + + /** + * json 转换时的格式保护 + * @access protected + * @param mixed $val 要转换的值 + * @param string $key 键名 + * @param string $type 转换类别 + * @return void + */ + protected static function jsonFormatProtect(&$val, $key, $type = 'encode') + { + if (!empty($val) && true !== $val) { + $val = 'decode' == $type ? urldecode($val) : urlencode($val); + } + } +} diff --git a/thinkphp/library/think/Db.php b/thinkphp/library/think/Db.php new file mode 100644 index 0000000..c4b5f00 --- /dev/null +++ b/thinkphp/library/think/Db.php @@ -0,0 +1,180 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\db\Connection; +use think\db\Query; + +/** + * Class Db + * @package think + * @method static Query table(string $table) 指定数据表(含前缀) + * @method static Query name(string $name) 指定数据表(不含前缀) + * @method static Query where(mixed $field, string $op = null, mixed $condition = null) 查询条件 + * @method static Query join(mixed $join, mixed $condition = null, string $type = 'INNER') JOIN查询 + * @method static Query union(mixed $union, boolean $all = false) UNION查询 + * @method static Query limit(mixed $offset, integer $length = null) 查询LIMIT + * @method static Query order(mixed $field, string $order = null) 查询ORDER + * @method static Query cache(mixed $key = null , integer $expire = null) 设置查询缓存 + * @method static mixed value(string $field) 获取某个字段的值 + * @method static array column(string $field, string $key = '') 获取某个列的值 + * @method static Query view(mixed $join, mixed $field = null, mixed $on = null, string $type = 'INNER') 视图查询 + * @method static mixed find(mixed $data = null) 查询单个记录 + * @method static mixed select(mixed $data = null) 查询多个记录 + * @method static integer insert(array $data, boolean $replace = false, boolean $getLastInsID = false, string $sequence = null) 插入一条记录 + * @method static integer insertGetId(array $data, boolean $replace = false, string $sequence = null) 插入一条记录并返回自增ID + * @method static integer insertAll(array $dataSet) 插入多条记录 + * @method static integer update(array $data) 更新记录 + * @method static integer delete(mixed $data = null) 删除记录 + * @method static boolean chunk(integer $count, callable $callback, string $column = null) 分块获取数据 + * @method static mixed query(string $sql, array $bind = [], boolean $master = false, bool $pdo = false) SQL查询 + * @method static integer execute(string $sql, array $bind = [], boolean $fetch = false, boolean $getLastInsID = false, string $sequence = null) SQL执行 + * @method static Paginator paginate(integer $listRows = 15, mixed $simple = null, array $config = []) 分页查询 + * @method static mixed transaction(callable $callback) 执行数据库事务 + * @method static void startTrans() 启动事务 + * @method static void commit() 用于非自动提交状态下面的查询提交 + * @method static void rollback() 事务回滚 + * @method static boolean batchQuery(array $sqlArray) 批处理执行SQL语句 + * @method static string quote(string $str) SQL指令安全过滤 + * @method static string getLastInsID($sequence = null) 获取最近插入的ID + */ +class Db +{ + /** + * @var Connection[] 数据库连接实例 + */ + private static $instance = []; + + /** + * @var int 查询次数 + */ + public static $queryTimes = 0; + + /** + * @var int 执行次数 + */ + public static $executeTimes = 0; + + /** + * 数据库初始化,并取得数据库类实例 + * @access public + * @param mixed $config 连接配置 + * @param bool|string $name 连接标识 true 强制重新连接 + * @return Connection + * @throws Exception + */ + public static function connect($config = [], $name = false) + { + if (false === $name) { + $name = md5(serialize($config)); + } + + if (true === $name || !isset(self::$instance[$name])) { + // 解析连接参数 支持数组和字符串 + $options = self::parseConfig($config); + + if (empty($options['type'])) { + throw new \InvalidArgumentException('Undefined db type'); + } + + $class = false !== strpos($options['type'], '\\') ? + $options['type'] : + '\\think\\db\\connector\\' . ucwords($options['type']); + + // 记录初始化信息 + if (App::$debug) { + Log::record('[ DB ] INIT ' . $options['type'], 'info'); + } + + if (true === $name) { + $name = md5(serialize($config)); + } + + self::$instance[$name] = new $class($options); + } + + return self::$instance[$name]; + } + + /** + * 清除连接实例 + * @access public + * @return void + */ + public static function clear() + { + self::$instance = []; + } + + /** + * 数据库连接参数解析 + * @access private + * @param mixed $config 连接参数 + * @return array + */ + private static function parseConfig($config) + { + if (empty($config)) { + $config = Config::get('database'); + } elseif (is_string($config) && false === strpos($config, '/')) { + $config = Config::get($config); // 支持读取配置参数 + } + + return is_string($config) ? self::parseDsn($config) : $config; + } + + /** + * DSN 解析 + * 格式: mysql://username:passwd@localhost:3306/DbName?param1=val1¶m2=val2#utf8 + * @access private + * @param string $dsnStr 数据库 DSN 字符串解析 + * @return array + */ + private static function parseDsn($dsnStr) + { + $info = parse_url($dsnStr); + + if (!$info) { + return []; + } + + $dsn = [ + 'type' => $info['scheme'], + 'username' => isset($info['user']) ? $info['user'] : '', + 'password' => isset($info['pass']) ? $info['pass'] : '', + 'hostname' => isset($info['host']) ? $info['host'] : '', + 'hostport' => isset($info['port']) ? $info['port'] : '', + 'database' => !empty($info['path']) ? ltrim($info['path'], '/') : '', + 'charset' => isset($info['fragment']) ? $info['fragment'] : 'utf8', + ]; + + if (isset($info['query'])) { + parse_str($info['query'], $dsn['params']); + } else { + $dsn['params'] = []; + } + + return $dsn; + } + + /** + * 调用驱动类的方法 + * @access public + * @param string $method 方法名 + * @param array $params 参数 + * @return mixed + */ + public static function __callStatic($method, $params) + { + return call_user_func_array([self::connect(), $method], $params); + } +} diff --git a/thinkphp/library/think/Debug.php b/thinkphp/library/think/Debug.php new file mode 100644 index 0000000..df48748 --- /dev/null +++ b/thinkphp/library/think/Debug.php @@ -0,0 +1,252 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\ClassNotFoundException; +use think\response\Redirect; + +class Debug +{ + /** + * @var array 区间时间信息 + */ + protected static $info = []; + + /** + * @var array 区间内存信息 + */ + protected static $mem = []; + + /** + * 记录时间(微秒)和内存使用情况 + * @access public + * @param string $name 标记位置 + * @param mixed $value 标记值(留空则取当前 time 表示仅记录时间 否则同时记录时间和内存) + * @return void + */ + public static function remark($name, $value = '') + { + self::$info[$name] = is_float($value) ? $value : microtime(true); + + if ('time' != $value) { + self::$mem['mem'][$name] = is_float($value) ? $value : memory_get_usage(); + self::$mem['peak'][$name] = memory_get_peak_usage(); + } + } + + /** + * 统计某个区间的时间(微秒)使用情况 返回值以秒为单位 + * @access public + * @param string $start 开始标签 + * @param string $end 结束标签 + * @param integer $dec 小数位 + * @return string + */ + public static function getRangeTime($start, $end, $dec = 6) + { + if (!isset(self::$info[$end])) { + self::$info[$end] = microtime(true); + } + + return number_format((self::$info[$end] - self::$info[$start]), $dec); + } + + /** + * 统计从开始到统计时的时间(微秒)使用情况 返回值以秒为单位 + * @access public + * @param integer $dec 小数位 + * @return string + */ + public static function getUseTime($dec = 6) + { + return number_format((microtime(true) - THINK_START_TIME), $dec); + } + + /** + * 获取当前访问的吞吐率情况 + * @access public + * @return string + */ + public static function getThroughputRate() + { + return number_format(1 / self::getUseTime(), 2) . 'req/s'; + } + + /** + * 记录区间的内存使用情况 + * @access public + * @param string $start 开始标签 + * @param string $end 结束标签 + * @param integer $dec 小数位 + * @return string + */ + public static function getRangeMem($start, $end, $dec = 2) + { + if (!isset(self::$mem['mem'][$end])) { + self::$mem['mem'][$end] = memory_get_usage(); + } + + $size = self::$mem['mem'][$end] - self::$mem['mem'][$start]; + $a = ['B', 'KB', 'MB', 'GB', 'TB']; + $pos = 0; + + while ($size >= 1024) { + $size /= 1024; + $pos++; + } + + return round($size, $dec) . " " . $a[$pos]; + } + + /** + * 统计从开始到统计时的内存使用情况 + * @access public + * @param integer $dec 小数位 + * @return string + */ + public static function getUseMem($dec = 2) + { + $size = memory_get_usage() - THINK_START_MEM; + $a = ['B', 'KB', 'MB', 'GB', 'TB']; + $pos = 0; + + while ($size >= 1024) { + $size /= 1024; + $pos++; + } + + return round($size, $dec) . " " . $a[$pos]; + } + + /** + * 统计区间的内存峰值情况 + * @access public + * @param string $start 开始标签 + * @param string $end 结束标签 + * @param integer $dec 小数位 + * @return string + */ + public static function getMemPeak($start, $end, $dec = 2) + { + if (!isset(self::$mem['peak'][$end])) { + self::$mem['peak'][$end] = memory_get_peak_usage(); + } + + $size = self::$mem['peak'][$end] - self::$mem['peak'][$start]; + $a = ['B', 'KB', 'MB', 'GB', 'TB']; + $pos = 0; + + while ($size >= 1024) { + $size /= 1024; + $pos++; + } + + return round($size, $dec) . " " . $a[$pos]; + } + + /** + * 获取文件加载信息 + * @access public + * @param bool $detail 是否显示详细 + * @return integer|array + */ + public static function getFile($detail = false) + { + $files = get_included_files(); + + if ($detail) { + $info = []; + + foreach ($files as $file) { + $info[] = $file . ' ( ' . number_format(filesize($file) / 1024, 2) . ' KB )'; + } + + return $info; + } + + return count($files); + } + + /** + * 浏览器友好的变量输出 + * @access public + * @param mixed $var 变量 + * @param boolean $echo 是否输出(默认为 true,为 false 则返回输出字符串) + * @param string|null $label 标签(默认为空) + * @param integer $flags htmlspecialchars 的标志 + * @return null|string + */ + public static function dump($var, $echo = true, $label = null, $flags = ENT_SUBSTITUTE) + { + $label = (null === $label) ? '' : rtrim($label) . ':'; + + ob_start(); + var_dump($var); + $output = preg_replace('/\]\=\>\n(\s+)/m', '] => ', ob_get_clean()); + + if (IS_CLI) { + $output = PHP_EOL . $label . $output . PHP_EOL; + } else { + if (!extension_loaded('xdebug')) { + $output = htmlspecialchars($output, $flags); + } + + $output = '
' . $label . $output . '
'; + } + + if ($echo) { + echo($output); + return; + } + + return $output; + } + + /** + * 调试信息注入到响应中 + * @access public + * @param Response $response 响应实例 + * @param string $content 返回的字符串 + * @return void + */ + public static function inject(Response $response, &$content) + { + $config = Config::get('trace'); + $type = isset($config['type']) ? $config['type'] : 'Html'; + $class = false !== strpos($type, '\\') ? $type : '\\think\\debug\\' . ucwords($type); + + unset($config['type']); + + if (!class_exists($class)) { + throw new ClassNotFoundException('class not exists:' . $class, $class); + } + + /** @var \think\debug\Console|\think\debug\Html $trace */ + $trace = new $class($config); + + if ($response instanceof Redirect) { + // TODO 记录 + } else { + $output = $trace->output($response, Log::getLog()); + + if (is_string($output)) { + // trace 调试信息注入 + $pos = strripos($content, ''); + if (false !== $pos) { + $content = substr($content, 0, $pos) . $output . substr($content, $pos); + } else { + $content = $content . $output; + } + } + } + } +} diff --git a/thinkphp/library/think/Env.php b/thinkphp/library/think/Env.php new file mode 100644 index 0000000..0a8b250 --- /dev/null +++ b/thinkphp/library/think/Env.php @@ -0,0 +1,39 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class Env +{ + /** + * 获取环境变量值 + * @access public + * @param string $name 环境变量名(支持二级 . 号分割) + * @param string $default 默认值 + * @return mixed + */ + public static function get($name, $default = null) + { + $result = getenv(ENV_PREFIX . strtoupper(str_replace('.', '_', $name))); + + if (false !== $result) { + if ('false' === $result) { + $result = false; + } elseif ('true' === $result) { + $result = true; + } + + return $result; + } + + return $default; + } +} diff --git a/thinkphp/library/think/Error.php b/thinkphp/library/think/Error.php new file mode 100644 index 0000000..5f361d5 --- /dev/null +++ b/thinkphp/library/think/Error.php @@ -0,0 +1,136 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\console\Output as ConsoleOutput; +use think\exception\ErrorException; +use think\exception\Handle; +use think\exception\ThrowableError; + +class Error +{ + /** + * 注册异常处理 + * @access public + * @return void + */ + public static function register() + { + error_reporting(E_ALL); + set_error_handler([__CLASS__, 'appError']); + set_exception_handler([__CLASS__, 'appException']); + register_shutdown_function([__CLASS__, 'appShutdown']); + } + + /** + * 异常处理 + * @access public + * @param \Exception|\Throwable $e 异常 + * @return void + */ + public static function appException($e) + { + if (!$e instanceof \Exception) { + $e = new ThrowableError($e); + } + + $handler = self::getExceptionHandler(); + $handler->report($e); + + if (IS_CLI) { + $handler->renderForConsole(new ConsoleOutput, $e); + } else { + $handler->render($e)->send(); + } + } + + /** + * 错误处理 + * @access public + * @param integer $errno 错误编号 + * @param integer $errstr 详细错误信息 + * @param string $errfile 出错的文件 + * @param integer $errline 出错行号 + * @return void + * @throws ErrorException + */ + public static function appError($errno, $errstr, $errfile = '', $errline = 0) + { + $exception = new ErrorException($errno, $errstr, $errfile, $errline); + + // 符合异常处理的则将错误信息托管至 think\exception\ErrorException + if (error_reporting() & $errno) { + throw $exception; + } + + self::getExceptionHandler()->report($exception); + } + + /** + * 异常中止处理 + * @access public + * @return void + */ + public static function appShutdown() + { + // 将错误信息托管至 think\ErrorException + if (!is_null($error = error_get_last()) && self::isFatal($error['type'])) { + self::appException(new ErrorException( + $error['type'], $error['message'], $error['file'], $error['line'] + )); + } + + // 写入日志 + Log::save(); + } + + /** + * 确定错误类型是否致命 + * @access protected + * @param int $type 错误类型 + * @return bool + */ + protected static function isFatal($type) + { + return in_array($type, [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE]); + } + + /** + * 获取异常处理的实例 + * @access public + * @return Handle + */ + public static function getExceptionHandler() + { + static $handle; + + if (!$handle) { + // 异常处理 handle + $class = Config::get('exception_handle'); + + if ($class && is_string($class) && class_exists($class) && + is_subclass_of($class, "\\think\\exception\\Handle") + ) { + $handle = new $class; + } else { + $handle = new Handle; + + if ($class instanceof \Closure) { + $handle->setRender($class); + } + + } + } + + return $handle; + } +} diff --git a/thinkphp/library/think/Exception.php b/thinkphp/library/think/Exception.php new file mode 100644 index 0000000..1ef06bd --- /dev/null +++ b/thinkphp/library/think/Exception.php @@ -0,0 +1,55 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class Exception extends \Exception +{ + /** + * @var array 保存异常页面显示的额外 Debug 数据 + */ + 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 需要显示的数据,必须为关联数组 + * @return void + */ + final protected function setData($label, array $data) + { + $this->data[$label] = $data; + } + + /** + * 获取异常额外 Debug 数据 + * 主要用于输出到异常页面便于调试 + * @access public + * @return array + */ + final public function getData() + { + return $this->data; + } + +} diff --git a/thinkphp/library/think/File.php b/thinkphp/library/think/File.php new file mode 100644 index 0000000..d2ed220 --- /dev/null +++ b/thinkphp/library/think/File.php @@ -0,0 +1,478 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use SplFileObject; + +class File extends SplFileObject +{ + /** + * @var string 错误信息 + */ + private $error = ''; + + /** + * @var string 当前完整文件名 + */ + protected $filename; + + /** + * @var string 上传文件名 + */ + protected $saveName; + + /** + * @var string 文件上传命名规则 + */ + protected $rule = 'date'; + + /** + * @var array 文件上传验证规则 + */ + protected $validate = []; + + /** + * @var bool 单元测试 + */ + protected $isTest; + + /** + * @var array 上传文件信息 + */ + protected $info; + + /** + * @var array 文件 hash 信息 + */ + protected $hash = []; + + /** + * File constructor. + * @access public + * @param string $filename 文件名称 + * @param string $mode 访问模式 + */ + public function __construct($filename, $mode = 'r') + { + parent::__construct($filename, $mode); + $this->filename = $this->getRealPath() ?: $this->getPathname(); + } + + /** + * 设置是否是单元测试 + * @access public + * @param bool $test 是否是测试 + * @return $this + */ + public function isTest($test = false) + { + $this->isTest = $test; + + return $this; + } + + /** + * 设置上传信息 + * @access public + * @param array $info 上传文件信息 + * @return $this + */ + public function setUploadInfo($info) + { + $this->info = $info; + + return $this; + } + + /** + * 获取上传文件的信息 + * @access public + * @param string $name 信息名称 + * @return array|string + */ + public function getInfo($name = '') + { + return isset($this->info[$name]) ? $this->info[$name] : $this->info; + } + + /** + * 获取上传文件的文件名 + * @access public + * @return string + */ + public function getSaveName() + { + return $this->saveName; + } + + /** + * 设置上传文件的保存文件名 + * @access public + * @param string $saveName 保存名称 + * @return $this + */ + public function setSaveName($saveName) + { + $this->saveName = $saveName; + + return $this; + } + + /** + * 获取文件的哈希散列值 + * @access public + * @param string $type 类型 + * @return string + */ + public function hash($type = 'sha1') + { + if (!isset($this->hash[$type])) { + $this->hash[$type] = hash_file($type, $this->filename); + } + + return $this->hash[$type]; + } + + /** + * 检查目录是否可写 + * @access protected + * @param string $path 目录 + * @return boolean + */ + protected function checkPath($path) + { + if (is_dir($path) || mkdir($path, 0755, true)) { + return true; + } + + $this->error = ['directory {:path} creation failed', ['path' => $path]]; + + return false; + } + + /** + * 获取文件类型信息 + * @access public + * @return string + */ + public function getMime() + { + $finfo = finfo_open(FILEINFO_MIME_TYPE); + + return finfo_file($finfo, $this->filename); + } + + /** + * 设置文件的命名规则 + * @access public + * @param string $rule 文件命名规则 + * @return $this + */ + public function rule($rule) + { + $this->rule = $rule; + + return $this; + } + + /** + * 设置上传文件的验证规则 + * @access public + * @param array $rule 验证规则 + * @return $this + */ + public function validate(array $rule = []) + { + $this->validate = $rule; + + return $this; + } + + /** + * 检测是否合法的上传文件 + * @access public + * @return bool + */ + public function isValid() + { + return $this->isTest ? is_file($this->filename) : is_uploaded_file($this->filename); + } + + /** + * 检测上传文件 + * @access public + * @param array $rule 验证规则 + * @return bool + */ + public function check($rule = []) + { + $rule = $rule ?: $this->validate; + + /* 检查文件大小 */ + if (isset($rule['size']) && !$this->checkSize($rule['size'])) { + $this->error = 'filesize not match'; + return false; + } + + /* 检查文件 Mime 类型 */ + if (isset($rule['type']) && !$this->checkMime($rule['type'])) { + $this->error = 'mimetype to upload is not allowed'; + return false; + } + + /* 检查文件后缀 */ + if (isset($rule['ext']) && !$this->checkExt($rule['ext'])) { + $this->error = 'extensions to upload is not allowed'; + return false; + } + + /* 检查图像文件 */ + if (!$this->checkImg()) { + $this->error = 'illegal image files'; + return false; + } + + return true; + } + + /** + * 检测上传文件后缀 + * @access public + * @param array|string $ext 允许后缀 + * @return bool + */ + public function checkExt($ext) + { + if (is_string($ext)) { + $ext = explode(',', $ext); + } + + $extension = strtolower(pathinfo($this->getInfo('name'), PATHINFO_EXTENSION)); + + return in_array($extension, $ext); + } + + /** + * 检测图像文件 + * @access public + * @return bool + */ + public function checkImg() + { + $extension = strtolower(pathinfo($this->getInfo('name'), PATHINFO_EXTENSION)); + + // 如果上传的不是图片,或者是图片而且后缀确实符合图片类型则返回 true + return !in_array($extension, ['gif', 'jpg', 'jpeg', 'bmp', 'png', 'swf']) || in_array($this->getImageType($this->filename), [1, 2, 3, 4, 6, 13]); + } + + /** + * 判断图像类型 + * @access protected + * @param string $image 图片名称 + * @return bool|int + */ + 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 integer $size 最大大小 + * @return bool + */ + public function checkSize($size) + { + return $this->getSize() <= $size; + } + + /** + * 检测上传文件类型 + * @access public + * @param array|string $mime 允许类型 + * @return bool + */ + public function checkMime($mime) + { + $mime = is_string($mime) ? explode(',', $mime) : $mime; + + return in_array(strtolower($this->getMime()), $mime); + } + + /** + * 移动文件 + * @access public + * @param string $path 保存路径 + * @param string|bool $savename 保存的文件名 默认自动生成 + * @param boolean $replace 同名文件是否覆盖 + * @return false|File + */ + public function move($path, $savename = true, $replace = true) + { + // 文件上传失败,捕获错误代码 + if (!empty($this->info['error'])) { + $this->error($this->info['error']); + return false; + } + + // 检测合法性 + if (!$this->isValid()) { + $this->error = 'upload illegal files'; + return false; + } + + // 验证上传 + if (!$this->check()) { + return false; + } + + $path = rtrim($path, DS) . DS; + // 文件保存命名规则 + $saveName = $this->buildSaveName($savename); + $filename = $path . $saveName; + + // 检测目录 + if (false === $this->checkPath(dirname($filename))) { + return false; + } + + // 不覆盖同名文件 + if (!$replace && is_file($filename)) { + $this->error = ['has the same filename: {:filename}', ['filename' => $filename]]; + return false; + } + + /* 移动文件 */ + if ($this->isTest) { + rename($this->filename, $filename); + } elseif (!move_uploaded_file($this->filename, $filename)) { + $this->error = 'upload write error'; + return false; + } + + // 返回 File 对象实例 + $file = new self($filename); + $file->setSaveName($saveName)->setUploadInfo($this->info); + + return $file; + } + + /** + * 获取保存文件名 + * @access protected + * @param string|bool $savename 保存的文件名 默认自动生成 + * @return string + */ + protected function buildSaveName($savename) + { + // 自动生成文件名 + if (true === $savename) { + if ($this->rule instanceof \Closure) { + $savename = call_user_func_array($this->rule, [$this]); + } else { + switch ($this->rule) { + case 'date': + $savename = date('Ymd') . DS . md5(microtime(true)); + break; + default: + if (in_array($this->rule, hash_algos())) { + $hash = $this->hash($this->rule); + $savename = substr($hash, 0, 2) . DS . substr($hash, 2); + } elseif (is_callable($this->rule)) { + $savename = call_user_func($this->rule); + } else { + $savename = date('Ymd') . DS . md5(microtime(true)); + } + } + } + } elseif ('' === $savename || false === $savename) { + $savename = $this->getInfo('name'); + } + + if (!strpos($savename, '.')) { + $savename .= '.' . pathinfo($this->getInfo('name'), PATHINFO_EXTENSION); + } + + return $savename; + } + + /** + * 获取错误代码信息 + * @access private + * @param int $errorNo 错误号 + * @return $this + */ + private function error($errorNo) + { + switch ($errorNo) { + case 1: + case 2: + $this->error = 'upload File size exceeds the maximum value'; + break; + case 3: + $this->error = 'only the portion of file is uploaded'; + break; + case 4: + $this->error = 'no file to uploaded'; + break; + case 6: + $this->error = 'upload temp dir not found'; + break; + case 7: + $this->error = 'file write error'; + break; + default: + $this->error = 'unknown upload error'; + } + + return $this; + } + + /** + * 获取错误信息(支持多语言) + * @access public + * @return string + */ + public function getError() + { + if (is_array($this->error)) { + list($msg, $vars) = $this->error; + } else { + $msg = $this->error; + $vars = []; + } + + return Lang::has($msg) ? Lang::get($msg, $vars) : $msg; + } + + /** + * 魔法方法,获取文件的 hash 值 + * @access public + * @param string $method 方法名 + * @param mixed $args 调用参数 + * @return string + */ + public function __call($method, $args) + { + return $this->hash($method); + } +} diff --git a/thinkphp/library/think/Hook.php b/thinkphp/library/think/Hook.php new file mode 100644 index 0000000..a69ce54 --- /dev/null +++ b/thinkphp/library/think/Hook.php @@ -0,0 +1,148 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class Hook +{ + /** + * @var array 标签 + */ + private static $tags = []; + + /** + * 动态添加行为扩展到某个标签 + * @access public + * @param string $tag 标签名称 + * @param mixed $behavior 行为名称 + * @param bool $first 是否放到开头执行 + * @return void + */ + public static function add($tag, $behavior, $first = false) + { + isset(self::$tags[$tag]) || self::$tags[$tag] = []; + + if (is_array($behavior) && !is_callable($behavior)) { + if (!array_key_exists('_overlay', $behavior) || !$behavior['_overlay']) { + unset($behavior['_overlay']); + self::$tags[$tag] = array_merge(self::$tags[$tag], $behavior); + } else { + unset($behavior['_overlay']); + self::$tags[$tag] = $behavior; + } + } elseif ($first) { + array_unshift(self::$tags[$tag], $behavior); + } else { + self::$tags[$tag][] = $behavior; + } + } + + /** + * 批量导入插件 + * @access public + * @param array $tags 插件信息 + * @param boolean $recursive 是否递归合并 + * @return void + */ + public static function import(array $tags, $recursive = true) + { + if ($recursive) { + foreach ($tags as $tag => $behavior) { + self::add($tag, $behavior); + } + } else { + self::$tags = $tags + self::$tags; + } + } + + /** + * 获取插件信息 + * @access public + * @param string $tag 插件位置(留空获取全部) + * @return array + */ + public static function get($tag = '') + { + if (empty($tag)) { + return self::$tags; + } + + return array_key_exists($tag, self::$tags) ? self::$tags[$tag] : []; + } + + /** + * 监听标签的行为 + * @access public + * @param string $tag 标签名称 + * @param mixed $params 传入参数 + * @param mixed $extra 额外参数 + * @param bool $once 只获取一个有效返回值 + * @return mixed + */ + public static function listen($tag, &$params = null, $extra = null, $once = false) + { + $results = []; + + foreach (static::get($tag) as $key => $name) { + $results[$key] = self::exec($name, $tag, $params, $extra); + + // 如果返回 false,或者仅获取一个有效返回则中断行为执行 + if (false === $results[$key] || (!is_null($results[$key]) && $once)) { + break; + } + } + + return $once ? end($results) : $results; + } + + /** + * 执行某个行为 + * @access public + * @param mixed $class 要执行的行为 + * @param string $tag 方法名(标签名) + * @param mixed $params 传人的参数 + * @param mixed $extra 额外参数 + * @return mixed + */ + public static function exec($class, $tag = '', &$params = null, $extra = null) + { + App::$debug && Debug::remark('behavior_start', 'time'); + + $method = Loader::parseName($tag, 1, false); + + if ($class instanceof \Closure) { + $result = call_user_func_array($class, [ & $params, $extra]); + $class = 'Closure'; + } elseif (is_array($class)) { + list($class, $method) = $class; + + $result = (new $class())->$method($params, $extra); + $class = $class . '->' . $method; + } elseif (is_object($class)) { + $result = $class->$method($params, $extra); + $class = get_class($class); + } elseif (strpos($class, '::')) { + $result = call_user_func_array($class, [ & $params, $extra]); + } else { + $obj = new $class(); + $method = ($tag && is_callable([$obj, $method])) ? $method : 'run'; + $result = $obj->$method($params, $extra); + } + + if (App::$debug) { + Debug::remark('behavior_end', 'time'); + Log::record('[ BEHAVIOR ] Run ' . $class . ' @' . $tag . ' [ RunTime:' . Debug::getRangeTime('behavior_start', 'behavior_end') . 's ]', 'info'); + } + + return $result; + } + +} diff --git a/thinkphp/library/think/Lang.php b/thinkphp/library/think/Lang.php new file mode 100644 index 0000000..a50d838 --- /dev/null +++ b/thinkphp/library/think/Lang.php @@ -0,0 +1,265 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class Lang +{ + /** + * @var array 语言数据 + */ + private static $lang = []; + + /** + * @var string 语言作用域 + */ + private static $range = 'zh-cn'; + + /** + * @var string 语言自动侦测的变量 + */ + protected static $langDetectVar = 'lang'; + + /** + * @var string 语言 Cookie 变量 + */ + protected static $langCookieVar = 'think_var'; + + /** + * @var int 语言 Cookie 的过期时间 + */ + protected static $langCookieExpire = 3600; + + /** + * @var array 允许语言列表 + */ + protected static $allowLangList = []; + + /** + * @var array Accept-Language 转义为对应语言包名称 系统默认配置 + */ + protected static $acceptLanguage = ['zh-hans-cn' => 'zh-cn']; + + /** + * 设定当前的语言 + * @access public + * @param string $range 语言作用域 + * @return string + */ + public static function range($range = '') + { + if ($range) { + self::$range = $range; + } + + return self::$range; + } + + /** + * 设置语言定义(不区分大小写) + * @access public + * @param string|array $name 语言变量 + * @param string $value 语言值 + * @param string $range 语言作用域 + * @return mixed + */ + public static function set($name, $value = null, $range = '') + { + $range = $range ?: self::$range; + + if (!isset(self::$lang[$range])) { + self::$lang[$range] = []; + } + + if (is_array($name)) { + return self::$lang[$range] = array_change_key_case($name) + self::$lang[$range]; + } + + return self::$lang[$range][strtolower($name)] = $value; + } + + /** + * 加载语言定义(不区分大小写) + * @access public + * @param array|string $file 语言文件 + * @param string $range 语言作用域 + * @return mixed + */ + public static function load($file, $range = '') + { + $range = $range ?: self::$range; + $file = is_string($file) ? [$file] : $file; + + if (!isset(self::$lang[$range])) { + self::$lang[$range] = []; + } + + $lang = []; + + foreach ($file as $_file) { + if (is_file($_file)) { + // 记录加载信息 + App::$debug && Log::record('[ LANG ] ' . $_file, 'info'); + + $_lang = include $_file; + + if (is_array($_lang)) { + $lang = array_change_key_case($_lang) + $lang; + } + } + } + + if (!empty($lang)) { + self::$lang[$range] = $lang + self::$lang[$range]; + } + + return self::$lang[$range]; + } + + /** + * 获取语言定义(不区分大小写) + * @access public + * @param string|null $name 语言变量 + * @param string $range 语言作用域 + * @return mixed + */ + public static function has($name, $range = '') + { + $range = $range ?: self::$range; + + return isset(self::$lang[$range][strtolower($name)]); + } + + /** + * 获取语言定义(不区分大小写) + * @access public + * @param string|null $name 语言变量 + * @param array $vars 变量替换 + * @param string $range 语言作用域 + * @return mixed + */ + public static function get($name = null, $vars = [], $range = '') + { + $range = $range ?: self::$range; + + // 空参数返回所有定义 + if (empty($name)) { + return self::$lang[$range]; + } + + $key = strtolower($name); + $value = isset(self::$lang[$range][$key]) ? self::$lang[$range][$key] : $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; + } + + /** + * 自动侦测设置获取语言选择 + * @access public + * @return string + */ + public static function detect() + { + $langSet = ''; + + if (isset($_GET[self::$langDetectVar])) { + // url 中设置了语言变量 + $langSet = strtolower($_GET[self::$langDetectVar]); + } elseif (isset($_COOKIE[self::$langCookieVar])) { + // Cookie 中设置了语言变量 + $langSet = strtolower($_COOKIE[self::$langCookieVar]); + } elseif (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { + // 自动侦测浏览器语言 + preg_match('/^([a-z\d\-]+)/i', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $matches); + $langSet = strtolower($matches[1]); + $acceptLangs = Config::get('header_accept_lang'); + + if (isset($acceptLangs[$langSet])) { + $langSet = $acceptLangs[$langSet]; + } elseif (isset(self::$acceptLanguage[$langSet])) { + $langSet = self::$acceptLanguage[$langSet]; + } + } + + // 合法的语言 + if (empty(self::$allowLangList) || in_array($langSet, self::$allowLangList)) { + self::$range = $langSet ?: self::$range; + } + + return self::$range; + } + + /** + * 设置语言自动侦测的变量 + * @access public + * @param string $var 变量名称 + * @return void + */ + public static function setLangDetectVar($var) + { + self::$langDetectVar = $var; + } + + /** + * 设置语言的 cookie 保存变量 + * @access public + * @param string $var 变量名称 + * @return void + */ + public static function setLangCookieVar($var) + { + self::$langCookieVar = $var; + } + + /** + * 设置语言的 cookie 的过期时间 + * @access public + * @param string $expire 过期时间 + * @return void + */ + public static function setLangCookieExpire($expire) + { + self::$langCookieExpire = $expire; + } + + /** + * 设置允许的语言列表 + * @access public + * @param array $list 语言列表 + * @return void + */ + public static function setAllowLangList($list) + { + self::$allowLangList = $list; + } +} diff --git a/thinkphp/library/think/Loader.php b/thinkphp/library/think/Loader.php new file mode 100644 index 0000000..d813a5d --- /dev/null +++ b/thinkphp/library/think/Loader.php @@ -0,0 +1,677 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\ClassNotFoundException; + +class Loader +{ + /** + * @var array 实例数组 + */ + protected static $instance = []; + + /** + * @var array 类名映射 + */ + protected static $classMap = []; + + /** + * @var array 命名空间别名 + */ + protected static $namespaceAlias = []; + + /** + * @var array PSR-4 命名空间前缀长度映射 + */ + private static $prefixLengthsPsr4 = []; + + /** + * @var array PSR-4 的加载目录 + */ + private static $prefixDirsPsr4 = []; + + /** + * @var array PSR-4 加载失败的回退目录 + */ + private static $fallbackDirsPsr4 = []; + + /** + * @var array PSR-0 命名空间前缀映射 + */ + private static $prefixesPsr0 = []; + + /** + * @var array PSR-0 加载失败的回退目录 + */ + private static $fallbackDirsPsr0 = []; + + /** + * @var array 需要加载的文件 + */ + private static $files = []; + + /** + * 自动加载 + * @access public + * @param string $class 类名 + * @return bool + */ + public static function autoload($class) + { + // 检测命名空间别名 + if (!empty(self::$namespaceAlias)) { + $namespace = dirname($class); + if (isset(self::$namespaceAlias[$namespace])) { + $original = self::$namespaceAlias[$namespace] . '\\' . basename($class); + if (class_exists($original)) { + return class_alias($original, $class, false); + } + } + } + + if ($file = self::findFile($class)) { + // 非 Win 环境不严格区分大小写 + if (!IS_WIN || pathinfo($file, PATHINFO_FILENAME) == pathinfo(realpath($file), PATHINFO_FILENAME)) { + __include_file($file); + return true; + } + } + + return false; + } + + /** + * 查找文件 + * @access private + * @param string $class 类名 + * @return bool|string + */ + private static function findFile($class) + { + // 类库映射 + if (!empty(self::$classMap[$class])) { + return self::$classMap[$class]; + } + + // 查找 PSR-4 + $logicalPathPsr4 = strtr($class, '\\', DS) . EXT; + $first = $class[0]; + + if (isset(self::$prefixLengthsPsr4[$first])) { + foreach (self::$prefixLengthsPsr4[$first] as $prefix => $length) { + if (0 === strpos($class, $prefix)) { + foreach (self::$prefixDirsPsr4[$prefix] as $dir) { + if (is_file($file = $dir . DS . substr($logicalPathPsr4, $length))) { + return $file; + } + } + } + } + } + + // 查找 PSR-4 fallback dirs + foreach (self::$fallbackDirsPsr4 as $dir) { + if (is_file($file = $dir . DS . $logicalPathPsr4)) { + return $file; + } + } + + // 查找 PSR-0 + if (false !== $pos = strrpos($class, '\\')) { + // namespace class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DS); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DS) . EXT; + } + + if (isset(self::$prefixesPsr0[$first])) { + foreach (self::$prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (is_file($file = $dir . DS . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // 查找 PSR-0 fallback dirs + foreach (self::$fallbackDirsPsr0 as $dir) { + if (is_file($file = $dir . DS . $logicalPathPsr0)) { + return $file; + } + } + + // 找不到则设置映射为 false 并返回 + return self::$classMap[$class] = false; + } + + /** + * 注册 classmap + * @access public + * @param string|array $class 类名 + * @param string $map 映射 + * @return void + */ + public static function addClassMap($class, $map = '') + { + if (is_array($class)) { + self::$classMap = array_merge(self::$classMap, $class); + } else { + self::$classMap[$class] = $map; + } + } + + /** + * 注册命名空间 + * @access public + * @param string|array $namespace 命名空间 + * @param string $path 路径 + * @return void + */ + public static function addNamespace($namespace, $path = '') + { + if (is_array($namespace)) { + foreach ($namespace as $prefix => $paths) { + self::addPsr4($prefix . '\\', rtrim($paths, DS), true); + } + } else { + self::addPsr4($namespace . '\\', rtrim($path, DS), true); + } + } + + /** + * 添加 PSR-0 命名空间 + * @access private + * @param array|string $prefix 空间前缀 + * @param array $paths 路径 + * @param bool $prepend 预先设置的优先级更高 + * @return void + */ + private static function addPsr0($prefix, $paths, $prepend = false) + { + if (!$prefix) { + self::$fallbackDirsPsr0 = $prepend ? + array_merge((array) $paths, self::$fallbackDirsPsr0) : + array_merge(self::$fallbackDirsPsr0, (array) $paths); + } else { + $first = $prefix[0]; + + if (!isset(self::$prefixesPsr0[$first][$prefix])) { + self::$prefixesPsr0[$first][$prefix] = (array) $paths; + } else { + self::$prefixesPsr0[$first][$prefix] = $prepend ? + array_merge((array) $paths, self::$prefixesPsr0[$first][$prefix]) : + array_merge(self::$prefixesPsr0[$first][$prefix], (array) $paths); + } + } + } + + /** + * 添加 PSR-4 空间 + * @access private + * @param array|string $prefix 空间前缀 + * @param string $paths 路径 + * @param bool $prepend 预先设置的优先级更高 + * @return void + */ + private static function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + self::$fallbackDirsPsr4 = $prepend ? + array_merge((array) $paths, self::$fallbackDirsPsr4) : + array_merge(self::$fallbackDirsPsr4, (array) $paths); + + } elseif (!isset(self::$prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException( + "A non-empty PSR-4 prefix must end with a namespace separator." + ); + } + + self::$prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + self::$prefixDirsPsr4[$prefix] = (array) $paths; + + } else { + self::$prefixDirsPsr4[$prefix] = $prepend ? + // Prepend directories for an already registered namespace. + array_merge((array) $paths, self::$prefixDirsPsr4[$prefix]) : + // Append directories for an already registered namespace. + array_merge(self::$prefixDirsPsr4[$prefix], (array) $paths); + } + } + + /** + * 注册命名空间别名 + * @access public + * @param array|string $namespace 命名空间 + * @param string $original 源文件 + * @return void + */ + public static function addNamespaceAlias($namespace, $original = '') + { + if (is_array($namespace)) { + self::$namespaceAlias = array_merge(self::$namespaceAlias, $namespace); + } else { + self::$namespaceAlias[$namespace] = $original; + } + } + + /** + * 注册自动加载机制 + * @access public + * @param callable $autoload 自动加载处理方法 + * @return void + */ + public static function register($autoload = null) + { + // 注册系统自动加载 + spl_autoload_register($autoload ?: 'think\\Loader::autoload', true, true); + + // Composer 自动加载支持 + if (is_dir(VENDOR_PATH . 'composer')) { + if (PHP_VERSION_ID >= 50600 && is_file(VENDOR_PATH . 'composer' . DS . 'autoload_static.php')) { + require VENDOR_PATH . 'composer' . DS . 'autoload_static.php'; + + $declaredClass = get_declared_classes(); + $composerClass = array_pop($declaredClass); + + foreach (['prefixLengthsPsr4', 'prefixDirsPsr4', 'fallbackDirsPsr4', 'prefixesPsr0', 'fallbackDirsPsr0', 'classMap', 'files'] as $attr) { + if (property_exists($composerClass, $attr)) { + self::${$attr} = $composerClass::${$attr}; + } + } + } else { + self::registerComposerLoader(); + } + } + + // 注册命名空间定义 + self::addNamespace([ + 'think' => LIB_PATH . 'think' . DS, + 'behavior' => LIB_PATH . 'behavior' . DS, + 'traits' => LIB_PATH . 'traits' . DS, + ]); + + // 加载类库映射文件 + if (is_file(RUNTIME_PATH . 'classmap' . EXT)) { + self::addClassMap(__include_file(RUNTIME_PATH . 'classmap' . EXT)); + } + + self::loadComposerAutoloadFiles(); + + // 自动加载 extend 目录 + self::$fallbackDirsPsr4[] = rtrim(EXTEND_PATH, DS); + } + + /** + * 注册 composer 自动加载 + * @access private + * @return void + */ + private static function registerComposerLoader() + { + if (is_file(VENDOR_PATH . 'composer/autoload_namespaces.php')) { + $map = require VENDOR_PATH . 'composer/autoload_namespaces.php'; + foreach ($map as $namespace => $path) { + self::addPsr0($namespace, $path); + } + } + + if (is_file(VENDOR_PATH . 'composer/autoload_psr4.php')) { + $map = require VENDOR_PATH . 'composer/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + self::addPsr4($namespace, $path); + } + } + + if (is_file(VENDOR_PATH . 'composer/autoload_classmap.php')) { + $classMap = require VENDOR_PATH . 'composer/autoload_classmap.php'; + if ($classMap) { + self::addClassMap($classMap); + } + } + + if (is_file(VENDOR_PATH . 'composer/autoload_files.php')) { + self::$files = require VENDOR_PATH . 'composer/autoload_files.php'; + } + } + + // 加载composer autofile文件 + public static function loadComposerAutoloadFiles() + { + foreach (self::$files as $fileIdentifier => $file) { + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + __require_file($file); + + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + } + } + } + + /** + * 导入所需的类库 同 Java 的 Import 本函数有缓存功能 + * @access public + * @param string $class 类库命名空间字符串 + * @param string $baseUrl 起始路径 + * @param string $ext 导入的文件扩展名 + * @return bool + */ + public static function import($class, $baseUrl = '', $ext = EXT) + { + static $_file = []; + $key = $class . $baseUrl; + $class = str_replace(['.', '#'], [DS, '.'], $class); + + if (isset($_file[$key])) { + return true; + } + + if (empty($baseUrl)) { + list($name, $class) = explode(DS, $class, 2); + + if (isset(self::$prefixDirsPsr4[$name . '\\'])) { + // 注册的命名空间 + $baseUrl = self::$prefixDirsPsr4[$name . '\\']; + } elseif ('@' == $name) { + // 加载当前模块应用类库 + $baseUrl = App::$modulePath; + } elseif (is_dir(EXTEND_PATH . $name)) { + $baseUrl = EXTEND_PATH . $name . DS; + } else { + // 加载其它模块的类库 + $baseUrl = APP_PATH . $name . DS; + } + } elseif (substr($baseUrl, -1) != DS) { + $baseUrl .= DS; + } + + // 如果类存在则导入类库文件 + if (is_array($baseUrl)) { + foreach ($baseUrl as $path) { + if (is_file($filename = $path . DS . $class . $ext)) { + break; + } + } + } else { + $filename = $baseUrl . $class . $ext; + } + + if (!empty($filename) && + is_file($filename) && + (!IS_WIN || pathinfo($filename, PATHINFO_FILENAME) == pathinfo(realpath($filename), PATHINFO_FILENAME)) + ) { + __include_file($filename); + $_file[$key] = true; + + return true; + } + + return false; + } + + /** + * 实例化(分层)模型 + * @access public + * @param string $name Model名称 + * @param string $layer 业务层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @param string $common 公共模块名 + * @return object + * @throws ClassNotFoundException + */ + public static function model($name = '', $layer = 'model', $appendSuffix = false, $common = 'common') + { + $uid = $name . $layer; + + if (isset(self::$instance[$uid])) { + return self::$instance[$uid]; + } + + list($module, $class) = self::getModuleAndClass($name, $layer, $appendSuffix); + + if (class_exists($class)) { + $model = new $class(); + } else { + $class = str_replace('\\' . $module . '\\', '\\' . $common . '\\', $class); + + if (class_exists($class)) { + $model = new $class(); + } else { + throw new ClassNotFoundException('class not exists:' . $class, $class); + } + } + + return self::$instance[$uid] = $model; + } + + /** + * 实例化(分层)控制器 格式:[模块名/]控制器名 + * @access public + * @param string $name 资源地址 + * @param string $layer 控制层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @param string $empty 空控制器名称 + * @return object + * @throws ClassNotFoundException + */ + public static function controller($name, $layer = 'controller', $appendSuffix = false, $empty = '') + { + list($module, $class) = self::getModuleAndClass($name, $layer, $appendSuffix); + + if (class_exists($class)) { + return App::invokeClass($class); + } + + if ($empty) { + $emptyClass = self::parseClass($module, $layer, $empty, $appendSuffix); + + if (class_exists($emptyClass)) { + return new $emptyClass(Request::instance()); + } + } + + throw new ClassNotFoundException('class not exists:' . $class, $class); + } + + /** + * 实例化验证类 格式:[模块名/]验证器名 + * @access public + * @param string $name 资源地址 + * @param string $layer 验证层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @param string $common 公共模块名 + * @return object|false + * @throws ClassNotFoundException + */ + public static function validate($name = '', $layer = 'validate', $appendSuffix = false, $common = 'common') + { + $name = $name ?: Config::get('default_validate'); + + if (empty($name)) { + return new Validate; + } + + $uid = $name . $layer; + if (isset(self::$instance[$uid])) { + return self::$instance[$uid]; + } + + list($module, $class) = self::getModuleAndClass($name, $layer, $appendSuffix); + + if (class_exists($class)) { + $validate = new $class; + } else { + $class = str_replace('\\' . $module . '\\', '\\' . $common . '\\', $class); + + if (class_exists($class)) { + $validate = new $class; + } else { + throw new ClassNotFoundException('class not exists:' . $class, $class); + } + } + + return self::$instance[$uid] = $validate; + } + + /** + * 解析模块和类名 + * @access protected + * @param string $name 资源地址 + * @param string $layer 验证层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return array + */ + protected static function getModuleAndClass($name, $layer, $appendSuffix) + { + if (false !== strpos($name, '\\')) { + $module = Request::instance()->module(); + $class = $name; + } else { + if (strpos($name, '/')) { + list($module, $name) = explode('/', $name, 2); + } else { + $module = Request::instance()->module(); + } + + $class = self::parseClass($module, $layer, $name, $appendSuffix); + } + + return [$module, $class]; + } + + /** + * 数据库初始化 并取得数据库类实例 + * @access public + * @param mixed $config 数据库配置 + * @param bool|string $name 连接标识 true 强制重新连接 + * @return \think\db\Connection + */ + public static function db($config = [], $name = false) + { + return Db::connect($config, $name); + } + + /** + * 远程调用模块的操作方法 参数格式 [模块/控制器/]操作 + * @access public + * @param string $url 调用地址 + * @param string|array $vars 调用参数 支持字符串和数组 + * @param string $layer 要调用的控制层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return mixed + */ + public static function action($url, $vars = [], $layer = 'controller', $appendSuffix = false) + { + $info = pathinfo($url); + $action = $info['basename']; + $module = '.' != $info['dirname'] ? $info['dirname'] : Request::instance()->controller(); + $class = self::controller($module, $layer, $appendSuffix); + + if ($class) { + if (is_scalar($vars)) { + if (strpos($vars, '=')) { + parse_str($vars, $vars); + } else { + $vars = [$vars]; + } + } + + return App::invokeMethod([$class, $action . Config::get('action_suffix')], $vars); + } + + return false; + } + + /** + * 字符串命名风格转换 + * type 0 将 Java 风格转换为 C 的风格 1 将 C 风格转换为 Java 的风格 + * @access public + * @param string $name 字符串 + * @param integer $type 转换类型 + * @param bool $ucfirst 首字母是否大写(驼峰规则) + * @return string + */ + public static function parseName($name, $type = 0, $ucfirst = true) + { + 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), "_")); + } + + /** + * 解析应用类的类名 + * @access public + * @param string $module 模块名 + * @param string $layer 层名 controller model ... + * @param string $name 类名 + * @param bool $appendSuffix 是否添加类名后缀 + * @return string + */ + public static function parseClass($module, $layer, $name, $appendSuffix = false) + { + + $array = explode('\\', str_replace(['/', '.'], '\\', $name)); + $class = self::parseName(array_pop($array), 1); + $class = $class . (App::$suffix || $appendSuffix ? ucfirst($layer) : ''); + $path = $array ? implode('\\', $array) . '\\' : ''; + + return App::$namespace . '\\' . + ($module ? $module . '\\' : '') . + $layer . '\\' . $path . $class; + } + + /** + * 初始化类的实例 + * @access public + * @return void + */ + public static function clearInstance() + { + self::$instance = []; + } +} + +// 作用范围隔离 + +/** + * include + * @param string $file 文件路径 + * @return mixed + */ +function __include_file($file) +{ + return include $file; +} + +/** + * require + * @param string $file 文件路径 + * @return mixed + */ +function __require_file($file) +{ + return require $file; +} diff --git a/thinkphp/library/think/Log.php b/thinkphp/library/think/Log.php new file mode 100644 index 0000000..a84eda8 --- /dev/null +++ b/thinkphp/library/think/Log.php @@ -0,0 +1,237 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\ClassNotFoundException; + +/** + * Class Log + * @package think + * + * @method static void log($msg) 记录一般日志 + * @method static void error($msg) 记录错误日志 + * @method static void info($msg) 记录一般信息日志 + * @method static void sql($msg) 记录 SQL 查询日志 + * @method static void notice($msg) 记录提示日志 + * @method static void alert($msg) 记录报警日志 + */ +class Log +{ + const LOG = 'log'; + const ERROR = 'error'; + const INFO = 'info'; + const SQL = 'sql'; + const NOTICE = 'notice'; + const ALERT = 'alert'; + const DEBUG = 'debug'; + + /** + * @var array 日志信息 + */ + protected static $log = []; + + /** + * @var array 配置参数 + */ + protected static $config = []; + + /** + * @var array 日志类型 + */ + protected static $type = ['log', 'error', 'info', 'sql', 'notice', 'alert', 'debug']; + + /** + * @var log\driver\File|log\driver\Test|log\driver\Socket 日志写入驱动 + */ + protected static $driver; + + /** + * @var string 当前日志授权 key + */ + protected static $key; + + /** + * 日志初始化 + * @access public + * @param array $config 配置参数 + * @return void + */ + public static function init($config = []) + { + $type = isset($config['type']) ? $config['type'] : 'File'; + $class = false !== strpos($type, '\\') ? $type : '\\think\\log\\driver\\' . ucwords($type); + + self::$config = $config; + unset($config['type']); + + if (class_exists($class)) { + self::$driver = new $class($config); + } else { + throw new ClassNotFoundException('class not exists:' . $class, $class); + } + + // 记录初始化信息 + App::$debug && Log::record('[ LOG ] INIT ' . $type, 'info'); + } + + /** + * 获取日志信息 + * @access public + * @param string $type 信息类型 + * @return array|string + */ + public static function getLog($type = '') + { + return $type ? self::$log[$type] : self::$log; + } + + /** + * 记录调试信息 + * @access public + * @param mixed $msg 调试信息 + * @param string $type 信息类型 + * @return void + */ + public static function record($msg, $type = 'log') + { + self::$log[$type][] = $msg; + + // 命令行下面日志写入改进 + IS_CLI && self::save(); + } + + /** + * 清空日志信息 + * @access public + * @return void + */ + public static function clear() + { + self::$log = []; + } + + /** + * 设置当前日志记录的授权 key + * @access public + * @param string $key 授权 key + * @return void + */ + public static function key($key) + { + self::$key = $key; + } + + /** + * 检查日志写入权限 + * @access public + * @param array $config 当前日志配置参数 + * @return bool + */ + public static function check($config) + { + return !self::$key || empty($config['allow_key']) || in_array(self::$key, $config['allow_key']); + } + + /** + * 保存调试信息 + * @access public + * @return bool + */ + public static function save() + { + // 没有需要保存的记录则直接返回 + if (empty(self::$log)) { + return true; + } + + is_null(self::$driver) && self::init(Config::get('log')); + + // 检测日志写入权限 + if (!self::check(self::$config)) { + return false; + } + + if (empty(self::$config['level'])) { + // 获取全部日志 + $log = self::$log; + if (!App::$debug && isset($log['debug'])) { + unset($log['debug']); + } + } else { + // 记录允许级别 + $log = []; + foreach (self::$config['level'] as $level) { + if (isset(self::$log[$level])) { + $log[$level] = self::$log[$level]; + } + } + } + + if ($result = self::$driver->save($log, true)) { + self::$log = []; + } + + Hook::listen('log_write_done', $log); + + return $result; + } + + /** + * 实时写入日志信息 并支持行为 + * @access public + * @param mixed $msg 调试信息 + * @param string $type 信息类型 + * @param bool $force 是否强制写入 + * @return bool + */ + public static function write($msg, $type = 'log', $force = false) + { + $log = self::$log; + + // 如果不是强制写入,而且信息类型不在可记录的类别中则直接返回 false 不做记录 + if (true !== $force && !empty(self::$config['level']) && !in_array($type, self::$config['level'])) { + return false; + } + + // 封装日志信息 + $log[$type][] = $msg; + + // 监听 log_write + Hook::listen('log_write', $log); + + is_null(self::$driver) && self::init(Config::get('log')); + + // 写入日志 + if ($result = self::$driver->save($log, false)) { + self::$log = []; + } + + return $result; + } + + /** + * 静态方法调用 + * @access public + * @param string $method 调用方法 + * @param mixed $args 参数 + * @return void + */ + public static function __callStatic($method, $args) + { + if (in_array($method, self::$type)) { + array_push($args, $method); + + call_user_func_array('\\think\\Log::record', $args); + } + } + +} diff --git a/thinkphp/library/think/Model.php b/thinkphp/library/think/Model.php new file mode 100644 index 0000000..2dc27b4 --- /dev/null +++ b/thinkphp/library/think/Model.php @@ -0,0 +1,2350 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use BadMethodCallException; +use InvalidArgumentException; +use think\db\Query; +use think\exception\ValidateException; +use think\model\Collection as ModelCollection; +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\MorphMany; +use think\model\relation\MorphOne; +use think\model\relation\MorphTo; + +/** + * Class Model + * @package think + * @mixin Query + */ +abstract class Model implements \JsonSerializable, \ArrayAccess +{ + // 数据库查询对象池 + protected static $links = []; + // 数据库配置 + protected $connection = []; + // 父关联模型对象 + protected $parent; + // 数据库查询对象 + protected $query; + // 当前模型名称 + protected $name; + // 数据表名称 + protected $table; + // 当前类名称 + protected $class; + // 回调事件 + private static $event = []; + // 错误信息 + protected $error; + // 字段验证规则 + protected $validate; + // 数据表主键 复合主键使用数组定义 不设置则自动获取 + protected $pk; + // 数据表字段信息 留空则自动获取 + protected $field = []; + // 数据排除字段 + protected $except = []; + // 数据废弃字段 + protected $disuse = []; + // 只读字段 + protected $readonly = []; + // 显示属性 + protected $visible = []; + // 隐藏属性 + protected $hidden = []; + // 追加属性 + protected $append = []; + // 数据信息 + protected $data = []; + // 原始数据 + protected $origin = []; + // 关联模型 + protected $relation = []; + + // 保存自动完成列表 + protected $auto = []; + // 新增自动完成列表 + protected $insert = []; + // 更新自动完成列表 + protected $update = []; + // 是否需要自动写入时间戳 如果设置为字符串 则表示时间字段的类型 + protected $autoWriteTimestamp; + // 创建时间字段 + protected $createTime = 'create_time'; + // 更新时间字段 + protected $updateTime = 'update_time'; + // 时间字段取出后的默认时间格式 + protected $dateFormat; + // 字段类型或者格式转换 + protected $type = []; + // 是否为更新数据 + protected $isUpdate = false; + // 是否使用Replace + protected $replace = false; + // 是否强制更新所有数据 + protected $force = false; + // 更新条件 + protected $updateWhere; + // 验证失败是否抛出异常 + protected $failException = false; + // 全局查询范围 + protected $useGlobalScope = true; + // 是否采用批量验证 + protected $batchValidate = false; + // 查询数据集对象 + protected $resultSetType; + // 关联自动写入 + protected $relationWrite; + + /** + * 初始化过的模型. + * + * @var array + */ + protected static $initialized = []; + + /** + * 是否从主库读取(主从分布式有效) + * @var array + */ + protected static $readMaster; + + /** + * 构造方法 + * @access public + * @param array|object $data 数据 + */ + public function __construct($data = []) + { + if (is_object($data)) { + $this->data = get_object_vars($data); + } else { + $this->data = $data; + } + + if ($this->disuse) { + // 废弃字段 + foreach ((array) $this->disuse as $key) { + if (array_key_exists($key, $this->data)) { + unset($this->data[$key]); + } + } + } + + // 记录原始数据 + $this->origin = $this->data; + + // 当前类名 + $this->class = get_called_class(); + + if (empty($this->name)) { + // 当前模型名 + $name = str_replace('\\', '/', $this->class); + $this->name = basename($name); + if (Config::get('class_suffix')) { + $suffix = basename(dirname($name)); + $this->name = substr($this->name, 0, -strlen($suffix)); + } + } + + if (is_null($this->autoWriteTimestamp)) { + // 自动写入时间戳 + $this->autoWriteTimestamp = $this->getQuery()->getConfig('auto_timestamp'); + } + + if (is_null($this->dateFormat)) { + // 设置时间戳格式 + $this->dateFormat = $this->getQuery()->getConfig('datetime_format'); + } + + if (is_null($this->resultSetType)) { + $this->resultSetType = $this->getQuery()->getConfig('resultset_type'); + } + // 执行初始化操作 + $this->initialize(); + } + + /** + * 是否从主库读取数据(主从分布有效) + * @access public + * @param bool $all 是否所有模型生效 + * @return $this + */ + public function readMaster($all = false) + { + $model = $all ? '*' : $this->class; + + static::$readMaster[$model] = true; + return $this; + } + + /** + * 创建模型的查询对象 + * @access protected + * @return Query + */ + protected function buildQuery() + { + // 合并数据库配置 + if (!empty($this->connection)) { + if (is_array($this->connection)) { + $connection = array_merge(Config::get('database'), $this->connection); + } else { + $connection = $this->connection; + } + } else { + $connection = []; + } + + $con = Db::connect($connection); + // 设置当前模型 确保查询返回模型对象 + $queryClass = $this->query ?: $con->getConfig('query'); + $query = new $queryClass($con, $this); + + if (isset(static::$readMaster['*']) || isset(static::$readMaster[$this->class])) { + $query->master(true); + } + + // 设置当前数据表和模型名 + if (!empty($this->table)) { + $query->setTable($this->table); + } else { + $query->name($this->name); + } + + if (!empty($this->pk)) { + $query->pk($this->pk); + } + + return $query; + } + + /** + * 创建新的模型实例 + * @access public + * @param array|object $data 数据 + * @param bool $isUpdate 是否为更新 + * @param mixed $where 更新条件 + * @return Model + */ + public function newInstance($data = [], $isUpdate = false, $where = null) + { + return (new static($data))->isUpdate($isUpdate, $where); + } + + /** + * 获取当前模型的查询对象 + * @access public + * @param bool $buildNewQuery 创建新的查询对象 + * @return Query + */ + public function getQuery($buildNewQuery = false) + { + if ($buildNewQuery) { + return $this->buildQuery(); + } elseif (!isset(self::$links[$this->class])) { + // 创建模型查询对象 + self::$links[$this->class] = $this->buildQuery(); + } + + return self::$links[$this->class]; + } + + /** + * 获取当前模型的数据库查询对象 + * @access public + * @param bool $useBaseQuery 是否调用全局查询范围 + * @param bool $buildNewQuery 创建新的查询对象 + * @return Query + */ + public function db($useBaseQuery = true, $buildNewQuery = true) + { + $query = $this->getQuery($buildNewQuery); + + // 全局作用域 + if ($useBaseQuery && method_exists($this, 'base')) { + call_user_func_array([$this, 'base'], [ & $query]); + } + + // 返回当前模型的数据库查询对象 + return $query; + } + + /** + * 初始化模型 + * @access protected + * @return void + */ + protected function initialize() + { + $class = get_class($this); + if (!isset(static::$initialized[$class])) { + static::$initialized[$class] = true; + static::init(); + } + } + + /** + * 初始化处理 + * @access protected + * @return void + */ + protected static function init() + { + } + + /** + * 设置父关联对象 + * @access public + * @param Model $model 模型对象 + * @return $this + */ + public function setParent($model) + { + $this->parent = $model; + return $this; + } + + /** + * 获取父关联对象 + * @access public + * @return Model + */ + public function getParent() + { + return $this->parent; + } + + /** + * 设置数据对象值 + * @access public + * @param mixed $data 数据或者属性名 + * @param mixed $value 值 + * @return $this + */ + public function data($data, $value = null) + { + if (is_string($data)) { + $this->data[$data] = $value; + } else { + // 清空数据 + $this->data = []; + if (is_object($data)) { + $data = get_object_vars($data); + } + if (true === $value) { + // 数据对象赋值 + foreach ($data as $key => $value) { + $this->setAttr($key, $value, $data); + } + } else { + $this->data = $data; + } + } + return $this; + } + + /** + * 获取对象原始数据 如果不存在指定字段返回false + * @access public + * @param string $name 字段名 留空获取全部 + * @return mixed + * @throws InvalidArgumentException + */ + public function getData($name = null) + { + if (is_null($name)) { + return $this->data; + } elseif (array_key_exists($name, $this->data)) { + return $this->data[$name]; + } elseif (array_key_exists($name, $this->relation)) { + return $this->relation[$name]; + } else { + throw new InvalidArgumentException('property not exists:' . $this->class . '->' . $name); + } + } + + /** + * 是否需要自动写入时间字段 + * @access public + * @param bool $auto + * @return $this + */ + public function isAutoWriteTimestamp($auto) + { + $this->autoWriteTimestamp = $auto; + return $this; + } + + /** + * 更新是否强制写入数据 而不做比较 + * @access public + * @param bool $force + * @return $this + */ + public function force($force = true) + { + $this->force = $force; + return $this; + } + + /** + * 修改器 设置数据对象值 + * @access public + * @param string $name 属性名 + * @param mixed $value 属性值 + * @param array $data 数据 + * @return $this + */ + public function setAttr($name, $value, $data = []) + { + if (is_null($value) && $this->autoWriteTimestamp && in_array($name, [$this->createTime, $this->updateTime])) { + // 自动写入的时间戳字段 + $value = $this->autoWriteTimestamp($name); + } else { + // 检测修改器 + $method = 'set' . Loader::parseName($name, 1) . 'Attr'; + if (method_exists($this, $method)) { + $value = $this->$method($value, array_merge($this->data, $data), $this->relation); + } elseif (isset($this->type[$name])) { + // 类型转换 + $value = $this->writeTransform($value, $this->type[$name]); + } + } + + // 设置数据对象属性 + $this->data[$name] = $value; + return $this; + } + + /** + * 获取当前模型的关联模型数据 + * @access public + * @param string $name 关联方法名 + * @return mixed + */ + public function getRelation($name = null) + { + if (is_null($name)) { + return $this->relation; + } elseif (array_key_exists($name, $this->relation)) { + return $this->relation[$name]; + } else { + return; + } + } + + /** + * 设置关联数据对象值 + * @access public + * @param string $name 属性名 + * @param mixed $value 属性值 + * @return $this + */ + public function setRelation($name, $value) + { + $this->relation[$name] = $value; + return $this; + } + + /** + * 自动写入时间戳 + * @access public + * @param string $name 时间戳字段 + * @return mixed + */ + protected function autoWriteTimestamp($name) + { + if (isset($this->type[$name])) { + $type = $this->type[$name]; + if (strpos($type, ':')) { + list($type, $param) = explode(':', $type, 2); + } + switch ($type) { + case 'datetime': + case 'date': + $format = !empty($param) ? $param : $this->dateFormat; + $value = $this->formatDateTime(time(), $format); + break; + case 'timestamp': + case 'integer': + default: + $value = time(); + break; + } + } elseif (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), [ + 'datetime', + 'date', + 'timestamp', + ]) + ) { + $value = $this->formatDateTime(time(), $this->dateFormat); + } else { + $value = $this->formatDateTime(time(), $this->dateFormat, true); + } + return $value; + } + + /** + * 时间日期字段格式化处理 + * @access public + * @param mixed $time 时间日期表达式 + * @param mixed $format 日期格式 + * @param bool $timestamp 是否进行时间戳转换 + * @return mixed + */ + protected function formatDateTime($time, $format, $timestamp = false) + { + if (false !== strpos($format, '\\')) { + $time = new $format($time); + } elseif (!$timestamp && false !== $format) { + $time = date($format, $time); + } + return $time; + } + + /** + * 数据写入 类型转换 + * @access public + * @param mixed $value 值 + * @param string|array $type 要转换的类型 + * @return mixed + */ + protected function writeTransform($value, $type) + { + if (is_null($value)) { + return; + } + + if (is_array($type)) { + list($type, $param) = $type; + } elseif (strpos($type, ':')) { + list($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, $param, '.', ''); + } + break; + case 'boolean': + $value = (bool) $value; + break; + case 'timestamp': + if (!is_numeric($value)) { + $value = strtotime($value); + } + break; + case 'datetime': + $format = !empty($param) ? $param : $this->dateFormat; + $value = is_numeric($value) ? $value : strtotime($value); + $value = $this->formatDateTime($value, $format); + 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; + + } + return $value; + } + + /** + * 获取器 获取数据对象的值 + * @access public + * @param string $name 名称 + * @return mixed + * @throws InvalidArgumentException + */ + public function getAttr($name) + { + try { + $notFound = false; + $value = $this->getData($name); + } catch (InvalidArgumentException $e) { + $notFound = true; + $value = null; + } + + // 检测属性获取器 + $method = 'get' . Loader::parseName($name, 1) . 'Attr'; + if (method_exists($this, $method)) { + $value = $this->$method($value, $this->data, $this->relation); + } elseif (isset($this->type[$name])) { + // 类型转换 + $value = $this->readTransform($value, $this->type[$name]); + } elseif (in_array($name, [$this->createTime, $this->updateTime])) { + if (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), [ + 'datetime', + 'date', + 'timestamp', + ]) + ) { + $value = $this->formatDateTime(strtotime($value), $this->dateFormat); + } else { + $value = $this->formatDateTime($value, $this->dateFormat); + } + } elseif ($notFound) { + $relation = Loader::parseName($name, 1, false); + if (method_exists($this, $relation)) { + $modelRelation = $this->$relation(); + // 不存在该字段 获取关联数据 + $value = $this->getRelationData($modelRelation); + // 保存关联对象值 + $this->relation[$name] = $value; + } else { + throw new InvalidArgumentException('property not exists:' . $this->class . '->' . $name); + } + } + return $value; + } + + /** + * 获取关联模型数据 + * @access public + * @param Relation $modelRelation 模型关联对象 + * @return mixed + * @throws BadMethodCallException + */ + protected function getRelationData(Relation $modelRelation) + { + if ($this->parent && !$modelRelation->isSelfRelation() && get_class($modelRelation->getModel()) == get_class($this->parent)) { + $value = $this->parent; + } else { + // 首先获取关联数据 + if (method_exists($modelRelation, 'getRelation')) { + $value = $modelRelation->getRelation(); + } else { + throw new BadMethodCallException('method not exists:' . get_class($modelRelation) . '-> getRelation'); + } + } + return $value; + } + + /** + * 数据读取 类型转换 + * @access public + * @param mixed $value 值 + * @param string|array $type 要转换的类型 + * @return mixed + */ + protected function readTransform($value, $type) + { + if (is_null($value)) { + return; + } + + if (is_array($type)) { + list($type, $param) = $type; + } elseif (strpos($type, ':')) { + list($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, $param, '.', ''); + } + break; + case 'boolean': + $value = (bool) $value; + break; + case 'timestamp': + if (!is_null($value)) { + $format = !empty($param) ? $param : $this->dateFormat; + $value = $this->formatDateTime($value, $format); + } + break; + case 'datetime': + if (!is_null($value)) { + $format = !empty($param) ? $param : $this->dateFormat; + $value = $this->formatDateTime(strtotime($value), $format); + } + 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 array $append 属性列表 + * @param bool $override 是否覆盖 + * @return $this + */ + public function append($append = [], $override = false) + { + $this->append = $override ? $append : array_merge($this->append, $append); + return $this; + } + + /** + * 设置附加关联对象的属性 + * @access public + * @param string $relation 关联方法 + * @param string|array $append 追加属性名 + * @return $this + * @throws Exception + */ + public function appendRelationAttr($relation, $append) + { + if (is_string($append)) { + $append = explode(',', $append); + } + + $relation = Loader::parseName($relation, 1, false); + + // 获取关联数据 + 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); + } else { + $this->data[$key] = $model->getAttr($attr); + } + } + } + return $this; + } + + /** + * 设置需要隐藏的输出属性 + * @access public + * @param array $hidden 属性列表 + * @param bool $override 是否覆盖 + * @return $this + */ + public function hidden($hidden = [], $override = false) + { + $this->hidden = $override ? $hidden : array_merge($this->hidden, $hidden); + return $this; + } + + /** + * 设置需要输出的属性 + * @access public + * @param array $visible + * @param bool $override 是否覆盖 + * @return $this + */ + public function visible($visible = [], $override = false) + { + $this->visible = $override ? $visible : array_merge($this->visible, $visible); + return $this; + } + + /** + * 解析隐藏及显示属性 + * @access protected + * @param array $attrs 属性 + * @param array $result 结果集 + * @param bool $visible + * @return array + */ + protected function parseAttr($attrs, &$result, $visible = true) + { + $array = []; + foreach ($attrs as $key => $val) { + if (is_array($val)) { + if ($visible) { + $array[] = $key; + } + $result[$key] = $val; + } elseif (strpos($val, '.')) { + list($key, $name) = explode('.', $val); + if ($visible) { + $array[] = $key; + } + $result[$key][] = $name; + } else { + $array[] = $val; + } + } + return $array; + } + + /** + * 转换子模型对象 + * @access protected + * @param Model|ModelCollection $model + * @param $visible + * @param $hidden + * @param $key + * @return array + */ + protected function subToArray($model, $visible, $hidden, $key) + { + // 关联模型对象 + if (isset($visible[$key])) { + $model->visible($visible[$key]); + } elseif (isset($hidden[$key])) { + $model->hidden($hidden[$key]); + } + return $model->toArray(); + } + + /** + * 转换当前模型对象为数组 + * @access public + * @return array + */ + public function toArray() + { + $item = []; + $visible = []; + $hidden = []; + + $data = array_merge($this->data, $this->relation); + + // 过滤属性 + if (!empty($this->visible)) { + $array = $this->parseAttr($this->visible, $visible); + $data = array_intersect_key($data, array_flip($array)); + } elseif (!empty($this->hidden)) { + $array = $this->parseAttr($this->hidden, $hidden, false); + $data = array_diff_key($data, array_flip($array)); + } + + foreach ($data as $key => $val) { + if ($val instanceof Model || $val instanceof ModelCollection) { + // 关联模型对象 + $item[$key] = $this->subToArray($val, $visible, $hidden, $key); + } elseif (is_array($val) && reset($val) instanceof Model) { + // 关联模型数据集 + $arr = []; + foreach ($val as $k => $value) { + $arr[$k] = $this->subToArray($value, $visible, $hidden, $key); + } + $item[$key] = $arr; + } else { + // 模型属性 + $item[$key] = $this->getAttr($key); + } + } + // 追加属性(必须定义获取器) + if (!empty($this->append)) { + foreach ($this->append as $key => $name) { + if (is_array($name)) { + // 追加关联对象属性 + $relation = $this->getAttr($key); + $item[$key] = $relation->append($name)->toArray(); + } elseif (strpos($name, '.')) { + list($key, $attr) = explode('.', $name); + // 追加关联对象属性 + $relation = $this->getAttr($key); + $item[$key] = $relation->append([$attr])->toArray(); + } else { + $relation = Loader::parseName($name, 1, false); + if (method_exists($this, $relation)) { + $modelRelation = $this->$relation(); + $value = $this->getRelationData($modelRelation); + + if (method_exists($modelRelation, 'getBindAttr')) { + $bindAttr = $modelRelation->getBindAttr(); + if ($bindAttr) { + foreach ($bindAttr as $key => $attr) { + $key = is_numeric($key) ? $attr : $key; + if (isset($this->data[$key])) { + throw new Exception('bind attr has exists:' . $key); + } else { + $item[$key] = $value ? $value->getAttr($attr) : null; + } + } + continue; + } + } + $item[$name] = $value; + } else { + $item[$name] = $this->getAttr($name); + } + } + } + } + return !empty($item) ? $item : []; + } + + /** + * 转换当前模型对象为JSON字符串 + * @access public + * @param integer $options json参数 + * @return string + */ + public function toJson($options = JSON_UNESCAPED_UNICODE) + { + return json_encode($this->toArray(), $options); + } + + /** + * 移除当前模型的关联属性 + * @access public + * @return $this + */ + public function removeRelation() + { + $this->relation = []; + return $this; + } + + /** + * 转换当前模型数据集为数据集对象 + * @access public + * @param array|\think\Collection $collection 数据集 + * @return \think\Collection + */ + public function toCollection($collection) + { + if ($this->resultSetType) { + if ('collection' == $this->resultSetType) { + $collection = new ModelCollection($collection); + } elseif (false !== strpos($this->resultSetType, '\\')) { + $class = $this->resultSetType; + $collection = new $class($collection); + } + } + return $collection; + } + + /** + * 关联数据一起更新 + * @access public + * @param mixed $relation 关联 + * @return $this + */ + public function together($relation) + { + if (is_string($relation)) { + $relation = explode(',', $relation); + } + $this->relationWrite = $relation; + return $this; + } + + /** + * 获取模型对象的主键 + * @access public + * @param string $name 模型名 + * @return mixed + */ + public function getPk($name = '') + { + if (!empty($name)) { + $table = $this->getQuery()->getTable($name); + return $this->getQuery()->getPk($table); + } elseif (empty($this->pk)) { + $this->pk = $this->getQuery()->getPk(); + } + return $this->pk; + } + + /** + * 判断一个字段名是否为主键字段 + * @access public + * @param string $key 名称 + * @return bool + */ + protected function isPk($key) + { + $pk = $this->getPk(); + if (is_string($pk) && $pk == $key) { + return true; + } elseif (is_array($pk) && in_array($key, $pk)) { + return true; + } + return false; + } + + /** + * 新增数据是否使用Replace + * @access public + * @param bool $replace + * @return $this + */ + public function replace($replace = true) + { + $this->replace = $replace; + return $this; + } + + /** + * 保存当前数据对象 + * @access public + * @param array $data 数据 + * @param array $where 更新条件 + * @param string $sequence 自增序列名 + * @return integer|false + */ + public function save($data = [], $where = [], $sequence = null) + { + if (is_string($data)) { + $sequence = $data; + $data = []; + } + + // 数据自动验证 + if (!empty($data)) { + if (!$this->validateData($data)) { + return false; + } + + // 数据对象赋值 + foreach ($data as $key => $value) { + $this->setAttr($key, $value, $data); + } + } + + if (!empty($where)) { + $this->isUpdate = true; + $this->updateWhere = $where; + } + + // 自动关联写入 + if (!empty($this->relationWrite)) { + $relation = []; + foreach ($this->relationWrite as $key => $name) { + if (is_array($name)) { + if (key($name) === 0) { + $relation[$key] = []; + foreach ($name as $val) { + if (isset($this->data[$val])) { + $relation[$key][$val] = $this->data[$val]; + unset($this->data[$val]); + } + } + } else { + $relation[$key] = $name; + } + } elseif (isset($this->relation[$name])) { + $relation[$name] = $this->relation[$name]; + } elseif (isset($this->data[$name])) { + $relation[$name] = $this->data[$name]; + unset($this->data[$name]); + } + } + } + + // 数据自动完成 + $this->autoCompleteData($this->auto); + + // 事件回调 + if (false === $this->trigger('before_write', $this)) { + return false; + } + $pk = $this->getPk(); + if ($this->isUpdate) { + // 自动更新 + $this->autoCompleteData($this->update); + + // 事件回调 + if (false === $this->trigger('before_update', $this)) { + return false; + } + + // 获取有更新的数据 + $data = $this->getChangedData(); + + if (empty($data) || (count($data) == 1 && is_string($pk) && isset($data[$pk]))) { + // 关联更新 + if (isset($relation)) { + $this->autoRelationUpdate($relation); + } + return 0; + } elseif ($this->autoWriteTimestamp && $this->updateTime && !isset($data[$this->updateTime])) { + // 自动写入更新时间 + $data[$this->updateTime] = $this->autoWriteTimestamp($this->updateTime); + $this->data[$this->updateTime] = $data[$this->updateTime]; + } + + if (empty($where) && !empty($this->updateWhere)) { + $where = $this->updateWhere; + } + + // 保留主键数据 + foreach ($this->data as $key => $val) { + if ($this->isPk($key)) { + $data[$key] = $val; + } + } + + $array = []; + + foreach ((array) $pk as $key) { + if (isset($data[$key])) { + $array[$key] = $data[$key]; + unset($data[$key]); + } + } + + if (!empty($array)) { + $where = $array; + } + + // 检测字段 + $allowFields = $this->checkAllowField(array_merge($this->auto, $this->update)); + + // 模型更新 + if (!empty($allowFields)) { + $result = $this->getQuery()->where($where)->strict(false)->field($allowFields)->update($data); + } else { + $result = $this->getQuery()->where($where)->update($data); + } + + // 关联更新 + if (isset($relation)) { + $this->autoRelationUpdate($relation); + } + + // 更新回调 + $this->trigger('after_update', $this); + + } else { + // 自动写入 + $this->autoCompleteData($this->insert); + + // 自动写入创建时间和更新时间 + 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('before_insert', $this)) { + return false; + } + + // 检测字段 + $allowFields = $this->checkAllowField(array_merge($this->auto, $this->insert)); + if (!empty($allowFields)) { + $result = $this->getQuery()->strict(false)->field($allowFields)->insert($this->data, $this->replace, false, $sequence); + } else { + $result = $this->getQuery()->insert($this->data, $this->replace, false, $sequence); + } + + // 获取自动增长主键 + if ($result && $insertId = $this->getQuery()->getLastInsID($sequence)) { + foreach ((array) $pk as $key) { + if (!isset($this->data[$key]) || '' == $this->data[$key]) { + $this->data[$key] = $insertId; + } + } + } + + // 关联写入 + if (isset($relation)) { + foreach ($relation as $name => $val) { + $method = Loader::parseName($name, 1, false); + $this->$method()->save($val); + } + } + + // 标记为更新 + $this->isUpdate = true; + + // 新增回调 + $this->trigger('after_insert', $this); + } + // 写入回调 + $this->trigger('after_write', $this); + + // 重新记录原始数据 + $this->origin = $this->data; + + return $result; + } + + protected function checkAllowField($auto = []) + { + if (true === $this->field) { + $this->field = $this->getQuery()->getTableInfo('', 'fields'); + $field = $this->field; + } elseif (!empty($this->field)) { + $field = array_merge($this->field, $auto); + if ($this->autoWriteTimestamp) { + array_push($field, $this->createTime, $this->updateTime); + } + } elseif (!empty($this->except)) { + $fields = $this->getQuery()->getTableInfo('', 'fields'); + $field = array_diff($fields, (array) $this->except); + $this->field = $field; + } else { + $field = []; + } + + if ($this->disuse) { + // 废弃字段 + $field = array_diff($field, (array) $this->disuse); + } + return $field; + } + + protected function autoRelationUpdate($relation) + { + foreach ($relation as $name => $val) { + if ($val instanceof Model) { + $val->save(); + } else { + unset($this->data[$name]); + $model = $this->getAttr($name); + if ($model instanceof Model) { + $model->save($val); + } + } + } + } + + /** + * 获取变化的数据 并排除只读数据 + * @access public + * @return array + */ + public function getChangedData() + { + if ($this->force) { + $data = $this->data; + } else { + $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; + }); + } + + if (!empty($this->readonly)) { + // 只读字段不允许更新 + foreach ($this->readonly as $key => $field) { + if (isset($data[$field])) { + unset($data[$field]); + } + } + } + + return $data; + } + + /** + * 字段值(延迟)增长 + * @access public + * @param string $field 字段名 + * @param integer $step 增长值 + * @param integer $lazyTime 延时时间(s) + * @return integer|true + * @throws Exception + */ + public function setInc($field, $step = 1, $lazyTime = 0) + { + // 更新条件 + $where = $this->getWhere(); + + $result = $this->getQuery()->where($where)->setInc($field, $step, $lazyTime); + if (true !== $result) { + $this->data[$field] += $step; + } + + return $result; + } + + /** + * 字段值(延迟)增长 + * @access public + * @param string $field 字段名 + * @param integer $step 增长值 + * @param integer $lazyTime 延时时间(s) + * @return integer|true + * @throws Exception + */ + public function setDec($field, $step = 1, $lazyTime = 0) + { + // 更新条件 + $where = $this->getWhere(); + $result = $this->getQuery()->where($where)->setDec($field, $step, $lazyTime); + if (true !== $result) { + $this->data[$field] -= $step; + } + + return $result; + } + + /** + * 获取更新条件 + * @access protected + * @return mixed + */ + protected function getWhere() + { + // 删除条件 + $pk = $this->getPk(); + + if (is_string($pk) && isset($this->data[$pk])) { + $where = [$pk => $this->data[$pk]]; + } elseif (!empty($this->updateWhere)) { + $where = $this->updateWhere; + } else { + $where = null; + } + return $where; + } + + /** + * 保存多个数据到当前数据对象 + * @access public + * @param array $dataSet 数据 + * @param boolean $replace 是否自动识别更新和写入 + * @return array|false + * @throws \Exception + */ + public function saveAll($dataSet, $replace = true) + { + if ($this->validate) { + // 数据批量验证 + $validate = $this->validate; + foreach ($dataSet as $data) { + if (!$this->validateData($data, $validate)) { + return false; + } + } + } + + $result = []; + $db = $this->getQuery(); + $db->startTrans(); + try { + $pk = $this->getPk(); + if (is_string($pk) && $replace) { + $auto = true; + } + foreach ($dataSet as $key => $data) { + if ($this->isUpdate || (!empty($auto) && isset($data[$pk]))) { + $result[$key] = self::update($data, [], $this->field); + } else { + $result[$key] = self::create($data, $this->field); + } + } + $db->commit(); + return $this->toCollection($result); + } catch (\Exception $e) { + $db->rollback(); + throw $e; + } + } + + /** + * 设置允许写入的字段 + * @access public + * @param string|array $field 允许写入的字段 如果为true只允许写入数据表字段 + * @return $this + */ + public function allowField($field) + { + if (is_string($field)) { + $field = explode(',', $field); + } + $this->field = $field; + return $this; + } + + /** + * 设置排除写入的字段 + * @access public + * @param string|array $field 排除允许写入的字段 + * @return $this + */ + public function except($field) + { + if (is_string($field)) { + $field = explode(',', $field); + } + $this->except = $field; + return $this; + } + + /** + * 设置只读字段 + * @access public + * @param mixed $field 只读字段 + * @return $this + */ + public function readonly($field) + { + if (is_string($field)) { + $field = explode(',', $field); + } + $this->readonly = $field; + return $this; + } + + /** + * 是否为更新数据 + * @access public + * @param bool $update + * @param mixed $where + * @return $this + */ + public function isUpdate($update = true, $where = null) + { + $this->isUpdate = $update; + if (!empty($where)) { + $this->updateWhere = $where; + } + return $this; + } + + /** + * 数据自动完成 + * @access public + * @param array $auto 要自动更新的字段列表 + * @return void + */ + protected function autoCompleteData($auto = []) + { + foreach ($auto as $field => $value) { + if (is_integer($field)) { + $field = $value; + $value = null; + } + + if (!isset($this->data[$field])) { + $default = null; + } else { + $default = $this->data[$field]; + } + + $this->setAttr($field, !is_null($value) ? $value : $default); + } + } + + /** + * 删除当前的记录 + * @access public + * @return integer + */ + public function delete() + { + if (false === $this->trigger('before_delete', $this)) { + return false; + } + + // 删除条件 + $where = $this->getWhere(); + + // 删除当前模型数据 + $result = $this->getQuery()->where($where)->delete(); + + // 关联删除 + if (!empty($this->relationWrite)) { + foreach ($this->relationWrite as $key => $name) { + $name = is_numeric($key) ? $name : $key; + $model = $this->getAttr($name); + if ($model instanceof Model) { + $model->delete(); + } + } + } + + $this->trigger('after_delete', $this); + // 清空原始数据 + $this->origin = []; + + return $result; + } + + /** + * 设置自动完成的字段( 规则通过修改器定义) + * @access public + * @param array $fields 需要自动完成的字段 + * @return $this + */ + public function auto($fields) + { + $this->auto = $fields; + return $this; + } + + /** + * 设置字段验证 + * @access public + * @param array|string|bool $rule 验证规则 true表示自动读取验证器类 + * @param array $msg 提示信息 + * @param bool $batch 批量验证 + * @return $this + */ + public function validate($rule = true, $msg = [], $batch = false) + { + if (is_array($rule)) { + $this->validate = [ + 'rule' => $rule, + 'msg' => $msg, + ]; + } else { + $this->validate = true === $rule ? $this->name : $rule; + } + $this->batchValidate = $batch; + return $this; + } + + /** + * 设置验证失败后是否抛出异常 + * @access public + * @param bool $fail 是否抛出异常 + * @return $this + */ + public function validateFailException($fail = true) + { + $this->failException = $fail; + return $this; + } + + /** + * 自动验证数据 + * @access protected + * @param array $data 验证数据 + * @param mixed $rule 验证规则 + * @param bool $batch 批量验证 + * @return bool + */ + protected function validateData($data, $rule = null, $batch = null) + { + $info = is_null($rule) ? $this->validate : $rule; + + if (!empty($info)) { + if (is_array($info)) { + $validate = Loader::validate(); + $validate->rule($info['rule']); + $validate->message($info['msg']); + } else { + $name = is_string($info) ? $info : $this->name; + if (strpos($name, '.')) { + list($name, $scene) = explode('.', $name); + } + $validate = Loader::validate($name); + if (!empty($scene)) { + $validate->scene($scene); + } + } + $batch = is_null($batch) ? $this->batchValidate : $batch; + + if (!$validate->batch($batch)->check($data)) { + $this->error = $validate->getError(); + if ($this->failException) { + throw new ValidateException($this->error); + } else { + return false; + } + } + $this->validate = null; + } + return true; + } + + /** + * 返回模型的错误信息 + * @access public + * @return string|array + */ + public function getError() + { + return $this->error; + } + + /** + * 注册回调方法 + * @access public + * @param string $event 事件名 + * @param callable $callback 回调方法 + * @param bool $override 是否覆盖 + * @return void + */ + public static function event($event, $callback, $override = false) + { + $class = get_called_class(); + if ($override) { + self::$event[$class][$event] = []; + } + self::$event[$class][$event][] = $callback; + } + + /** + * 触发事件 + * @access protected + * @param string $event 事件名 + * @param mixed $params 传入参数(引用) + * @return bool + */ + protected function trigger($event, &$params) + { + if (isset(self::$event[$this->class][$event])) { + foreach (self::$event[$this->class][$event] as $callback) { + if (is_callable($callback)) { + $result = call_user_func_array($callback, [ & $params]); + if (false === $result) { + return false; + } + } + } + } + return true; + } + + /** + * 写入数据 + * @access public + * @param array $data 数据数组 + * @param array|true $field 允许字段 + * @return $this + */ + public static function create($data = [], $field = null) + { + $model = new static(); + if (!empty($field)) { + $model->allowField($field); + } + $model->isUpdate(false)->save($data, []); + return $model; + } + + /** + * 更新数据 + * @access public + * @param array $data 数据数组 + * @param array $where 更新条件 + * @param array|true $field 允许字段 + * @return $this + */ + public static function update($data = [], $where = [], $field = null) + { + $model = new static(); + if (!empty($field)) { + $model->allowField($field); + } + $result = $model->isUpdate(true)->save($data, $where); + return $model; + } + + /** + * 查找单条记录 + * @access public + * @param mixed $data 主键值或者查询条件(闭包) + * @param array|string $with 关联预查询 + * @param bool $cache 是否缓存 + * @return static|null + * @throws exception\DbException + */ + public static function get($data, $with = [], $cache = false) + { + if (is_null($data)) { + return; + } + + if (true === $with || is_int($with)) { + $cache = $with; + $with = []; + } + $query = static::parseQuery($data, $with, $cache); + return $query->find($data); + } + + /** + * 查找所有记录 + * @access public + * @param mixed $data 主键列表或者查询条件(闭包) + * @param array|string $with 关联预查询 + * @param bool $cache 是否缓存 + * @return static[]|false + * @throws exception\DbException + */ + public static function all($data = null, $with = [], $cache = false) + { + if (true === $with || is_int($with)) { + $cache = $with; + $with = []; + } + $query = static::parseQuery($data, $with, $cache); + return $query->select($data); + } + + /** + * 分析查询表达式 + * @access public + * @param mixed $data 主键列表或者查询条件(闭包) + * @param string $with 关联预查询 + * @param bool $cache 是否缓存 + * @return Query + */ + protected static function parseQuery(&$data, $with, $cache) + { + $result = self::with($with)->cache($cache); + if (is_array($data) && key($data) !== 0) { + $result = $result->where($data); + $data = null; + } elseif ($data instanceof \Closure) { + call_user_func_array($data, [ & $result]); + $data = null; + } elseif ($data instanceof Query) { + $result = $data->with($with)->cache($cache); + $data = null; + } + return $result; + } + + /** + * 删除记录 + * @access public + * @param mixed $data 主键列表 支持闭包查询条件 + * @return integer 成功删除的记录数 + */ + public static function destroy($data) + { + $model = new static(); + $query = $model->db(); + if (empty($data) && 0 !== $data) { + return 0; + } elseif (is_array($data) && key($data) !== 0) { + $query->where($data); + $data = null; + } elseif ($data instanceof \Closure) { + call_user_func_array($data, [ & $query]); + $data = null; + } + $resultSet = $query->select($data); + $count = 0; + if ($resultSet) { + foreach ($resultSet as $data) { + $result = $data->delete(); + $count += $result; + } + } + return $count; + } + + /** + * 命名范围 + * @access public + * @param string|array|\Closure $name 命名范围名称 逗号分隔 + * @internal mixed ...$params 参数调用 + * @return Query + */ + public static function scope($name) + { + $model = new static(); + $query = $model->db(); + $params = func_get_args(); + array_shift($params); + array_unshift($params, $query); + if ($name instanceof \Closure) { + call_user_func_array($name, $params); + } elseif (is_string($name)) { + $name = explode(',', $name); + } + if (is_array($name)) { + foreach ($name as $scope) { + $method = 'scope' . trim($scope); + if (method_exists($model, $method)) { + call_user_func_array([$model, $method], $params); + } + } + } + return $query; + } + + /** + * 设置是否使用全局查询范围 + * @param bool $use 是否启用全局查询范围 + * @access public + * @return Query + */ + public static function useGlobalScope($use) + { + $model = new static(); + return $model->db($use); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $relation 关联方法名 + * @param mixed $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @return Relation|Query + */ + public static function has($relation, $operator = '>=', $count = 1, $id = '*') + { + $relation = (new static())->$relation(); + if (is_array($operator) || $operator instanceof \Closure) { + return $relation->hasWhere($operator); + } + return $relation->has($operator, $count, $id); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $relation 关联方法名 + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Relation|Query + */ + public static function hasWhere($relation, $where = [], $fields = null) + { + return (new static())->$relation()->hasWhere($where, $fields); + } + + /** + * 解析模型的完整命名空间 + * @access public + * @param string $model 模型名(或者完整类名) + * @return string + */ + protected function parseModel($model) + { + if (false === strpos($model, '\\')) { + $path = explode('\\', get_called_class()); + array_pop($path); + array_push($path, Loader::parseName($model, 1)); + $model = implode('\\', $path); + } + return $model; + } + + /** + * 查询当前模型的关联数据 + * @access public + * @param string|array $relations 关联名 + * @return $this + */ + public function relationQuery($relations) + { + if (is_string($relations)) { + $relations = explode(',', $relations); + } + + 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, '.')) { + list($relation, $subRelation) = explode('.', $relation, 2); + } + $method = Loader::parseName($relation, 1, false); + $this->data[$relation] = $this->$method()->getRelation($subRelation, $closure); + } + return $this; + } + + /** + * 预载入关联查询 返回数据集 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 关联名 + * @return array + */ + public function eagerlyResultSet(&$resultSet, $relation) + { + $relations = is_string($relation) ? explode(',', $relation) : $relation; + foreach ($relations as $key => $relation) { + $subRelation = ''; + $closure = false; + if ($relation instanceof \Closure) { + $closure = $relation; + $relation = $key; + } + if (is_array($relation)) { + $subRelation = $relation; + $relation = $key; + } elseif (strpos($relation, '.')) { + list($relation, $subRelation) = explode('.', $relation, 2); + } + $relation = Loader::parseName($relation, 1, false); + $this->$relation()->eagerlyResultSet($resultSet, $relation, $subRelation, $closure); + } + } + + /** + * 预载入关联查询 返回模型对象 + * @access public + * @param Model $result 数据对象 + * @param string $relation 关联名 + * @return Model + */ + public function eagerlyResult(&$result, $relation) + { + $relations = is_string($relation) ? explode(',', $relation) : $relation; + + foreach ($relations as $key => $relation) { + $subRelation = ''; + $closure = false; + if ($relation instanceof \Closure) { + $closure = $relation; + $relation = $key; + } + if (is_array($relation)) { + $subRelation = $relation; + $relation = $key; + } elseif (strpos($relation, '.')) { + list($relation, $subRelation) = explode('.', $relation, 2); + } + $relation = Loader::parseName($relation, 1, false); + $this->$relation()->eagerlyResult($result, $relation, $subRelation, $closure); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param string|array $relation 关联名 + * @return void + */ + public function relationCount(&$result, $relation) + { + $relations = is_string($relation) ? explode(',', $relation) : $relation; + + foreach ($relations as $key => $relation) { + $closure = false; + if ($relation instanceof \Closure) { + $closure = $relation; + $relation = $key; + } elseif (is_string($key)) { + $name = $relation; + $relation = $key; + } + $relation = Loader::parseName($relation, 1, false); + $count = $this->$relation()->relationCount($result, $closure); + if (!isset($name)) { + $name = Loader::parseName($relation) . '_count'; + } + $result->setAttr($name, $count); + } + } + + /** + * 获取模型的默认外键名 + * @access public + * @param string $name 模型名 + * @return string + */ + protected function getForeignKey($name) + { + if (strpos($name, '\\')) { + $name = basename(str_replace('\\', '/', $name)); + } + return Loader::parseName($name) . '_id'; + } + + /** + * HAS ONE 关联定义 + * @access public + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前模型主键 + * @param array $alias 别名定义(已经废弃) + * @param string $joinType JOIN类型 + * @return HasOne + */ + public function hasOne($model, $foreignKey = '', $localKey = '', $alias = [], $joinType = 'INNER') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $localKey = $localKey ?: $this->getPk(); + $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); + return new HasOne($this, $model, $foreignKey, $localKey, $joinType); + } + + /** + * BELONGS TO 关联定义 + * @access public + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 关联主键 + * @param array $alias 别名定义(已经废弃) + * @param string $joinType JOIN类型 + * @return BelongsTo + */ + public function belongsTo($model, $foreignKey = '', $localKey = '', $alias = [], $joinType = 'INNER') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $foreignKey = $foreignKey ?: $this->getForeignKey($model); + $localKey = $localKey ?: (new $model)->getPk(); + $trace = debug_backtrace(false, 2); + $relation = Loader::parseName($trace[1]['function']); + return new BelongsTo($this, $model, $foreignKey, $localKey, $joinType, $relation); + } + + /** + * HAS MANY 关联定义 + * @access public + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前模型主键 + * @return HasMany + */ + public function hasMany($model, $foreignKey = '', $localKey = '') + { + // 记录当前关联信息 + $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 当前模型主键 + * @return HasManyThrough + */ + public function hasManyThrough($model, $through, $foreignKey = '', $throughKey = '', $localKey = '') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $through = $this->parseModel($through); + $localKey = $localKey ?: $this->getPk(); + $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); + $throughKey = $throughKey ?: $this->getForeignKey($through); + return new HasManyThrough($this, $model, $through, $foreignKey, $throughKey, $localKey); + } + + /** + * BELONGS TO MANY 关联定义 + * @access public + * @param string $model 模型名 + * @param string $table 中间表名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前模型关联键 + * @return BelongsToMany + */ + public function belongsToMany($model, $table = '', $foreignKey = '', $localKey = '') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $name = Loader::parseName(basename(str_replace('\\', '/', $model))); + $table = $table ?: Loader::parseName($this->name) . '_' . $name; + $foreignKey = $foreignKey ?: $name . '_id'; + $localKey = $localKey ?: $this->getForeignKey($this->name); + return new BelongsToMany($this, $model, $table, $foreignKey, $localKey); + } + + /** + * MORPH MANY 关联定义 + * @access public + * @param string $model 模型名 + * @param string|array $morph 多态字段信息 + * @param string $type 多态类型 + * @return MorphMany + */ + public function morphMany($model, $morph = null, $type = '') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + if (is_null($morph)) { + $trace = debug_backtrace(false, 2); + $morph = Loader::parseName($trace[1]['function']); + } + $type = $type ?: get_class($this); + if (is_array($morph)) { + list($morphType, $foreignKey) = $morph; + } else { + $morphType = $morph . '_type'; + $foreignKey = $morph . '_id'; + } + return new MorphMany($this, $model, $foreignKey, $morphType, $type); + } + + /** + * MORPH One 关联定义 + * @access public + * @param string $model 模型名 + * @param string|array $morph 多态字段信息 + * @param string $type 多态类型 + * @return MorphOne + */ + public function morphOne($model, $morph = null, $type = '') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + if (is_null($morph)) { + $trace = debug_backtrace(false, 2); + $morph = Loader::parseName($trace[1]['function']); + } + $type = $type ?: get_class($this); + if (is_array($morph)) { + list($morphType, $foreignKey) = $morph; + } else { + $morphType = $morph . '_type'; + $foreignKey = $morph . '_id'; + } + return new MorphOne($this, $model, $foreignKey, $morphType, $type); + } + + /** + * MORPH TO 关联定义 + * @access public + * @param string|array $morph 多态字段信息 + * @param array $alias 多态别名定义 + * @return MorphTo + */ + public function morphTo($morph = null, $alias = []) + { + $trace = debug_backtrace(false, 2); + $relation = Loader::parseName($trace[1]['function']); + + if (is_null($morph)) { + $morph = $relation; + } + // 记录当前关联信息 + if (is_array($morph)) { + list($morphType, $foreignKey) = $morph; + } else { + $morphType = $morph . '_type'; + $foreignKey = $morph . '_id'; + } + return new MorphTo($this, $morphType, $foreignKey, $alias, $relation); + } + + public function __call($method, $args) + { + $query = $this->db(true, false); + if (method_exists($this, 'scope' . $method)) { + // 动态调用命名范围 + $method = 'scope' . $method; + array_unshift($args, $query); + call_user_func_array([$this, $method], $args); + return $this; + } else { + return call_user_func_array([$query, $method], $args); + } + } + + public static function __callStatic($method, $args) + { + $model = new static(); + $query = $model->db(); + if (method_exists($model, 'scope' . $method)) { + // 动态调用命名范围 + $method = 'scope' . $method; + array_unshift($args, $query); + + call_user_func_array([$model, $method], $args); + return $query; + } else { + return call_user_func_array([$query, $method], $args); + } + } + + /** + * 修改器 设置数据对象的值 + * @access public + * @param string $name 名称 + * @param mixed $value 值 + * @return void + */ + public function __set($name, $value) + { + $this->setAttr($name, $value); + } + + /** + * 获取器 获取数据对象的值 + * @access public + * @param string $name 名称 + * @return mixed + */ + public function __get($name) + { + return $this->getAttr($name); + } + + /** + * 检测数据对象的值 + * @access public + * @param string $name 名称 + * @return boolean + */ + public function __isset($name) + { + try { + if (array_key_exists($name, $this->data) || array_key_exists($name, $this->relation)) { + return true; + } else { + $this->getAttr($name); + return true; + } + } catch (InvalidArgumentException $e) { + return false; + } + + } + + /** + * 销毁数据对象的值 + * @access public + * @param string $name 名称 + * @return void + */ + public function __unset($name) + { + unset($this->data[$name], $this->relation[$name]); + } + + public function __toString() + { + return $this->toJson(); + } + + // JsonSerializable + public function jsonSerialize() + { + return $this->toArray(); + } + + // ArrayAccess + public function offsetSet($name, $value) + { + $this->setAttr($name, $value); + } + + public function offsetExists($name) + { + return $this->__isset($name); + } + + public function offsetUnset($name) + { + $this->__unset($name); + } + + public function offsetGet($name) + { + return $this->getAttr($name); + } + + /** + * 解序列化后处理 + */ + public function __wakeup() + { + $this->initialize(); + } + + /** + * 模型事件快捷方法 + * @param $callback + * @param bool $override + */ + protected static function beforeInsert($callback, $override = false) + { + self::event('before_insert', $callback, $override); + } + + protected static function afterInsert($callback, $override = false) + { + self::event('after_insert', $callback, $override); + } + + protected static function beforeUpdate($callback, $override = false) + { + self::event('before_update', $callback, $override); + } + + protected static function afterUpdate($callback, $override = false) + { + self::event('after_update', $callback, $override); + } + + protected static function beforeWrite($callback, $override = false) + { + self::event('before_write', $callback, $override); + } + + protected static function afterWrite($callback, $override = false) + { + self::event('after_write', $callback, $override); + } + + protected static function beforeDelete($callback, $override = false) + { + self::event('before_delete', $callback, $override); + } + + protected static function afterDelete($callback, $override = false) + { + self::event('after_delete', $callback, $override); + } + +} diff --git a/thinkphp/library/think/Paginator.php b/thinkphp/library/think/Paginator.php new file mode 100644 index 0000000..3655567 --- /dev/null +++ b/thinkphp/library/think/Paginator.php @@ -0,0 +1,409 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use ArrayAccess; +use ArrayIterator; +use Countable; +use IteratorAggregate; +use JsonSerializable; +use Traversable; + +abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable +{ + /** @var bool 是否为简洁模式 */ + protected $simple = false; + + /** @var Collection 数据集 */ + protected $items; + + /** @var integer 当前页 */ + protected $currentPage; + + /** @var integer 最后一页 */ + protected $lastPage; + + /** @var integer|null 数据总数 */ + protected $total; + + /** @var integer 每页的数量 */ + protected $listRows; + + /** @var bool 是否有下一页 */ + protected $hasMore; + + /** @var array 一些配置 */ + protected $options = [ + 'var_page' => 'page', + 'path' => '/', + 'query' => [], + 'fragment' => '', + ]; + + /** @var mixed simple模式下的下个元素 */ + protected $nextItem; + + public function __construct($items, $listRows, $currentPage = null, $total = null, $simple = false, $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); + if ($this->hasMore) { + $this->nextItem = $items->slice($this->listRows, 1); + } + $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; + } + + /** + * @param $items + * @param $listRows + * @param null $currentPage + * @param bool $simple + * @param null $total + * @param array $options + * @return Paginator + */ + public static function make($items, $listRows, $currentPage = null, $total = null, $simple = false, $options = []) + { + return new static($items, $listRows, $currentPage, $total, $simple, $options); + } + + protected function setCurrentPage($currentPage) + { + if (!$this->simple && $currentPage > $this->lastPage) { + return $this->lastPage > 0 ? $this->lastPage : 1; + } + + return $currentPage; + } + + /** + * 获取页码对应的链接 + * + * @param $page + * @return string + */ + protected function url($page) + { + 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, null, '&'); + } + return $url . $this->buildFragment(); + } + + /** + * 自动获取当前页码 + * @param string $varPage + * @param int $default + * @return int + */ + public static function getCurrentPage($varPage = 'page', $default = 1) + { + $page = (int) Request::instance()->param($varPage); + + if (filter_var($page, FILTER_VALIDATE_INT) !== false && $page >= 1) { + return $page; + } + + return $default; + } + + /** + * 自动获取当前的path + * @return string + */ + public static function getCurrentPath() + { + return Request::instance()->baseUrl(); + } + + public function total() + { + if ($this->simple) { + throw new \DomainException('not support total'); + } + return $this->total; + } + + public function listRows() + { + return $this->listRows; + } + + public function currentPage() + { + return $this->currentPage; + } + + public function lastPage() + { + if ($this->simple) { + throw new \DomainException('not support last'); + } + return $this->lastPage; + } + + /** + * 数据是否足够分页 + * @return boolean + */ + public function hasPages() + { + return !(1 == $this->currentPage && !$this->hasMore); + } + + /** + * 创建一组分页链接 + * + * @param int $start + * @param int $end + * @return array + */ + public function getUrlRange($start, $end) + { + $urls = []; + + for ($page = $start; $page <= $end; $page++) { + $urls[$page] = $this->url($page); + } + + return $urls; + } + + /** + * 设置URL锚点 + * + * @param string|null $fragment + * @return $this + */ + public function fragment($fragment) + { + $this->options['fragment'] = $fragment; + return $this; + } + + /** + * 添加URL参数 + * + * @param array|string $key + * @param string|null $value + * @return $this + */ + public function appends($key, $value = null) + { + if (!is_array($key)) { + $queries = [$key => $value]; + } else { + $queries = $key; + } + + foreach ($queries as $k => $v) { + if ($k !== $this->options['var_page']) { + $this->options['query'][$k] = $v; + } + } + + return $this; + } + + /** + * 构造锚点字符串 + * + * @return string + */ + protected function buildFragment() + { + return $this->options['fragment'] ? '#' . $this->options['fragment'] : ''; + } + + /** + * 渲染分页html + * @return mixed + */ + abstract public function render(); + + public function items() + { + return $this->items->all(); + } + + public function getCollection() + { + return $this->items; + } + + public function isEmpty() + { + return $this->items->isEmpty(); + } + + /** + * 给每个元素执行个回调 + * + * @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 + * @return Traversable An instance of an object implementing Iterator or + * Traversable + */ + public function getIterator() + { + return new ArrayIterator($this->items->all()); + } + + /** + * Whether a offset exists + * @param mixed $offset + * @return bool + */ + public function offsetExists($offset) + { + return $this->items->offsetExists($offset); + } + + /** + * Offset to retrieve + * @param mixed $offset + * @return mixed + */ + public function offsetGet($offset) + { + return $this->items->offsetGet($offset); + } + + /** + * Offset to set + * @param mixed $offset + * @param mixed $value + */ + public function offsetSet($offset, $value) + { + $this->items->offsetSet($offset, $value); + } + + /** + * Offset to unset + * @param mixed $offset + * @return void + * @since 5.0.0 + */ + public function offsetUnset($offset) + { + $this->items->offsetUnset($offset); + } + + /** + * Count elements of an object + */ + public function count() + { + return $this->items->count(); + } + + public function __toString() + { + return (string) $this->render(); + } + + public function toArray() + { + if ($this->simple) { + return [ + 'per_page' => $this->listRows, + 'current_page' => $this->currentPage, + 'has_more' => $this->hasMore, + 'next_item' => $this->nextItem, + 'data' => $this->items->toArray(), + ]; + } else { + return [ + 'total' => $this->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) + { + $collection = $this->getCollection(); + + $result = call_user_func_array([$collection, $name], $arguments); + + if ($result === $collection) { + return $this; + } + + return $result; + } + +} diff --git a/thinkphp/library/think/Process.php b/thinkphp/library/think/Process.php new file mode 100644 index 0000000..6f3faa3 --- /dev/null +++ b/thinkphp/library/think/Process.php @@ -0,0 +1,1205 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\process\exception\Failed as ProcessFailedException; +use think\process\exception\Timeout as ProcessTimeoutException; +use think\process\pipes\Pipes; +use think\process\pipes\Unix as UnixPipes; +use think\process\pipes\Windows as WindowsPipes; +use think\process\Utils; + +class Process +{ + + 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; + + const TIMEOUT_PRECISION = 0.2; + + private $callback; + private $commandline; + private $cwd; + private $env; + private $input; + private $starttime; + private $lastOutputTime; + private $timeout; + private $idleTimeout; + private $options; + private $exitcode; + private $fallbackExitcode; + private $processInformation; + private $outputDisabled = false; + private $stdout; + private $stderr; + private $enhanceWindowsCompatibility = true; + private $enhanceSigchildCompatibility; + private $process; + private $status = self::STATUS_READY; + private $incrementalOutputOffset = 0; + private $incrementalErrorOutputOffset = 0; + private $tty; + private $pty; + + private $useFileHandles = false; + + /** @var Pipes */ + private $processPipes; + + private $latestSignal; + + private static $sigchild; + + /** + * @var array + */ + 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 string $commandline 指令 + * @param string|null $cwd 工作目录 + * @param array|null $env 环境变量 + * @param string|null $input 输入 + * @param int|float|null $timeout 超时时间 + * @param array $options proc_open的选项 + * @throws \RuntimeException + * @api + */ + public function __construct($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60, array $options = []) + { + if (!function_exists('proc_open')) { + throw new \RuntimeException('The Process class relies on proc_open, which is not available on your PHP installation.'); + } + + $this->commandline = $commandline; + $this->cwd = $cwd; + + if (null === $this->cwd && (defined('ZEND_THREAD_SAFE') || '\\' === DS)) { + $this->cwd = getcwd(); + } + if (null !== $env) { + $this->setEnv($env); + } + + $this->input = $input; + $this->setTimeout($timeout); + $this->useFileHandles = '\\' === DS; + $this->pty = false; + $this->enhanceWindowsCompatibility = true; + $this->enhanceSigchildCompatibility = '\\' !== DS && $this->isSigchildEnabled(); + $this->options = array_replace([ + 'suppress_errors' => true, + 'binary_pipes' => true, + ], $options); + } + + public function __destruct() + { + $this->stop(); + } + + public function __clone() + { + $this->resetProcessData(); + } + + /** + * 运行指令 + * @param callback|null $callback + * @return int + */ + public function run($callback = null) + { + $this->start($callback); + + return $this->wait(); + } + + /** + * 运行指令 + * @param callable|null $callback + * @return self + * @throws \RuntimeException + * @throws ProcessFailedException + */ + public function mustRun($callback = null) + { + if ($this->isSigchildEnabled() && !$this->enhanceSigchildCompatibility) { + throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.'); + } + + if (0 !== $this->run($callback)) { + throw new ProcessFailedException($this); + } + + return $this; + } + + /** + * 启动进程并写到 STDIN 输入后返回。 + * @param callable|null $callback + * @throws \RuntimeException + * @throws \RuntimeException + * @throws \LogicException + */ + public function start($callback = null) + { + if ($this->isRunning()) { + throw new \RuntimeException('Process is already running'); + } + if ($this->outputDisabled && null !== $callback) { + throw new \LogicException('Output has been disabled, enable it to allow the use of a callback.'); + } + + $this->resetProcessData(); + $this->starttime = $this->lastOutputTime = microtime(true); + $this->callback = $this->buildCallback($callback); + $descriptors = $this->getDescriptors(); + + $commandline = $this->commandline; + + if ('\\' === DS && $this->enhanceWindowsCompatibility) { + $commandline = 'cmd /V:ON /E:ON /C "(' . $commandline . ')'; + foreach ($this->processPipes->getFiles() as $offset => $filename) { + $commandline .= ' ' . $offset . '>' . Utils::escapeArgument($filename); + } + $commandline .= '"'; + + if (!isset($this->options['bypass_shell'])) { + $this->options['bypass_shell'] = true; + } + } + + $this->process = proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $this->env, $this->options); + + if (!is_resource($this->process)) { + throw new \RuntimeException('Unable to launch a new process.'); + } + $this->status = self::STATUS_STARTED; + + if ($this->tty) { + return; + } + + $this->updateStatus(false); + $this->checkTimeout(); + } + + /** + * 重启进程 + * @param callable|null $callback + * @return Process + * @throws \RuntimeException + * @throws \RuntimeException + */ + public function restart($callback = null) + { + if ($this->isRunning()) { + throw new \RuntimeException('Process is already running'); + } + + $process = clone $this; + $process->start($callback); + + return $process; + } + + /** + * 等待要终止的进程 + * @param callable|null $callback + * @return int + */ + public function wait($callback = null) + { + $this->requireProcessIsStarted(__FUNCTION__); + + $this->updateStatus(false); + if (null !== $callback) { + $this->callback = $this->buildCallback($callback); + } + + do { + $this->checkTimeout(); + $running = '\\' === DS ? $this->isRunning() : $this->processPipes->areOpen(); + $close = '\\' !== DS || !$running; + $this->readPipes(true, $close); + } while ($running); + + while ($this->isRunning()) { + usleep(1000); + } + + if ($this->processInformation['signaled'] && $this->processInformation['termsig'] !== $this->latestSignal) { + throw new \RuntimeException(sprintf('The process has been signaled with signal "%s".', $this->processInformation['termsig'])); + } + + return $this->exitcode; + } + + /** + * 获取PID + * @return int|null + * @throws \RuntimeException + */ + public function getPid() + { + if ($this->isSigchildEnabled()) { + throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. The process identifier can not be retrieved.'); + } + + $this->updateStatus(false); + + return $this->isRunning() ? $this->processInformation['pid'] : null; + } + + /** + * 将一个 POSIX 信号发送到进程中 + * @param int $signal + * @return Process + */ + public function signal($signal) + { + $this->doSignal($signal, true); + + return $this; + } + + /** + * 禁用从底层过程获取输出和错误输出。 + * @return Process + */ + 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; + } + + /** + * 开启从底层过程获取输出和错误输出。 + * @return Process + * @throws \RuntimeException + */ + public function enableOutput() + { + if ($this->isRunning()) { + throw new \RuntimeException('Enabling output while the process is running is not possible.'); + } + + $this->outputDisabled = false; + + return $this; + } + + /** + * 输出是否禁用 + * @return bool + */ + public function isOutputDisabled() + { + return $this->outputDisabled; + } + + /** + * 获取当前的输出管道 + * @return string + * @throws \LogicException + * @throws \LogicException + * @api + */ + public function getOutput() + { + if ($this->outputDisabled) { + throw new \LogicException('Output has been disabled.'); + } + + $this->requireProcessIsStarted(__FUNCTION__); + + $this->readPipes(false, '\\' === DS ? !$this->processInformation['running'] : true); + + return $this->stdout; + } + + /** + * 以增量方式返回的输出结果。 + * @return string + */ + public function getIncrementalOutput() + { + $this->requireProcessIsStarted(__FUNCTION__); + + $data = $this->getOutput(); + + $latest = substr($data, $this->incrementalOutputOffset); + + if (false === $latest) { + return ''; + } + + $this->incrementalOutputOffset = strlen($data); + + return $latest; + } + + /** + * 清空输出 + * @return Process + */ + public function clearOutput() + { + $this->stdout = ''; + $this->incrementalOutputOffset = 0; + + return $this; + } + + /** + * 返回当前的错误输出的过程 (STDERR)。 + * @return string + */ + public function getErrorOutput() + { + if ($this->outputDisabled) { + throw new \LogicException('Output has been disabled.'); + } + + $this->requireProcessIsStarted(__FUNCTION__); + + $this->readPipes(false, '\\' === DS ? !$this->processInformation['running'] : true); + + return $this->stderr; + } + + /** + * 以增量方式返回 errorOutput + * @return string + */ + public function getIncrementalErrorOutput() + { + $this->requireProcessIsStarted(__FUNCTION__); + + $data = $this->getErrorOutput(); + + $latest = substr($data, $this->incrementalErrorOutputOffset); + + if (false === $latest) { + return ''; + } + + $this->incrementalErrorOutputOffset = strlen($data); + + return $latest; + } + + /** + * 清空 errorOutput + * @return Process + */ + public function clearErrorOutput() + { + $this->stderr = ''; + $this->incrementalErrorOutputOffset = 0; + + return $this; + } + + /** + * 获取退出码 + * @return null|int + */ + public function getExitCode() + { + if ($this->isSigchildEnabled() && !$this->enhanceSigchildCompatibility) { + throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.'); + } + + $this->updateStatus(false); + + return $this->exitcode; + } + + /** + * 获取退出文本 + * @return null|string + */ + public function getExitCodeText() + { + if (null === $exitcode = $this->getExitCode()) { + return; + } + + return isset(self::$exitCodes[$exitcode]) ? self::$exitCodes[$exitcode] : 'Unknown error'; + } + + /** + * 检查是否成功 + * @return bool + */ + public function isSuccessful() + { + return 0 === $this->getExitCode(); + } + + /** + * 是否未捕获的信号已被终止子进程 + * @return bool + */ + public function hasBeenSignaled() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + if ($this->isSigchildEnabled()) { + throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.'); + } + + $this->updateStatus(false); + + return $this->processInformation['signaled']; + } + + /** + * 返回导致子进程终止其执行的数。 + * @return int + */ + public function getTermSignal() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + if ($this->isSigchildEnabled()) { + throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.'); + } + + $this->updateStatus(false); + + return $this->processInformation['termsig']; + } + + /** + * 检查子进程信号是否已停止 + * @return bool + */ + public function hasBeenStopped() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + $this->updateStatus(false); + + return $this->processInformation['stopped']; + } + + /** + * 返回导致子进程停止其执行的数。 + * @return int + */ + public function getStopSignal() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + $this->updateStatus(false); + + return $this->processInformation['stopsig']; + } + + /** + * 检查是否正在运行 + * @return bool + */ + public function isRunning() + { + if (self::STATUS_STARTED !== $this->status) { + return false; + } + + $this->updateStatus(false); + + return $this->processInformation['running']; + } + + /** + * 检查是否已开始 + * @return bool + */ + public function isStarted() + { + return self::STATUS_READY != $this->status; + } + + /** + * 检查是否已终止 + * @return bool + */ + public function isTerminated() + { + $this->updateStatus(false); + + return self::STATUS_TERMINATED == $this->status; + } + + /** + * 获取当前的状态 + * @return string + */ + public function getStatus() + { + $this->updateStatus(false); + + return $this->status; + } + + /** + * 终止进程 + */ + public function stop() + { + if ($this->isRunning()) { + if ('\\' === DS && !$this->isSigchildEnabled()) { + exec(sprintf('taskkill /F /T /PID %d 2>&1', $this->getPid()), $output, $exitCode); + if ($exitCode > 0) { + throw new \RuntimeException('Unable to kill the process'); + } + } else { + $pids = preg_split('/\s+/', `ps -o pid --no-heading --ppid {$this->getPid()}`); + foreach ($pids as $pid) { + if (is_numeric($pid)) { + posix_kill($pid, 9); + } + } + } + } + + $this->updateStatus(false); + if ($this->processInformation['running']) { + $this->close(); + } + + return $this->exitcode; + } + + /** + * 添加一行输出 + * @param string $line + */ + public function addOutput($line) +{ + $this->lastOutputTime = microtime(true); + $this->stdout .= $line; + } + + /** + * 添加一行错误输出 + * @param string $line + */ + public function addErrorOutput($line) +{ + $this->lastOutputTime = microtime(true); + $this->stderr .= $line; + } + + /** + * 获取被执行的指令 + * @return string + */ + public function getCommandLine() +{ + return $this->commandline; + } + + /** + * 设置指令 + * @param string $commandline + * @return self + */ + public function setCommandLine($commandline) +{ + $this->commandline = $commandline; + + return $this; + } + + /** + * 获取超时时间 + * @return float|null + */ + public function getTimeout() +{ + return $this->timeout; + } + + /** + * 获取idle超时时间 + * @return float|null + */ + public function getIdleTimeout() +{ + return $this->idleTimeout; + } + + /** + * 设置超时时间 + * @param int|float|null $timeout + * @return self + */ + public function setTimeout($timeout) +{ + $this->timeout = $this->validateTimeout($timeout); + + return $this; + } + + /** + * 设置idle超时时间 + * @param int|float|null $timeout + * @return self + */ + 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; + } + + /** + * 设置TTY + * @param bool $tty + * @return self + */ + public function setTty($tty) +{ + if ('\\' === DS && $tty) { + throw new \RuntimeException('TTY mode is not supported on Windows platform.'); + } + if ($tty && (!file_exists('/dev/tty') || !is_readable('/dev/tty'))) { + throw new \RuntimeException('TTY mode requires /dev/tty to be readable.'); + } + + $this->tty = (bool) $tty; + + return $this; + } + + /** + * 检查是否是tty模式 + * @return bool + */ + public function isTty() +{ + return $this->tty; + } + + /** + * 设置pty模式 + * @param bool $bool + * @return self + */ + public function setPty($bool) +{ + $this->pty = (bool) $bool; + + return $this; + } + + /** + * 是否是pty模式 + * @return bool + */ + public function isPty() +{ + return $this->pty; + } + + /** + * 获取工作目录 + * @return string|null + */ + public function getWorkingDirectory() +{ + if (null === $this->cwd) { + return getcwd() ?: null; + } + + return $this->cwd; + } + + /** + * 设置工作目录 + * @param string $cwd + * @return self + */ + public function setWorkingDirectory($cwd) +{ + $this->cwd = $cwd; + + return $this; + } + + /** + * 获取环境变量 + * @return array + */ + public function getEnv() +{ + return $this->env; + } + + /** + * 设置环境变量 + * @param array $env + * @return self + */ + public function setEnv(array $env) +{ + $env = array_filter($env, function ($value) { + return !is_array($value); + }); + + $this->env = []; + foreach ($env as $key => $value) { + $this->env[(binary) $key] = (binary) $value; + } + + return $this; + } + + /** + * 获取输入 + * @return null|string + */ + public function getInput() +{ + return $this->input; + } + + /** + * 设置输入 + * @param mixed $input + * @return self + */ + public function setInput($input) +{ + if ($this->isRunning()) { + throw new \LogicException('Input can not be set while the process is running.'); + } + + $this->input = Utils::validateInput(sprintf('%s::%s', __CLASS__, __FUNCTION__), $input); + + return $this; + } + + /** + * 获取proc_open的选项 + * @return array + */ + public function getOptions() +{ + return $this->options; + } + + /** + * 设置proc_open的选项 + * @param array $options + * @return self + */ + public function setOptions(array $options) +{ + $this->options = $options; + + return $this; + } + + /** + * 是否兼容windows + * @return bool + */ + public function getEnhanceWindowsCompatibility() +{ + return $this->enhanceWindowsCompatibility; + } + + /** + * 设置是否兼容windows + * @param bool $enhance + * @return self + */ + public function setEnhanceWindowsCompatibility($enhance) +{ + $this->enhanceWindowsCompatibility = (bool) $enhance; + + return $this; + } + + /** + * 返回是否 sigchild 兼容模式激活 + * @return bool + */ + public function getEnhanceSigchildCompatibility() +{ + return $this->enhanceSigchildCompatibility; + } + + /** + * 激活 sigchild 兼容性模式。 + * @param bool $enhance + * @return self + */ + public function setEnhanceSigchildCompatibility($enhance) +{ + $this->enhanceSigchildCompatibility = (bool) $enhance; + + return $this; + } + + /** + * 是否超时 + */ + public function checkTimeout() +{ + if (self::STATUS_STARTED !== $this->status) { + return; + } + + if (null !== $this->timeout && $this->timeout < microtime(true) - $this->starttime) { + $this->stop(); + + throw new ProcessTimeoutException($this, ProcessTimeoutException::TYPE_GENERAL); + } + + if (null !== $this->idleTimeout && $this->idleTimeout < microtime(true) - $this->lastOutputTime) { + $this->stop(); + + throw new ProcessTimeoutException($this, ProcessTimeoutException::TYPE_IDLE); + } + } + + /** + * 是否支持pty + * @return bool + */ + public static function isPtySupported() +{ + static $result; + + if (null !== $result) { + return $result; + } + + if ('\\' === DS) { + return $result = false; + } + + $proc = @proc_open('echo 1', [['pty'], ['pty'], ['pty']], $pipes); + if (is_resource($proc)) { + proc_close($proc); + + return $result = true; + } + + return $result = false; + } + + /** + * 创建所需的 proc_open 的描述符 + * @return array + */ + private function getDescriptors() +{ + if ('\\' === DS) { + $this->processPipes = WindowsPipes::create($this, $this->input); + } else { + $this->processPipes = UnixPipes::create($this, $this->input); + } + $descriptors = $this->processPipes->getDescriptors($this->outputDisabled); + + if (!$this->useFileHandles && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { + + $descriptors = array_merge($descriptors, [['pipe', 'w']]); + + $this->commandline = '(' . $this->commandline . ') 3>/dev/null; code=$?; echo $code >&3; exit $code'; + } + + return $descriptors; + } + + /** + * 建立 wait () 使用的回调。 + * @param callable|null $callback + * @return callable + */ + protected function buildCallback($callback) +{ + $out = self::OUT; + $callback = function ($type, $data) use ($callback, $out) { + if ($out == $type) { + $this->addOutput($data); + } else { + $this->addErrorOutput($data); + } + + if (null !== $callback) { + call_user_func($callback, $type, $data); + } + }; + + return $callback; + } + + /** + * 更新状态 + * @param bool $blocking + */ + protected function updateStatus($blocking) +{ + if (self::STATUS_STARTED !== $this->status) { + return; + } + + $this->processInformation = proc_get_status($this->process); + $this->captureExitCode(); + + $this->readPipes($blocking, '\\' === DS ? !$this->processInformation['running'] : true); + + if (!$this->processInformation['running']) { + $this->close(); + } + } + + /** + * 是否开启 '--enable-sigchild' + * @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'); + } + + /** + * 验证是否超时 + * @param int|float|null $timeout + * @return float|null + */ + private function validateTimeout($timeout) +{ + $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; + } + + /** + * 读取pipes + * @param bool $blocking + * @param bool $close + */ + private function readPipes($blocking, $close) +{ + $result = $this->processPipes->readAndWrite($blocking, $close); + + $callback = $this->callback; + foreach ($result as $type => $data) { + if (3 == $type) { + $this->fallbackExitcode = (int) $data; + } else { + $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data); + } + } + } + + /** + * 捕获退出码 + */ + private function captureExitCode() +{ + if (isset($this->processInformation['exitcode']) && -1 != $this->processInformation['exitcode']) { + $this->exitcode = $this->processInformation['exitcode']; + } + } + + /** + * 关闭资源 + * @return int 退出码 + */ + private function close() +{ + $this->processPipes->close(); + if (is_resource($this->process)) { + $exitcode = proc_close($this->process); + } else { + $exitcode = -1; + } + + $this->exitcode = -1 !== $exitcode ? $exitcode : (null !== $this->exitcode ? $this->exitcode : -1); + $this->status = self::STATUS_TERMINATED; + + if (-1 === $this->exitcode && null !== $this->fallbackExitcode) { + $this->exitcode = $this->fallbackExitcode; + } elseif (-1 === $this->exitcode && $this->processInformation['signaled'] + && 0 < $this->processInformation['termsig'] + ) { + $this->exitcode = 128 + $this->processInformation['termsig']; + } + + return $this->exitcode; + } + + /** + * 重置数据 + */ + private function resetProcessData() +{ + $this->starttime = null; + $this->callback = null; + $this->exitcode = null; + $this->fallbackExitcode = null; + $this->processInformation = null; + $this->stdout = null; + $this->stderr = null; + $this->process = null; + $this->latestSignal = null; + $this->status = self::STATUS_READY; + $this->incrementalOutputOffset = 0; + $this->incrementalErrorOutputOffset = 0; + } + + /** + * 将一个 POSIX 信号发送到进程中。 + * @param int $signal + * @param bool $throwException + * @return bool + */ + private function doSignal($signal, $throwException) +{ + if (!$this->isRunning()) { + if ($throwException) { + throw new \LogicException('Can not send signal on a non running process.'); + } + + return false; + } + + if ($this->isSigchildEnabled()) { + if ($throwException) { + throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. The process can not be signaled.'); + } + + return false; + } + + if (true !== @proc_terminate($this->process, $signal)) { + if ($throwException) { + throw new \RuntimeException(sprintf('Error while sending signal `%s`.', $signal)); + } + + return false; + } + + $this->latestSignal = $signal; + + return true; + } + + /** + * 确保进程已经开启 + * @param string $functionName + */ + private function requireProcessIsStarted($functionName) +{ + if (!$this->isStarted()) { + throw new \LogicException(sprintf('Process must be started before calling %s.', $functionName)); + } + } + + /** + * 确保进程已经终止 + * @param string $functionName + */ + private function requireProcessIsTerminated($functionName) +{ + if (!$this->isTerminated()) { + throw new \LogicException(sprintf('Process must be terminated before calling %s.', $functionName)); + } + } +} diff --git a/thinkphp/library/think/Request.php b/thinkphp/library/think/Request.php new file mode 100644 index 0000000..5997a76 --- /dev/null +++ b/thinkphp/library/think/Request.php @@ -0,0 +1,1690 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class Request +{ + /** + * @var object 对象实例 + */ + protected static $instance; + + protected $method; + /** + * @var string 域名(含协议和端口) + */ + protected $domain; + + /** + * @var string URL地址 + */ + protected $url; + + /** + * @var string 基础URL + */ + protected $baseUrl; + + /** + * @var string 当前执行的文件 + */ + protected $baseFile; + + /** + * @var string 访问的ROOT地址 + */ + protected $root; + + /** + * @var string pathinfo + */ + protected $pathinfo; + + /** + * @var string pathinfo(不含后缀) + */ + protected $path; + + /** + * @var array 当前路由信息 + */ + protected $routeInfo = []; + + /** + * @var array 环境变量 + */ + protected $env; + + /** + * @var array 当前调度信息 + */ + protected $dispatch = []; + protected $module; + protected $controller; + protected $action; + // 当前语言集 + protected $langset; + + /** + * @var array 请求参数 + */ + protected $param = []; + protected $get = []; + protected $post = []; + protected $request = []; + protected $route = []; + protected $put; + protected $session = []; + protected $file = []; + protected $cookie = []; + protected $server = []; + 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,*/*', + ]; + + protected $content; + + // 全局过滤规则 + protected $filter; + // Hook扩展方法 + protected static $hook = []; + // 绑定的属性 + protected $bind = []; + // php://input + protected $input; + // 请求缓存 + protected $cache; + // 缓存是否检查 + protected $isCheckCache; + /** + * 是否合并Param + * @var bool + */ + protected $mergeParam = false; + + /** + * 构造函数 + * @access protected + * @param array $options 参数 + */ + protected function __construct($options = []) + { + foreach ($options as $name => $item) { + if (property_exists($this, $name)) { + $this->$name = $item; + } + } + if (is_null($this->filter)) { + $this->filter = Config::get('default_filter'); + } + + // 保存 php://input + $this->input = file_get_contents('php://input'); + } + + public function __call($method, $args) + { + if (array_key_exists($method, self::$hook)) { + array_unshift($args, $this); + return call_user_func_array(self::$hook[$method], $args); + } else { + throw new Exception('method not exists:' . __CLASS__ . '->' . $method); + } + } + + /** + * Hook 方法注入 + * @access public + * @param string|array $method 方法名 + * @param mixed $callback callable + * @return void + */ + public static function hook($method, $callback = null) + { + if (is_array($method)) { + self::$hook = array_merge(self::$hook, $method); + } else { + self::$hook[$method] = $callback; + } + } + + /** + * 初始化 + * @access public + * @param array $options 参数 + * @return \think\Request + */ + public static function instance($options = []) + { + if (is_null(self::$instance)) { + self::$instance = new static($options); + } + return self::$instance; + } + + /** + * 销毁当前请求对象 + * @access public + * @return void + */ + public static function destroy() + { + if (!is_null(self::$instance)) { + self::$instance = null; + } + } + + /** + * 创建一个URL请求 + * @access public + * @param string $uri URL地址 + * @param string $method 请求类型 + * @param array $params 请求参数 + * @param array $cookie + * @param array $files + * @param array $server + * @param string $content + * @return \think\Request + */ + public static function create($uri, $method = 'GET', $params = [], $cookie = [], $files = [], $server = [], $content = null) + { + $server['PATH_INFO'] = ''; + $server['REQUEST_METHOD'] = strtoupper($method); + $info = parse_url($uri); + if (isset($info['host'])) { + $server['SERVER_NAME'] = $info['host']; + $server['HTTP_HOST'] = $info['host']; + } + if (isset($info['scheme'])) { + if ('https' === $info['scheme']) { + $server['HTTPS'] = 'on'; + $server['SERVER_PORT'] = 443; + } else { + unset($server['HTTPS']); + $server['SERVER_PORT'] = 80; + } + } + if (isset($info['port'])) { + $server['SERVER_PORT'] = $info['port']; + $server['HTTP_HOST'] = $server['HTTP_HOST'] . ':' . $info['port']; + } + if (isset($info['user'])) { + $server['PHP_AUTH_USER'] = $info['user']; + } + if (isset($info['pass'])) { + $server['PHP_AUTH_PW'] = $info['pass']; + } + if (!isset($info['path'])) { + $info['path'] = '/'; + } + $options = []; + $options[strtolower($method)] = $params; + $queryString = ''; + if (isset($info['query'])) { + parse_str(html_entity_decode($info['query']), $query); + if (!empty($params)) { + $params = array_replace($query, $params); + $queryString = http_build_query($params, '', '&'); + } else { + $params = $query; + $queryString = $info['query']; + } + } elseif (!empty($params)) { + $queryString = http_build_query($params, '', '&'); + } + if ($queryString) { + parse_str($queryString, $get); + $options['get'] = isset($options['get']) ? array_merge($get, $options['get']) : $get; + } + + $server['REQUEST_URI'] = $info['path'] . ('' !== $queryString ? '?' . $queryString : ''); + $server['QUERY_STRING'] = $queryString; + $options['cookie'] = $cookie; + $options['param'] = $params; + $options['file'] = $files; + $options['server'] = $server; + $options['url'] = $server['REQUEST_URI']; + $options['baseUrl'] = $info['path']; + $options['pathinfo'] = '/' == $info['path'] ? '/' : ltrim($info['path'], '/'); + $options['method'] = $server['REQUEST_METHOD']; + $options['domain'] = isset($info['scheme']) ? $info['scheme'] . '://' . $server['HTTP_HOST'] : ''; + $options['content'] = $content; + self::$instance = new self($options); + return self::$instance; + } + + /** + * 设置或获取当前包含协议的域名 + * @access public + * @param string $domain 域名 + * @return string + */ + public function domain($domain = null) + { + if (!is_null($domain)) { + $this->domain = $domain; + return $this; + } elseif (!$this->domain) { + $this->domain = $this->scheme() . '://' . $this->host(); + } + return $this->domain; + } + + /** + * 设置或获取当前完整URL 包括QUERY_STRING + * @access public + * @param string|true $url URL地址 true 带域名获取 + * @return string + */ + public function url($url = null) + { + if (!is_null($url) && true !== $url) { + $this->url = $url; + return $this; + } elseif (!$this->url) { + if (IS_CLI) { + $this->url = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : ''; + } elseif (isset($_SERVER['HTTP_X_REWRITE_URL'])) { + $this->url = $_SERVER['HTTP_X_REWRITE_URL']; + } elseif (isset($_SERVER['REQUEST_URI'])) { + $this->url = $_SERVER['REQUEST_URI']; + } elseif (isset($_SERVER['ORIG_PATH_INFO'])) { + $this->url = $_SERVER['ORIG_PATH_INFO'] . (!empty($_SERVER['QUERY_STRING']) ? '?' . $_SERVER['QUERY_STRING'] : ''); + } else { + $this->url = ''; + } + } + return true === $url ? $this->domain() . $this->url : $this->url; + } + + /** + * 设置或获取当前URL 不含QUERY_STRING + * @access public + * @param string $url URL地址 + * @return string + */ + public function baseUrl($url = null) + { + if (!is_null($url) && true !== $url) { + $this->baseUrl = $url; + return $this; + } elseif (!$this->baseUrl) { + $str = $this->url(); + $this->baseUrl = strpos($str, '?') ? strstr($str, '?', true) : $str; + } + return true === $url ? $this->domain() . $this->baseUrl : $this->baseUrl; + } + + /** + * 设置或获取当前执行的文件 SCRIPT_NAME + * @access public + * @param string $file 当前执行的文件 + * @return string + */ + public function baseFile($file = null) + { + if (!is_null($file) && true !== $file) { + $this->baseFile = $file; + return $this; + } elseif (!$this->baseFile) { + $url = ''; + if (!IS_CLI) { + $script_name = basename($_SERVER['SCRIPT_FILENAME']); + if (basename($_SERVER['SCRIPT_NAME']) === $script_name) { + $url = $_SERVER['SCRIPT_NAME']; + } elseif (basename($_SERVER['PHP_SELF']) === $script_name) { + $url = $_SERVER['PHP_SELF']; + } elseif (isset($_SERVER['ORIG_SCRIPT_NAME']) && basename($_SERVER['ORIG_SCRIPT_NAME']) === $script_name) { + $url = $_SERVER['ORIG_SCRIPT_NAME']; + } elseif (($pos = strpos($_SERVER['PHP_SELF'], '/' . $script_name)) !== false) { + $url = substr($_SERVER['SCRIPT_NAME'], 0, $pos) . '/' . $script_name; + } elseif (isset($_SERVER['DOCUMENT_ROOT']) && strpos($_SERVER['SCRIPT_FILENAME'], $_SERVER['DOCUMENT_ROOT']) === 0) { + $url = str_replace('\\', '/', str_replace($_SERVER['DOCUMENT_ROOT'], '', $_SERVER['SCRIPT_FILENAME'])); + } + } + $this->baseFile = $url; + } + return true === $file ? $this->domain() . $this->baseFile : $this->baseFile; + } + + /** + * 设置或获取URL访问根地址 + * @access public + * @param string $url URL地址 + * @return string + */ + public function root($url = null) + { + if (!is_null($url) && true !== $url) { + $this->root = $url; + return $this; + } elseif (!$this->root) { + $file = $this->baseFile(); + if ($file && 0 !== strpos($this->url(), $file)) { + $file = str_replace('\\', '/', dirname($file)); + } + $this->root = rtrim($file, '/'); + } + return true === $url ? $this->domain() . $this->root : $this->root; + } + + /** + * 获取当前请求URL的pathinfo信息(含URL后缀) + * @access public + * @return string + */ + public function pathinfo() + { + if (is_null($this->pathinfo)) { + if (isset($_GET[Config::get('var_pathinfo')])) { + // 判断URL里面是否有兼容模式参数 + $_SERVER['PATH_INFO'] = $_GET[Config::get('var_pathinfo')]; + unset($_GET[Config::get('var_pathinfo')]); + } elseif (IS_CLI) { + // CLI模式下 index.php module/controller/action/params/... + $_SERVER['PATH_INFO'] = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : ''; + } + + // 分析PATHINFO信息 + if (!isset($_SERVER['PATH_INFO'])) { + foreach (Config::get('pathinfo_fetch') as $type) { + if (!empty($_SERVER[$type])) { + $_SERVER['PATH_INFO'] = (0 === strpos($_SERVER[$type], $_SERVER['SCRIPT_NAME'])) ? + substr($_SERVER[$type], strlen($_SERVER['SCRIPT_NAME'])) : $_SERVER[$type]; + break; + } + } + } + $this->pathinfo = empty($_SERVER['PATH_INFO']) ? '/' : ltrim($_SERVER['PATH_INFO'], '/'); + } + return $this->pathinfo; + } + + /** + * 获取当前请求URL的pathinfo信息(不含URL后缀) + * @access public + * @return string + */ + public function path() + { + if (is_null($this->path)) { + $suffix = Config::get('url_html_suffix'); + $pathinfo = $this->pathinfo(); + if (false === $suffix) { + // 禁止伪静态访问 + $this->path = $pathinfo; + } elseif ($suffix) { + // 去除正常的URL后缀 + $this->path = preg_replace('/\.(' . ltrim($suffix, '.') . ')$/i', '', $pathinfo); + } else { + // 允许任何后缀访问 + $this->path = preg_replace('/\.' . $this->ext() . '$/i', '', $pathinfo); + } + } + return $this->path; + } + + /** + * 当前URL的访问后缀 + * @access public + * @return string + */ + public function ext() + { + return pathinfo($this->pathinfo(), PATHINFO_EXTENSION); + } + + /** + * 获取当前请求的时间 + * @access public + * @param bool $float 是否使用浮点类型 + * @return integer|float + */ + public function time($float = false) + { + return $float ? $_SERVER['REQUEST_TIME_FLOAT'] : $_SERVER['REQUEST_TIME']; + } + + /** + * 当前请求的资源类型 + * @access public + * @return false|string + */ + public function type() + { + $accept = $this->server('HTTP_ACCEPT'); + if (empty($accept)) { + return false; + } + + foreach ($this->mimeType as $key => $val) { + $array = explode(',', $val); + foreach ($array as $k => $v) { + if (stristr($accept, $v)) { + return $key; + } + } + } + return false; + } + + /** + * 设置资源类型 + * @access public + * @param string|array $type 资源类型名 + * @param string $val 资源类型 + * @return void + */ + public function mimeType($type, $val = '') + { + if (is_array($type)) { + $this->mimeType = array_merge($this->mimeType, $type); + } else { + $this->mimeType[$type] = $val; + } + } + + /** + * 当前的请求类型 + * @access public + * @param bool $method true 获取原始请求类型 + * @return string + */ + public function method($method = false) + { + if (true === $method) { + // 获取原始请求类型 + return $this->server('REQUEST_METHOD') ?: 'GET'; + } elseif (!$this->method) { + if (isset($_POST[Config::get('var_method')])) { + $method = strtoupper($_POST[Config::get('var_method')]); + if (in_array($method, ['GET', 'POST', 'DELETE', 'PUT', 'PATCH'])) { + $this->method = $method; + $this->{$this->method}($_POST); + } else { + $this->method = 'POST'; + } + unset($_POST[Config::get('var_method')]); + } elseif (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) { + $this->method = strtoupper($_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() + { + return $this->method() == 'GET'; + } + + /** + * 是否为POST请求 + * @access public + * @return bool + */ + public function isPost() + { + return $this->method() == 'POST'; + } + + /** + * 是否为PUT请求 + * @access public + * @return bool + */ + public function isPut() + { + return $this->method() == 'PUT'; + } + + /** + * 是否为DELTE请求 + * @access public + * @return bool + */ + public function isDelete() + { + return $this->method() == 'DELETE'; + } + + /** + * 是否为HEAD请求 + * @access public + * @return bool + */ + public function isHead() + { + return $this->method() == 'HEAD'; + } + + /** + * 是否为PATCH请求 + * @access public + * @return bool + */ + public function isPatch() + { + return $this->method() == 'PATCH'; + } + + /** + * 是否为OPTIONS请求 + * @access public + * @return bool + */ + public function isOptions() + { + return $this->method() == 'OPTIONS'; + } + + /** + * 是否为cli + * @access public + * @return bool + */ + public function isCli() + { + return PHP_SAPI == 'cli'; + } + + /** + * 是否为cgi + * @access public + * @return bool + */ + public function isCgi() + { + 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 (true === $name) { + // 获取包含文件上传信息的数组 + $file = $this->file(); + $data = is_array($file) ? array_merge($this->param, $file) : $this->param; + return $this->input($data, '', $default, $filter); + } + return $this->input($this->param, $name, $default, $filter); + } + + /** + * 设置获取路由参数 + * @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)) { + $this->param = []; + $this->mergeParam = false; + return $this->route = array_merge($this->route, $name); + } + 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 (empty($this->get)) { + $this->get = $_GET; + } + if (is_array($name)) { + $this->param = []; + $this->mergeParam = false; + return $this->get = array_merge($this->get, $name); + } + return $this->input($this->get, $name, $default, $filter); + } + + /** + * 设置获取POST参数 + * @access public + * @param string $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function post($name = '', $default = null, $filter = '') + { + if (empty($this->post)) { + $content = $this->input; + if (empty($_POST) && false !== strpos($this->contentType(), 'application/json')) { + $this->post = (array) json_decode($content, true); + } else { + $this->post = $_POST; + } + } + if (is_array($name)) { + $this->param = []; + $this->mergeParam = false; + return $this->post = array_merge($this->post, $name); + } + 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_null($this->put)) { + $content = $this->input; + if (false !== strpos($this->contentType(), 'application/json')) { + $this->put = (array) json_decode($content, true); + } else { + parse_str($content, $this->put); + } + } + if (is_array($name)) { + $this->param = []; + $this->mergeParam = false; + return $this->put = is_null($this->put) ? $name : array_merge($this->put, $name); + } + + return $this->input($this->put, $name, $default, $filter); + } + + /** + * 设置获取DELETE参数 + * @access public + * @param string|array $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 string|array $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function patch($name = '', $default = null, $filter = '') + { + return $this->put($name, $default, $filter); + } + + /** + * 获取request变量 + * @param string $name 数据名称 + * @param string $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function request($name = '', $default = null, $filter = '') + { + if (empty($this->request)) { + $this->request = $_REQUEST; + } + if (is_array($name)) { + $this->param = []; + $this->mergeParam = false; + return $this->request = array_merge($this->request, $name); + } + return $this->input($this->request, $name, $default, $filter); + } + + /** + * 获取session数据 + * @access public + * @param string|array $name 数据名称 + * @param string $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function session($name = '', $default = null, $filter = '') + { + if (empty($this->session)) { + $this->session = Session::get(); + } + if (is_array($name)) { + return $this->session = array_merge($this->session, $name); + } + return $this->input($this->session, $name, $default, $filter); + } + + /** + * 获取cookie参数 + * @access public + * @param string|array $name 数据名称 + * @param string $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function cookie($name = '', $default = null, $filter = '') + { + if (empty($this->cookie)) { + $this->cookie = Cookie::get(); + } + if (is_array($name)) { + return $this->cookie = array_merge($this->cookie, $name); + } elseif (!empty($name)) { + $data = Cookie::has($name) ? Cookie::get($name) : $default; + } else { + $data = $this->cookie; + } + + // 解析过滤器 + $filter = $this->getFilter($filter, $default); + + if (is_array($data)) { + array_walk_recursive($data, [$this, 'filterValue'], $filter); + reset($data); + } else { + $this->filterValue($data, $name, $filter); + } + return $data; + } + + /** + * 获取server参数 + * @access public + * @param string|array $name 数据名称 + * @param string $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function server($name = '', $default = null, $filter = '') + { + if (empty($this->server)) { + $this->server = $_SERVER; + } + if (is_array($name)) { + return $this->server = array_merge($this->server, $name); + } + return $this->input($this->server, false === $name ? false : strtoupper($name), $default, $filter); + } + + /** + * 获取上传的文件信息 + * @access public + * @param string|array $name 名称 + * @return null|array|\think\File + */ + public function file($name = '') + { + if (empty($this->file)) { + $this->file = isset($_FILES) ? $_FILES : []; + } + if (is_array($name)) { + return $this->file = array_merge($this->file, $name); + } + $files = $this->file; + if (!empty($files)) { + // 处理上传文件 + $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 (empty($file['tmp_name'][$i]) || !is_file($file['tmp_name'][$i])) { + continue; + } + $temp['key'] = $key; + foreach ($keys as $_key) { + $temp[$_key] = $file[$_key][$i]; + } + $item[] = (new File($temp['tmp_name']))->setUploadInfo($temp); + } + $array[$key] = $item; + } else { + if ($file instanceof File) { + $array[$key] = $file; + } else { + if (empty($file['tmp_name']) || !is_file($file['tmp_name'])) { + continue; + } + $array[$key] = (new File($file['tmp_name']))->setUploadInfo($file); + } + } + } + if (strpos($name, '.')) { + list($name, $sub) = explode('.', $name); + } + if ('' === $name) { + // 获取全部文件 + return $array; + } elseif (isset($sub) && isset($array[$name][$sub])) { + return $array[$name][$sub]; + } elseif (isset($array[$name])) { + return $array[$name]; + } + } + return; + } + + /** + * 获取环境变量 + * @param string|array $name 数据名称 + * @param string $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function env($name = '', $default = null, $filter = '') + { + if (empty($this->env)) { + $this->env = $_ENV; + } + if (is_array($name)) { + return $this->env = array_merge($this->env, $name); + } + return $this->input($this->env, false === $name ? false : strtoupper($name), $default, $filter); + } + + /** + * 设置或者获取当前的Header + * @access public + * @param string|array $name header名称 + * @param string $default 默认值 + * @return string + */ + public function header($name = '', $default = null) + { + if (empty($this->header)) { + $header = []; + if (function_exists('apache_request_headers') && $result = apache_request_headers()) { + $header = $result; + } else { + $server = $this->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']; + } + } + $this->header = array_change_key_case($header); + } + if (is_array($name)) { + return $this->header = array_merge($this->header, $name); + } + if ('' === $name) { + return $this->header; + } + $name = str_replace('_', '-', strtolower($name)); + return isset($this->header[$name]) ? $this->header[$name] : $default; + } + + /** + * 获取变量 支持过滤和默认值 + * @param array $data 数据源 + * @param string|false $name 字段名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤函数 + * @return mixed + */ + public function input($data = [], $name = '', $default = null, $filter = '') + { + if (false === $name) { + // 获取原始数据 + return $data; + } + $name = (string) $name; + if ('' != $name) { + // 解析name + if (strpos($name, '/')) { + list($name, $type) = explode('/', $name); + } else { + $type = 's'; + } + // 按.拆分成多维数组进行判断 + foreach (explode('.', $name) as $val) { + if (isset($data[$val])) { + $data = $data[$val]; + } else { + // 无输入数据,返回默认值 + return $default; + } + } + if (is_object($data)) { + return $data; + } + } + + // 解析过滤器 + $filter = $this->getFilter($filter, $default); + + if (is_array($data)) { + array_walk_recursive($data, [$this, 'filterValue'], $filter); + reset($data); + } else { + $this->filterValue($data, $name, $filter); + } + + if (isset($type) && $data !== $default) { + // 强制类型转换 + $this->typeCast($data, $type); + } + return $data; + } + + /** + * 设置或获取当前的过滤规则 + * @param mixed $filter 过滤规则 + * @return mixed + */ + public function filter($filter = null) + { + if (is_null($filter)) { + return $this->filter; + } else { + $this->filter = $filter; + } + } + + protected function getFilter($filter, $default) + { + 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; + } + + /** + * 递归过滤给定的值 + * @param mixed $value 键值 + * @param mixed $key 键名 + * @param array $filters 过滤方法+默认值 + * @return mixed + */ + private function filterValue(&$value, $key, $filters) + { + $default = array_pop($filters); + foreach ($filters as $filter) { + if (is_callable($filter)) { + // 调用函数或者方法过滤 + $value = call_user_func($filter, $value); + } elseif (is_scalar($value)) { + if (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 $this->filterExp($value); + } + + /** + * 过滤表单中的表达式 + * @param string $value + * @return void + */ + public function filterExp(&$value) + { + // 过滤查询特殊字符 + if (is_string($value) && preg_match('/^(EXP|NEQ|GT|EGT|LT|ELT|OR|XOR|LIKE|NOTLIKE|NOT LIKE|NOT BETWEEN|NOTBETWEEN|BETWEEN|NOT EXISTS|NOTEXISTS|EXISTS|NOT NULL|NOTNULL|NULL|BETWEEN TIME|NOT BETWEEN TIME|NOTBETWEEN TIME|NOTIN|NOT IN|IN)$/i', $value)) { + $value .= ' '; + } + // TODO 其他安全过滤 + } + + /** + * 强制类型转换 + * @param string $data + * @param string $type + * @return mixed + */ + private function typeCast(&$data, $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': + default: + if (is_scalar($data)) { + $data = (string) $data; + } else { + throw new \InvalidArgumentException('variable type error:' . gettype($data)); + } + } + } + + /** + * 是否存在某个请求参数 + * @access public + * @param string $name 变量名 + * @param string $type 变量类型 + * @param bool $checkEmpty 是否检测空值 + * @return mixed + */ + public function has($name, $type = 'param', $checkEmpty = false) + { + if (empty($this->$type)) { + $param = $this->$type(); + } else { + $param = $this->$type; + } + // 按.拆分成多维数组进行判断 + foreach (explode('.', $name) as $val) { + if (isset($param[$val])) { + $param = $param[$val]; + } else { + return false; + } + } + return ($checkEmpty && '' === $param) ? false : true; + } + + /** + * 获取指定的参数 + * @access public + * @param string|array $name 变量名 + * @param string $type 变量类型 + * @return mixed + */ + public function only($name, $type = 'param') + { + $param = $this->$type(); + if (is_string($name)) { + $name = explode(',', $name); + } + $item = []; + foreach ($name as $key) { + if (isset($param[$key])) { + $item[$key] = $param[$key]; + } + } + return $item; + } + + /** + * 排除指定参数获取 + * @access public + * @param string|array $name 变量名 + * @param string $type 变量类型 + * @return mixed + */ + public function except($name, $type = 'param') + { + $param = $this->$type(); + if (is_string($name)) { + $name = explode(',', $name); + } + foreach ($name as $key) { + if (isset($param[$key])) { + unset($param[$key]); + } + } + return $param; + } + + /** + * 当前是否ssl + * @access public + * @return bool + */ + public function isSsl() + { + $server = array_merge($_SERVER, $this->server); + if (isset($server['HTTPS']) && ('1' == $server['HTTPS'] || 'on' == strtolower($server['HTTPS']))) { + return true; + } elseif (isset($server['REQUEST_SCHEME']) && 'https' == $server['REQUEST_SCHEME']) { + return true; + } elseif (isset($server['SERVER_PORT']) && ('443' == $server['SERVER_PORT'])) { + return true; + } elseif (isset($server['HTTP_X_FORWARDED_PROTO']) && 'https' == $server['HTTP_X_FORWARDED_PROTO']) { + return true; + } elseif (Config::get('https_agent_name') && isset($server[Config::get('https_agent_name')])) { + return true; + } + return false; + } + + /** + * 当前是否Ajax请求 + * @access public + * @param bool $ajax true 获取原始ajax请求 + * @return bool + */ + public function isAjax($ajax = false) + { + $value = $this->server('HTTP_X_REQUESTED_WITH', '', 'strtolower'); + $result = ('xmlhttprequest' == $value) ? true : false; + if (true === $ajax) { + return $result; + } else { + $result = $this->param(Config::get('var_ajax')) ? true : $result; + $this->mergeParam = false; + return $result; + } + } + + /** + * 当前是否Pjax请求 + * @access public + * @param bool $pjax true 获取原始pjax请求 + * @return bool + */ + public function isPjax($pjax = false) + { + $result = !is_null($this->server('HTTP_X_PJAX')) ? true : false; + if (true === $pjax) { + return $result; + } else { + $result = $this->param(Config::get('var_pjax')) ? true : $result; + $this->mergeParam = false; + return $result; + } + } + + /** + * 获取客户端IP地址 + * @param integer $type 返回类型 0 返回IP地址 1 返回IPV4地址数字 + * @param boolean $adv 是否进行高级模式获取(有可能被伪装) + * @return mixed + */ + public function ip($type = 0, $adv = true) + { + $type = $type ? 1 : 0; + static $ip = null; + if (null !== $ip) { + return $ip[$type]; + } + + $httpAgentIp = Config::get('http_agent_ip'); + + if ($httpAgentIp && isset($_SERVER[$httpAgentIp])) { + $ip = $_SERVER[$httpAgentIp]; + } elseif ($adv) { + if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { + $arr = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']); + $pos = array_search('unknown', $arr); + if (false !== $pos) { + unset($arr[$pos]); + } + $ip = trim(current($arr)); + } elseif (isset($_SERVER['HTTP_CLIENT_IP'])) { + $ip = $_SERVER['HTTP_CLIENT_IP']; + } elseif (isset($_SERVER['REMOTE_ADDR'])) { + $ip = $_SERVER['REMOTE_ADDR']; + } + } elseif (isset($_SERVER['REMOTE_ADDR'])) { + $ip = $_SERVER['REMOTE_ADDR']; + } + // IP地址合法验证 + $long = sprintf("%u", ip2long($ip)); + $ip = $long ? [$ip, $long] : ['0.0.0.0', 0]; + return $ip[$type]; + } + + /** + * 检测是否使用手机访问 + * @access public + * @return bool + */ + public function isMobile() + { + if (isset($_SERVER['HTTP_VIA']) && stristr($_SERVER['HTTP_VIA'], "wap")) { + return true; + } elseif (isset($_SERVER['HTTP_ACCEPT']) && strpos(strtoupper($_SERVER['HTTP_ACCEPT']), "VND.WAP.WML")) { + return true; + } elseif (isset($_SERVER['HTTP_X_WAP_PROFILE']) || isset($_SERVER['HTTP_PROFILE'])) { + return true; + } elseif (isset($_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', $_SERVER['HTTP_USER_AGENT'])) { + return true; + } else { + return false; + } + } + + /** + * 当前URL地址中的scheme参数 + * @access public + * @return string + */ + public function scheme() + { + return $this->isSsl() ? 'https' : 'http'; + } + + /** + * 当前请求URL地址中的query参数 + * @access public + * @return string + */ + public function query() + { + return $this->server('QUERY_STRING'); + } + + /** + * 当前请求的host + * @access public + * @param bool $strict true 仅仅获取HOST + * @return string + */ + public function host($strict = false) + { + if (isset($_SERVER['HTTP_X_REAL_HOST'])) { + $host = $_SERVER['HTTP_X_REAL_HOST']; + } else { + $host = $this->server('HTTP_HOST'); + } + + return true === $strict && strpos($host, ':') ? strstr($host, ':', true) : $host; + } + + /** + * 当前请求URL地址中的port参数 + * @access public + * @return integer + */ + public function port() + { + return $this->server('SERVER_PORT'); + } + + /** + * 当前请求 SERVER_PROTOCOL + * @access public + * @return integer + */ + public function protocol() + { + return $this->server('SERVER_PROTOCOL'); + } + + /** + * 当前请求 REMOTE_PORT + * @access public + * @return integer + */ + public function remotePort() + { + return $this->server('REMOTE_PORT'); + } + + /** + * 当前请求 HTTP_CONTENT_TYPE + * @access public + * @return string + */ + public function contentType() + { + $contentType = $this->server('CONTENT_TYPE'); + if ($contentType) { + if (strpos($contentType, ';')) { + list($type) = explode(';', $contentType); + } else { + $type = $contentType; + } + return trim($type); + } + return ''; + } + + /** + * 获取当前请求的路由信息 + * @access public + * @param array $route 路由名称 + * @return array + */ + public function routeInfo($route = []) + { + if (!empty($route)) { + $this->routeInfo = $route; + } else { + return $this->routeInfo; + } + } + + /** + * 设置或者获取当前请求的调度信息 + * @access public + * @param array $dispatch 调度信息 + * @return array + */ + public function dispatch($dispatch = null) + { + if (!is_null($dispatch)) { + $this->dispatch = $dispatch; + } + return $this->dispatch; + } + + /** + * 设置或者获取当前的模块名 + * @access public + * @param string $module 模块名 + * @return string|Request + */ + public function module($module = null) + { + if (!is_null($module)) { + $this->module = $module; + return $this; + } else { + return $this->module ?: ''; + } + } + + /** + * 设置或者获取当前的控制器名 + * @access public + * @param string $controller 控制器名 + * @return string|Request + */ + public function controller($controller = null) + { + if (!is_null($controller)) { + $this->controller = $controller; + return $this; + } else { + return $this->controller ?: ''; + } + } + + /** + * 设置或者获取当前的操作名 + * @access public + * @param string $action 操作名 + * @return string|Request + */ + public function action($action = null) + { + if (!is_null($action) && !is_bool($action)) { + $this->action = $action; + return $this; + } else { + $name = $this->action ?: ''; + return true === $action ? $name : strtolower($name); + } + } + + /** + * 设置或者获取当前的语言 + * @access public + * @param string $lang 语言名 + * @return string|Request + */ + public function langset($lang = null) + { + if (!is_null($lang)) { + $this->langset = $lang; + return $this; + } else { + return $this->langset ?: ''; + } + } + + /** + * 设置或者获取当前请求的content + * @access public + * @return string + */ + public function getContent() + { + if (is_null($this->content)) { + $this->content = $this->input; + } + return $this->content; + } + + /** + * 获取当前请求的php://input + * @access public + * @return string + */ + public function getInput() + { + return $this->input; + } + + /** + * 生成请求令牌 + * @access public + * @param string $name 令牌名称 + * @param mixed $type 令牌生成方法 + * @return string + */ + public function token($name = '__token__', $type = 'md5') + { + $type = is_callable($type) ? $type : 'md5'; + $token = call_user_func($type, $_SERVER['REQUEST_TIME_FLOAT']); + if ($this->isAjax()) { + header($name . ': ' . $token); + } + Session::set($name, $token); + return $token; + } + + /** + * 设置当前地址的请求缓存 + * @access public + * @param string $key 缓存标识,支持变量规则 ,例如 item/:name/:id + * @param mixed $expire 缓存有效期 + * @param array $except 缓存排除 + * @param string $tag 缓存标签 + * @return void + */ + public function cache($key, $expire = null, $except = [], $tag = null) + { + if (!is_array($except)) { + $tag = $except; + $except = []; + } + + if (false !== $key && $this->isGet() && !$this->isCheckCache) { + // 标记请求缓存检查 + $this->isCheckCache = true; + if (false === $expire) { + // 关闭当前缓存 + return; + } + if ($key instanceof \Closure) { + $key = call_user_func_array($key, [$this]); + } elseif (true === $key) { + foreach ($except as $rule) { + if (0 === stripos($this->url(), $rule)) { + return; + } + } + // 自动缓存功能 + $key = '__URL__'; + } elseif (strpos($key, '|')) { + list($key, $fun) = explode('|', $key); + } + // 特殊规则替换 + if (false !== strpos($key, '__')) { + $key = str_replace(['__MODULE__', '__CONTROLLER__', '__ACTION__', '__URL__', ''], [$this->module, $this->controller, $this->action, md5($this->url(true))], $key); + } + + if (false !== strpos($key, ':')) { + $param = $this->param(); + foreach ($param as $item => $val) { + if (is_string($val) && false !== strpos($key, ':' . $item)) { + $key = str_replace(':' . $item, $val, $key); + } + } + } elseif (strpos($key, ']')) { + if ('[' . $this->ext() . ']' == $key) { + // 缓存某个后缀的请求 + $key = md5($this->url()); + } else { + return; + } + } + if (isset($fun)) { + $key = $fun($key); + } + + if (strtotime($this->server('HTTP_IF_MODIFIED_SINCE')) + $expire > $_SERVER['REQUEST_TIME']) { + // 读取缓存 + $response = Response::create()->code(304); + throw new \think\exception\HttpResponseException($response); + } elseif (Cache::has($key)) { + list($content, $header) = Cache::get($key); + $response = Response::create($content)->header($header); + throw new \think\exception\HttpResponseException($response); + } else { + $this->cache = [$key, $expire, $tag]; + } + } + } + + /** + * 读取请求缓存设置 + * @access public + * @return array + */ + public function getCache() + { + return $this->cache; + } + + /** + * 设置当前请求绑定的对象实例 + * @access public + * @param string|array $name 绑定的对象标识 + * @param mixed $obj 绑定的对象实例 + * @return mixed + */ + public function bind($name, $obj = null) + { + if (is_array($name)) { + $this->bind = array_merge($this->bind, $name); + } else { + $this->bind[$name] = $obj; + } + } + + public function __set($name, $value) + { + $this->bind[$name] = $value; + } + + public function __get($name) + { + return isset($this->bind[$name]) ? $this->bind[$name] : null; + } + + public function __isset($name) + { + return isset($this->bind[$name]); + } +} diff --git a/thinkphp/library/think/Response.php b/thinkphp/library/think/Response.php new file mode 100644 index 0000000..c5c1520 --- /dev/null +++ b/thinkphp/library/think/Response.php @@ -0,0 +1,332 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\response\Json as JsonResponse; +use think\response\Jsonp as JsonpResponse; +use think\response\Redirect as RedirectResponse; +use think\response\View as ViewResponse; +use think\response\Xml as XmlResponse; + +class Response +{ + // 原始数据 + protected $data; + + // 当前的contentType + protected $contentType = 'text/html'; + + // 字符集 + protected $charset = 'utf-8'; + + //状态 + protected $code = 200; + + // 输出参数 + protected $options = []; + // header参数 + protected $header = []; + + protected $content = null; + + /** + * 构造函数 + * @access public + * @param mixed $data 输出数据 + * @param int $code + * @param array $header + * @param array $options 输出参数 + */ + public function __construct($data = '', $code = 200, array $header = [], $options = []) + { + $this->data($data); + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + $this->contentType($this->contentType, $this->charset); + $this->header = array_merge($this->header, $header); + $this->code = $code; + } + + /** + * 创建Response对象 + * @access public + * @param mixed $data 输出数据 + * @param string $type 输出类型 + * @param int $code + * @param array $header + * @param array $options 输出参数 + * @return Response|JsonResponse|ViewResponse|XmlResponse|RedirectResponse|JsonpResponse + */ + public static function create($data = '', $type = '', $code = 200, array $header = [], $options = []) + { + $class = false !== strpos($type, '\\') ? $type : '\\think\\response\\' . ucfirst(strtolower($type)); + if (class_exists($class)) { + $response = new $class($data, $code, $header, $options); + } else { + $response = new static($data, $code, $header, $options); + } + + return $response; + } + + /** + * 发送数据到客户端 + * @access public + * @return mixed + * @throws \InvalidArgumentException + */ + public function send() + { + // 监听response_send + Hook::listen('response_send', $this); + + // 处理输出数据 + $data = $this->getContent(); + + // Trace调试注入 + if (Env::get('app_trace', Config::get('app_trace'))) { + Debug::inject($this, $data); + } + + if (200 == $this->code) { + $cache = Request::instance()->getCache(); + if ($cache) { + $this->header['Cache-Control'] = 'max-age=' . $cache[1] . ',must-revalidate'; + $this->header['Last-Modified'] = gmdate('D, d M Y H:i:s') . ' GMT'; + $this->header['Expires'] = gmdate('D, d M Y H:i:s', $_SERVER['REQUEST_TIME'] + $cache[1]) . ' GMT'; + Cache::tag($cache[2])->set($cache[0], [$data, $this->header], $cache[1]); + } + } + + if (!headers_sent() && !empty($this->header)) { + // 发送状态码 + http_response_code($this->code); + // 发送头部信息 + foreach ($this->header as $name => $val) { + if (is_null($val)) { + header($name); + } else { + header($name . ':' . $val); + } + } + } + + echo $data; + + if (function_exists('fastcgi_finish_request')) { + // 提高页面响应 + fastcgi_finish_request(); + } + + // 监听response_end + Hook::listen('response_end', $this); + + // 清空当次请求有效的数据 + if (!($this instanceof RedirectResponse)) { + Session::flush(); + } + } + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + */ + protected function output($data) + { + return $data; + } + + /** + * 输出的参数 + * @access public + * @param mixed $options 输出参数 + * @return $this + */ + public function options($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 string|array $name 参数名 + * @param string $value 参数值 + * @return $this + */ + public function header($name, $value = null) + { + if (is_array($name)) { + $this->header = array_merge($this->header, $name); + } else { + $this->header[$name] = $value; + } + return $this; + } + + /** + * 设置页面输出内容 + * @param $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状态 + * @param integer $code 状态码 + * @return $this + */ + public function code($code) + { + $this->code = $code; + return $this; + } + + /** + * LastModified + * @param string $time + * @return $this + */ + public function lastModified($time) + { + $this->header['Last-Modified'] = $time; + return $this; + } + + /** + * Expires + * @param string $time + * @return $this + */ + public function expires($time) + { + $this->header['Expires'] = $time; + return $this; + } + + /** + * ETag + * @param string $eTag + * @return $this + */ + public function eTag($eTag) + { + $this->header['ETag'] = $eTag; + return $this; + } + + /** + * 页面缓存控制 + * @param string $cache 状态码 + * @return $this + */ + public function cacheControl($cache) + { + $this->header['Cache-control'] = $cache; + return $this; + } + + /** + * 页面输出类型 + * @param string $contentType 输出类型 + * @param string $charset 输出编码 + * @return $this + */ + public function contentType($contentType, $charset = 'utf-8') + { + $this->header['Content-Type'] = $contentType . '; charset=' . $charset; + return $this; + } + + /** + * 获取头部信息 + * @param string $name 头部名称 + * @return mixed + */ + public function getHeader($name = '') + { + if (!empty($name)) { + return isset($this->header[$name]) ? $this->header[$name] : null; + } else { + return $this->header; + } + } + + /** + * 获取原始数据 + * @return mixed + */ + public function getData() + { + return $this->data; + } + + /** + * 获取输出数据 + * @return mixed + */ + public function getContent() + { + 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; + } + + /** + * 获取状态码 + * @return integer + */ + public function getCode() + { + return $this->code; + } +} diff --git a/thinkphp/library/think/Route.php b/thinkphp/library/think/Route.php new file mode 100644 index 0000000..ab53aa2 --- /dev/null +++ b/thinkphp/library/think/Route.php @@ -0,0 +1,1645 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\HttpException; + +class Route +{ + // 路由规则 + private static $rules = [ + 'get' => [], + 'post' => [], + 'put' => [], + 'delete' => [], + 'patch' => [], + 'head' => [], + 'options' => [], + '*' => [], + 'alias' => [], + 'domain' => [], + 'pattern' => [], + 'name' => [], + ]; + + // REST路由操作方法定义 + private static $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'], + ]; + + // 不同请求类型的方法前缀 + private static $methodPrefix = [ + 'get' => 'get', + 'post' => 'post', + 'put' => 'put', + 'delete' => 'delete', + 'patch' => 'patch', + ]; + + // 子域名 + private static $subDomain = ''; + // 域名绑定 + private static $bind = []; + // 当前分组信息 + private static $group = []; + // 当前子域名绑定 + private static $domainBind; + private static $domainRule; + // 当前域名 + private static $domain; + // 当前路由执行过程中的参数 + private static $option = []; + + /** + * 注册变量规则 + * @access public + * @param string|array $name 变量名 + * @param string $rule 变量规则 + * @return void + */ + public static function pattern($name = null, $rule = '') + { + if (is_array($name)) { + self::$rules['pattern'] = array_merge(self::$rules['pattern'], $name); + } else { + self::$rules['pattern'][$name] = $rule; + } + } + + /** + * 注册子域名部署规则 + * @access public + * @param string|array $domain 子域名 + * @param mixed $rule 路由规则 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public static function domain($domain, $rule = '', $option = [], $pattern = []) + { + if (is_array($domain)) { + foreach ($domain as $key => $item) { + self::domain($key, $item, $option, $pattern); + } + } elseif ($rule instanceof \Closure) { + // 执行闭包 + self::setDomain($domain); + call_user_func_array($rule, []); + self::setDomain(null); + } elseif (is_array($rule)) { + self::setDomain($domain); + self::group('', function () use ($rule) { + // 动态注册域名的路由规则 + self::registerRules($rule); + }, $option, $pattern); + self::setDomain(null); + } else { + self::$rules['domain'][$domain]['[bind]'] = [$rule, $option, $pattern]; + } + } + + private static function setDomain($domain) + { + self::$domain = $domain; + } + + /** + * 设置路由绑定 + * @access public + * @param mixed $bind 绑定信息 + * @param string $type 绑定类型 默认为module 支持 namespace class controller + * @return mixed + */ + public static function bind($bind, $type = 'module') + { + self::$bind = ['type' => $type, $type => $bind]; + } + + /** + * 设置或者获取路由标识 + * @access public + * @param string|array $name 路由命名标识 数组表示批量设置 + * @param array $value 路由地址及变量信息 + * @return array + */ + public static function name($name = '', $value = null) + { + if (is_array($name)) { + return self::$rules['name'] = $name; + } elseif ('' === $name) { + return self::$rules['name']; + } elseif (!is_null($value)) { + self::$rules['name'][strtolower($name)][] = $value; + } else { + $name = strtolower($name); + return isset(self::$rules['name'][$name]) ? self::$rules['name'][$name] : null; + } + } + + /** + * 读取路由绑定 + * @access public + * @param string $type 绑定类型 + * @return mixed + */ + public static function getBind($type) + { + return isset(self::$bind[$type]) ? self::$bind[$type] : null; + } + + /** + * 导入配置文件的路由规则 + * @access public + * @param array $rule 路由规则 + * @param string $type 请求类型 + * @return void + */ + public static function import(array $rule, $type = '*') + { + // 检查域名部署 + if (isset($rule['__domain__'])) { + self::domain($rule['__domain__']); + unset($rule['__domain__']); + } + + // 检查变量规则 + if (isset($rule['__pattern__'])) { + self::pattern($rule['__pattern__']); + unset($rule['__pattern__']); + } + + // 检查路由别名 + if (isset($rule['__alias__'])) { + self::alias($rule['__alias__']); + unset($rule['__alias__']); + } + + // 检查资源路由 + if (isset($rule['__rest__'])) { + self::resource($rule['__rest__']); + unset($rule['__rest__']); + } + + self::registerRules($rule, strtolower($type)); + } + + // 批量注册路由 + protected static function registerRules($rules, $type = '*') + { + foreach ($rules as $key => $val) { + if (is_numeric($key)) { + $key = array_shift($val); + } + if (empty($val)) { + continue; + } + if (is_string($key) && 0 === strpos($key, '[')) { + $key = substr($key, 1, -1); + self::group($key, $val); + } elseif (is_array($val)) { + self::setRule($key, $val[0], $type, $val[1], isset($val[2]) ? $val[2] : []); + } else { + self::setRule($key, $val, $type); + } + } + } + + /** + * 注册路由规则 + * @access public + * @param string|array $rule 路由规则 + * @param string $route 路由地址 + * @param string $type 请求类型 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public static function rule($rule, $route = '', $type = '*', $option = [], $pattern = []) + { + $group = self::getGroup('name'); + + if (!is_null($group)) { + // 路由分组 + $option = array_merge(self::getGroup('option'), $option); + $pattern = array_merge(self::getGroup('pattern'), $pattern); + } + + $type = strtolower($type); + + if (strpos($type, '|')) { + $option['method'] = $type; + $type = '*'; + } + if (is_array($rule) && empty($route)) { + foreach ($rule as $key => $val) { + if (is_numeric($key)) { + $key = array_shift($val); + } + if (is_array($val)) { + $route = $val[0]; + $option1 = array_merge($option, $val[1]); + $pattern1 = array_merge($pattern, isset($val[2]) ? $val[2] : []); + } else { + $option1 = null; + $pattern1 = null; + $route = $val; + } + self::setRule($key, $route, $type, !is_null($option1) ? $option1 : $option, !is_null($pattern1) ? $pattern1 : $pattern, $group); + } + } else { + self::setRule($rule, $route, $type, $option, $pattern, $group); + } + + } + + /** + * 设置路由规则 + * @access public + * @param string|array $rule 路由规则 + * @param string $route 路由地址 + * @param string $type 请求类型 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @param string $group 所属分组 + * @return void + */ + protected static function setRule($rule, $route, $type = '*', $option = [], $pattern = [], $group = '') + { + if (is_array($rule)) { + $name = $rule[0]; + $rule = $rule[1]; + } elseif (is_string($route)) { + $name = $route; + } + if (!isset($option['complete_match'])) { + if (Config::get('route_complete_match')) { + $option['complete_match'] = true; + } elseif ('$' == substr($rule, -1, 1)) { + // 是否完整匹配 + $option['complete_match'] = true; + } + } elseif (empty($option['complete_match']) && '$' == substr($rule, -1, 1)) { + // 是否完整匹配 + $option['complete_match'] = true; + } + + if ('$' == substr($rule, -1, 1)) { + $rule = substr($rule, 0, -1); + } + + if ('/' != $rule || $group) { + $rule = trim($rule, '/'); + } + $vars = self::parseVar($rule); + if (isset($name)) { + $key = $group ? $group . ($rule ? '/' . $rule : '') : $rule; + $suffix = isset($option['ext']) ? $option['ext'] : null; + self::name($name, [$key, $vars, self::$domain, $suffix]); + } + if (isset($option['modular'])) { + $route = $option['modular'] . '/' . $route; + } + if ($group) { + if ('*' != $type) { + $option['method'] = $type; + } + if (self::$domain) { + self::$rules['domain'][self::$domain]['*'][$group]['rule'][] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern]; + } else { + self::$rules['*'][$group]['rule'][] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern]; + } + } else { + if ('*' != $type && isset(self::$rules['*'][$rule])) { + unset(self::$rules['*'][$rule]); + } + if (self::$domain) { + self::$rules['domain'][self::$domain][$type][$rule] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern]; + } else { + self::$rules[$type][$rule] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern]; + } + if ('*' == $type) { + // 注册路由快捷方式 + foreach (['get', 'post', 'put', 'delete', 'patch', 'head', 'options'] as $method) { + if (self::$domain && !isset(self::$rules['domain'][self::$domain][$method][$rule])) { + self::$rules['domain'][self::$domain][$method][$rule] = true; + } elseif (!self::$domain && !isset(self::$rules[$method][$rule])) { + self::$rules[$method][$rule] = true; + } + } + } + } + } + + /** + * 设置当前执行的参数信息 + * @access public + * @param array $options 参数信息 + * @return mixed + */ + protected static function setOption($options = []) + { + self::$option[] = $options; + } + + /** + * 获取当前执行的所有参数信息 + * @access public + * @return array + */ + public static function getOption() + { + return self::$option; + } + + /** + * 获取当前的分组信息 + * @access public + * @param string $type 分组信息名称 name option pattern + * @return mixed + */ + public static function getGroup($type) + { + if (isset(self::$group[$type])) { + return self::$group[$type]; + } else { + return 'name' == $type ? null : []; + } + } + + /** + * 设置当前的路由分组 + * @access public + * @param string $name 分组名称 + * @param array $option 分组路由参数 + * @param array $pattern 分组变量规则 + * @return void + */ + public static function setGroup($name, $option = [], $pattern = []) + { + self::$group['name'] = $name; + self::$group['option'] = $option ?: []; + self::$group['pattern'] = $pattern ?: []; + } + + /** + * 注册路由分组 + * @access public + * @param string|array $name 分组名称或者参数 + * @param array|\Closure $routes 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public static function group($name, $routes, $option = [], $pattern = []) + { + if (is_array($name)) { + $option = $name; + $name = isset($option['name']) ? $option['name'] : ''; + } + // 分组 + $currentGroup = self::getGroup('name'); + if ($currentGroup) { + $name = $currentGroup . ($name ? '/' . ltrim($name, '/') : ''); + } + if (!empty($name)) { + if ($routes instanceof \Closure) { + $currentOption = self::getGroup('option'); + $currentPattern = self::getGroup('pattern'); + self::setGroup($name, array_merge($currentOption, $option), array_merge($currentPattern, $pattern)); + call_user_func_array($routes, []); + self::setGroup($currentGroup, $currentOption, $currentPattern); + if ($currentGroup != $name) { + self::$rules['*'][$name]['route'] = ''; + self::$rules['*'][$name]['var'] = self::parseVar($name); + self::$rules['*'][$name]['option'] = $option; + self::$rules['*'][$name]['pattern'] = $pattern; + } + } else { + $item = []; + $completeMatch = Config::get('route_complete_match'); + foreach ($routes as $key => $val) { + if (is_numeric($key)) { + $key = array_shift($val); + } + if (is_array($val)) { + $route = $val[0]; + $option1 = array_merge($option, isset($val[1]) ? $val[1] : []); + $pattern1 = array_merge($pattern, isset($val[2]) ? $val[2] : []); + } else { + $route = $val; + } + + $options = isset($option1) ? $option1 : $option; + $patterns = isset($pattern1) ? $pattern1 : $pattern; + if ('$' == substr($key, -1, 1)) { + // 是否完整匹配 + $options['complete_match'] = true; + $key = substr($key, 0, -1); + } elseif ($completeMatch) { + $options['complete_match'] = true; + } + $key = trim($key, '/'); + $vars = self::parseVar($key); + $item[] = ['rule' => $key, 'route' => $route, 'var' => $vars, 'option' => $options, 'pattern' => $patterns]; + // 设置路由标识 + $suffix = isset($options['ext']) ? $options['ext'] : null; + self::name($route, [$name . ($key ? '/' . $key : ''), $vars, self::$domain, $suffix]); + } + self::$rules['*'][$name] = ['rule' => $item, 'route' => '', 'var' => [], 'option' => $option, 'pattern' => $pattern]; + } + + foreach (['get', 'post', 'put', 'delete', 'patch', 'head', 'options'] as $method) { + if (!isset(self::$rules[$method][$name])) { + self::$rules[$method][$name] = true; + } elseif (is_array(self::$rules[$method][$name])) { + self::$rules[$method][$name] = array_merge(self::$rules['*'][$name], self::$rules[$method][$name]); + } + } + + } elseif ($routes instanceof \Closure) { + // 闭包注册 + $currentOption = self::getGroup('option'); + $currentPattern = self::getGroup('pattern'); + self::setGroup('', array_merge($currentOption, $option), array_merge($currentPattern, $pattern)); + call_user_func_array($routes, []); + self::setGroup($currentGroup, $currentOption, $currentPattern); + } else { + // 批量注册路由 + self::rule($routes, '', '*', $option, $pattern); + } + } + + /** + * 注册路由 + * @access public + * @param string|array $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public static function any($rule, $route = '', $option = [], $pattern = []) + { + self::rule($rule, $route, '*', $option, $pattern); + } + + /** + * 注册GET路由 + * @access public + * @param string|array $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public static function get($rule, $route = '', $option = [], $pattern = []) + { + self::rule($rule, $route, 'GET', $option, $pattern); + } + + /** + * 注册POST路由 + * @access public + * @param string|array $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public static function post($rule, $route = '', $option = [], $pattern = []) + { + self::rule($rule, $route, 'POST', $option, $pattern); + } + + /** + * 注册PUT路由 + * @access public + * @param string|array $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public static function put($rule, $route = '', $option = [], $pattern = []) + { + self::rule($rule, $route, 'PUT', $option, $pattern); + } + + /** + * 注册DELETE路由 + * @access public + * @param string|array $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public static function delete($rule, $route = '', $option = [], $pattern = []) + { + self::rule($rule, $route, 'DELETE', $option, $pattern); + } + + /** + * 注册PATCH路由 + * @access public + * @param string|array $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public static function patch($rule, $route = '', $option = [], $pattern = []) + { + self::rule($rule, $route, 'PATCH', $option, $pattern); + } + + /** + * 注册资源路由 + * @access public + * @param string|array $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public static function resource($rule, $route = '', $option = [], $pattern = []) + { + if (is_array($rule)) { + foreach ($rule as $key => $val) { + if (is_array($val)) { + list($val, $option, $pattern) = array_pad($val, 3, []); + } + self::resource($key, $val, $option, $pattern); + } + } else { + if (strpos($rule, '.')) { + // 注册嵌套资源路由 + $array = explode('.', $rule); + $last = array_pop($array); + $item = []; + foreach ($array as $val) { + $item[] = $val . '/:' . (isset($option['var'][$val]) ? $option['var'][$val] : $val . '_id'); + } + $rule = implode('/', $item) . '/' . $last; + } + // 注册资源路由 + foreach (self::$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]); + } + $item = ltrim($rule . $val[1], '/'); + $option['rest'] = $key; + self::rule($item . '$', $route . '/' . $val[2], $val[0], $option, $pattern); + } + } + } + + /** + * 注册控制器路由 操作方法对应不同的请求后缀 + * @access public + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public static function controller($rule, $route = '', $option = [], $pattern = []) + { + foreach (self::$methodPrefix as $type => $val) { + self::$type($rule . '/:action', $route . '/' . $val . ':action', $option, $pattern); + } + } + + /** + * 注册别名路由 + * @access public + * @param string|array $rule 路由别名 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @return void + */ + public static function alias($rule = null, $route = '', $option = []) + { + if (is_array($rule)) { + self::$rules['alias'] = array_merge(self::$rules['alias'], $rule); + } else { + self::$rules['alias'][$rule] = $option ? [$route, $option] : $route; + } + } + + /** + * 设置不同请求类型下面的方法前缀 + * @access public + * @param string $method 请求类型 + * @param string $prefix 类型前缀 + * @return void + */ + public static function setMethodPrefix($method, $prefix = '') + { + if (is_array($method)) { + self::$methodPrefix = array_merge(self::$methodPrefix, array_change_key_case($method)); + } else { + self::$methodPrefix[strtolower($method)] = $prefix; + } + } + + /** + * rest方法定义和修改 + * @access public + * @param string|array $name 方法名称 + * @param array|bool $resource 资源 + * @return void + */ + public static function rest($name, $resource = []) + { + if (is_array($name)) { + self::$rest = $resource ? $name : array_merge(self::$rest, $name); + } else { + self::$rest[$name] = $resource; + } + } + + /** + * 注册未匹配路由规则后的处理 + * @access public + * @param string $route 路由地址 + * @param string $method 请求类型 + * @param array $option 路由参数 + * @return void + */ + public static function miss($route, $method = '*', $option = []) + { + self::rule('__miss__', $route, $method, $option, []); + } + + /** + * 注册一个自动解析的URL路由 + * @access public + * @param string $route 路由地址 + * @return void + */ + public static function auto($route) + { + self::rule('__auto__', $route, '*', [], []); + } + + /** + * 获取或者批量设置路由定义 + * @access public + * @param mixed $rules 请求类型或者路由定义数组 + * @return array + */ + public static function rules($rules = '') + { + if (is_array($rules)) { + self::$rules = $rules; + } elseif ($rules) { + return true === $rules ? self::$rules : self::$rules[strtolower($rules)]; + } else { + $rules = self::$rules; + unset($rules['pattern'], $rules['alias'], $rules['domain'], $rules['name']); + return $rules; + } + } + + /** + * 检测子域名部署 + * @access public + * @param Request $request Request请求对象 + * @param array $currentRules 当前路由规则 + * @param string $method 请求类型 + * @return void + */ + public static function checkDomain($request, &$currentRules, $method = 'get') + { + // 域名规则 + $rules = self::$rules['domain']; + // 开启子域名部署 支持二级和三级域名 + if (!empty($rules)) { + $host = $request->host(true); + if (isset($rules[$host])) { + // 完整域名或者IP配置 + $item = $rules[$host]; + } else { + $rootDomain = Config::get('url_domain_root'); + if ($rootDomain) { + // 配置域名根 例如 thinkphp.cn 163.com.cn 如果是国家级域名 com.cn net.cn 之类的域名需要配置 + $domain = explode('.', rtrim(stristr($host, $rootDomain, true), '.')); + } else { + $domain = explode('.', $host, -2); + } + // 子域名配置 + if (!empty($domain)) { + // 当前子域名 + $subDomain = implode('.', $domain); + self::$subDomain = $subDomain; + $domain2 = array_pop($domain); + if ($domain) { + // 存在三级域名 + $domain3 = array_pop($domain); + } + if ($subDomain && isset($rules[$subDomain])) { + // 子域名配置 + $item = $rules[$subDomain]; + } elseif (isset($rules['*.' . $domain2]) && !empty($domain3)) { + // 泛三级域名 + $item = $rules['*.' . $domain2]; + $panDomain = $domain3; + } elseif (isset($rules['*']) && !empty($domain2)) { + // 泛二级域名 + if ('www' != $domain2) { + $item = $rules['*']; + $panDomain = $domain2; + } + } + } + } + if (!empty($item)) { + if (isset($panDomain)) { + // 保存当前泛域名 + $request->route(['__domain__' => $panDomain]); + } + if (isset($item['[bind]'])) { + // 解析子域名部署规则 + list($rule, $option, $pattern) = $item['[bind]']; + if (!empty($option['https']) && !$request->isSsl()) { + // https检测 + throw new HttpException(404, 'must use https request:' . $host); + } + + if (strpos($rule, '?')) { + // 传入其它参数 + $array = parse_url($rule); + $result = $array['path']; + parse_str($array['query'], $params); + if (isset($panDomain)) { + $pos = array_search('*', $params); + if (false !== $pos) { + // 泛域名作为参数 + $params[$pos] = $panDomain; + } + } + $_GET = array_merge($_GET, $params); + } else { + $result = $rule; + } + + if (0 === strpos($result, '\\')) { + // 绑定到命名空间 例如 \app\index\behavior + self::$bind = ['type' => 'namespace', 'namespace' => $result]; + } elseif (0 === strpos($result, '@')) { + // 绑定到类 例如 @app\index\controller\User + self::$bind = ['type' => 'class', 'class' => substr($result, 1)]; + } else { + // 绑定到模块/控制器 例如 index/user + self::$bind = ['type' => 'module', 'module' => $result]; + } + self::$domainBind = true; + } else { + self::$domainRule = $item; + $currentRules = isset($item[$method]) ? $item[$method] : $item['*']; + } + } + } + } + + /** + * 检测URL路由 + * @access public + * @param Request $request Request请求对象 + * @param string $url URL地址 + * @param string $depr URL分隔符 + * @param bool $checkDomain 是否检测域名规则 + * @return false|array + */ + public static function check($request, $url, $depr = '/', $checkDomain = false) + { + //检查解析缓存 + if (!App::$debug && Config::get('route_check_cache')) { + $key = self::getCheckCacheKey($request); + if (Cache::has($key)) { + list($rule, $route, $pathinfo, $option, $matches) = Cache::get($key); + return self::parseRule($rule, $route, $pathinfo, $option, $matches, true); + } + } + + // 分隔符替换 确保路由定义使用统一的分隔符 + $url = str_replace($depr, '|', $url); + + if (isset(self::$rules['alias'][$url]) || isset(self::$rules['alias'][strstr($url, '|', true)])) { + // 检测路由别名 + $result = self::checkRouteAlias($request, $url, $depr); + if (false !== $result) { + return $result; + } + } + $method = strtolower($request->method()); + // 获取当前请求类型的路由规则 + $rules = isset(self::$rules[$method]) ? self::$rules[$method] : []; + // 检测域名部署 + if ($checkDomain) { + self::checkDomain($request, $rules, $method); + } + // 检测URL绑定 + $return = self::checkUrlBind($url, $rules, $depr); + if (false !== $return) { + return $return; + } + if ('|' != $url) { + $url = rtrim($url, '|'); + } + $item = str_replace('|', '/', $url); + if (isset($rules[$item])) { + // 静态路由规则检测 + $rule = $rules[$item]; + if (true === $rule) { + $rule = self::getRouteExpress($item); + } + if (!empty($rule['route']) && self::checkOption($rule['option'], $request)) { + self::setOption($rule['option']); + return self::parseRule($item, $rule['route'], $url, $rule['option']); + } + } + + // 路由规则检测 + if (!empty($rules)) { + return self::checkRoute($request, $rules, $url, $depr); + } + return false; + } + + private static function getRouteExpress($key) + { + return self::$domainRule ? self::$domainRule['*'][$key] : self::$rules['*'][$key]; + } + + /** + * 检测路由规则 + * @access private + * @param Request $request + * @param array $rules 路由规则 + * @param string $url URL地址 + * @param string $depr URL分割符 + * @param string $group 路由分组名 + * @param array $options 路由参数(分组) + * @return mixed + */ + private static function checkRoute($request, $rules, $url, $depr = '/', $group = '', $options = []) + { + foreach ($rules as $key => $item) { + if (true === $item) { + $item = self::getRouteExpress($key); + } + if (!isset($item['rule'])) { + continue; + } + $rule = $item['rule']; + $route = $item['route']; + $vars = $item['var']; + $option = $item['option']; + $pattern = $item['pattern']; + + // 检查参数有效性 + if (!self::checkOption($option, $request)) { + continue; + } + + if (isset($option['ext'])) { + // 路由ext参数 优先于系统配置的URL伪静态后缀参数 + $url = preg_replace('/\.' . $request->ext() . '$/i', '', $url); + } + + if (is_array($rule)) { + // 分组路由 + $pos = strpos(str_replace('<', ':', $key), ':'); + if (false !== $pos) { + $str = substr($key, 0, $pos); + } else { + $str = $key; + } + if (is_string($str) && $str && 0 !== stripos(str_replace('|', '/', $url), $str)) { + continue; + } + self::setOption($option); + $result = self::checkRoute($request, $rule, $url, $depr, $key, $option); + if (false !== $result) { + return $result; + } + } elseif ($route) { + if ('__miss__' == $rule || '__auto__' == $rule) { + // 指定特殊路由 + $var = trim($rule, '__'); + ${$var} = $item; + continue; + } + if ($group) { + $rule = $group . ($rule ? '/' . ltrim($rule, '/') : ''); + } + + self::setOption($option); + if (isset($options['bind_model']) && isset($option['bind_model'])) { + $option['bind_model'] = array_merge($options['bind_model'], $option['bind_model']); + } + $result = self::checkRule($rule, $route, $url, $pattern, $option, $depr); + if (false !== $result) { + return $result; + } + } + } + if (isset($auto)) { + // 自动解析URL地址 + return self::parseUrl($auto['route'] . '/' . $url, $depr); + } elseif (isset($miss)) { + // 未匹配所有路由的路由规则处理 + return self::parseRule('', $miss['route'], $url, $miss['option']); + } + return false; + } + + /** + * 检测路由别名 + * @access private + * @param Request $request + * @param string $url URL地址 + * @param string $depr URL分隔符 + * @return mixed + */ + private static function checkRouteAlias($request, $url, $depr) + { + $array = explode('|', $url); + $alias = array_shift($array); + $item = self::$rules['alias'][$alias]; + + if (is_array($item)) { + list($rule, $option) = $item; + $action = $array[0]; + if (isset($option['allow']) && !in_array($action, explode(',', $option['allow']))) { + // 允许操作 + return false; + } elseif (isset($option['except']) && in_array($action, explode(',', $option['except']))) { + // 排除操作 + return false; + } + if (isset($option['method'][$action])) { + $option['method'] = $option['method'][$action]; + } + } else { + $rule = $item; + } + $bind = implode('|', $array); + // 参数有效性检查 + if (isset($option) && !self::checkOption($option, $request)) { + // 路由不匹配 + return false; + } elseif (0 === strpos($rule, '\\')) { + // 路由到类 + return self::bindToClass($bind, substr($rule, 1), $depr); + } elseif (0 === strpos($rule, '@')) { + // 路由到控制器类 + return self::bindToController($bind, substr($rule, 1), $depr); + } else { + // 路由到模块/控制器 + return self::bindToModule($bind, $rule, $depr); + } + } + + /** + * 检测URL绑定 + * @access private + * @param string $url URL地址 + * @param array $rules 路由规则 + * @param string $depr URL分隔符 + * @return mixed + */ + private static function checkUrlBind(&$url, &$rules, $depr = '/') + { + if (!empty(self::$bind)) { + $type = self::$bind['type']; + $bind = self::$bind[$type]; + // 记录绑定信息 + App::$debug && Log::record('[ BIND ] ' . var_export($bind, true), 'info'); + // 如果有URL绑定 则进行绑定检测 + switch ($type) { + case 'class': + // 绑定到类 + return self::bindToClass($url, $bind, $depr); + case 'controller': + // 绑定到控制器类 + return self::bindToController($url, $bind, $depr); + case 'namespace': + // 绑定到命名空间 + return self::bindToNamespace($url, $bind, $depr); + } + } + return false; + } + + /** + * 绑定到类 + * @access public + * @param string $url URL地址 + * @param string $class 类名(带命名空间) + * @param string $depr URL分隔符 + * @return array + */ + public static function bindToClass($url, $class, $depr = '/') + { + $url = str_replace($depr, '|', $url); + $array = explode('|', $url, 2); + $action = !empty($array[0]) ? $array[0] : Config::get('default_action'); + if (!empty($array[1])) { + self::parseUrlParams($array[1]); + } + return ['type' => 'method', 'method' => [$class, $action], 'var' => []]; + } + + /** + * 绑定到命名空间 + * @access public + * @param string $url URL地址 + * @param string $namespace 命名空间 + * @param string $depr URL分隔符 + * @return array + */ + public static function bindToNamespace($url, $namespace, $depr = '/') + { + $url = str_replace($depr, '|', $url); + $array = explode('|', $url, 3); + $class = !empty($array[0]) ? $array[0] : Config::get('default_controller'); + $method = !empty($array[1]) ? $array[1] : Config::get('default_action'); + if (!empty($array[2])) { + self::parseUrlParams($array[2]); + } + return ['type' => 'method', 'method' => [$namespace . '\\' . Loader::parseName($class, 1), $method], 'var' => []]; + } + + /** + * 绑定到控制器类 + * @access public + * @param string $url URL地址 + * @param string $controller 控制器名 (支持带模块名 index/user ) + * @param string $depr URL分隔符 + * @return array + */ + public static function bindToController($url, $controller, $depr = '/') + { + $url = str_replace($depr, '|', $url); + $array = explode('|', $url, 2); + $action = !empty($array[0]) ? $array[0] : Config::get('default_action'); + if (!empty($array[1])) { + self::parseUrlParams($array[1]); + } + return ['type' => 'controller', 'controller' => $controller . '/' . $action, 'var' => []]; + } + + /** + * 绑定到模块/控制器 + * @access public + * @param string $url URL地址 + * @param string $controller 控制器类名(带命名空间) + * @param string $depr URL分隔符 + * @return array + */ + public static function bindToModule($url, $controller, $depr = '/') + { + $url = str_replace($depr, '|', $url); + $array = explode('|', $url, 2); + $action = !empty($array[0]) ? $array[0] : Config::get('default_action'); + if (!empty($array[1])) { + self::parseUrlParams($array[1]); + } + return ['type' => 'module', 'module' => $controller . '/' . $action]; + } + + /** + * 路由参数有效性检查 + * @access private + * @param array $option 路由参数 + * @param Request $request Request对象 + * @return bool + */ + private static function checkOption($option, $request) + { + if ((isset($option['method']) && is_string($option['method']) && false === stripos($option['method'], $request->method())) + || (isset($option['ajax']) && $option['ajax'] && !$request->isAjax()) // Ajax检测 + || (isset($option['ajax']) && !$option['ajax'] && $request->isAjax()) // 非Ajax检测 + || (isset($option['pjax']) && $option['pjax'] && !$request->isPjax()) // Pjax检测 + || (isset($option['pjax']) && !$option['pjax'] && $request->isPjax()) // 非Pjax检测 + || (isset($option['ext']) && false === stripos('|' . $option['ext'] . '|', '|' . $request->ext() . '|')) // 伪静态后缀检测 + || (isset($option['deny_ext']) && false !== stripos('|' . $option['deny_ext'] . '|', '|' . $request->ext() . '|')) + || (isset($option['domain']) && !in_array($option['domain'], [$_SERVER['HTTP_HOST'], self::$subDomain])) // 域名检测 + || (isset($option['https']) && $option['https'] && !$request->isSsl()) // https检测 + || (isset($option['https']) && !$option['https'] && $request->isSsl()) // https检测 + || (!empty($option['before_behavior']) && false === Hook::exec($option['before_behavior'])) // 行为检测 + || (!empty($option['callback']) && is_callable($option['callback']) && false === call_user_func($option['callback'])) // 自定义检测 + ) { + return false; + } + return true; + } + + /** + * 检测路由规则 + * @access private + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @param string $url URL地址 + * @param array $pattern 变量规则 + * @param array $option 路由参数 + * @param string $depr URL分隔符(全局) + * @return array|false + */ + private static function checkRule($rule, $route, $url, $pattern, $option, $depr) + { + // 检查完整规则定义 + if (isset($pattern['__url__']) && !preg_match(0 === strpos($pattern['__url__'], '/') ? $pattern['__url__'] : '/^' . $pattern['__url__'] . '/', str_replace('|', $depr, $url))) { + return false; + } + // 检查路由的参数分隔符 + if (isset($option['param_depr'])) { + $url = str_replace(['|', $option['param_depr']], [$depr, '|'], $url); + } + + $len1 = substr_count($url, '|'); + $len2 = substr_count($rule, '/'); + // 多余参数是否合并 + $merge = !empty($option['merge_extra_vars']); + if ($merge && $len1 > $len2) { + $url = str_replace('|', $depr, $url); + $url = implode('|', explode($depr, $url, $len2 + 1)); + } + + if ($len1 >= $len2 || strpos($rule, '[')) { + if (!empty($option['complete_match'])) { + // 完整匹配 + if (!$merge && $len1 != $len2 && (false === strpos($rule, '[') || $len1 > $len2 || $len1 < $len2 - substr_count($rule, '['))) { + return false; + } + } + $pattern = array_merge(self::$rules['pattern'], $pattern); + if (false !== $match = self::match($url, $rule, $pattern)) { + // 匹配到路由规则 + return self::parseRule($rule, $route, $url, $option, $match); + } + } + return false; + } + + /** + * 解析模块的URL地址 [模块/控制器/操作?]参数1=值1&参数2=值2... + * @access public + * @param string $url URL地址 + * @param string $depr URL分隔符 + * @param bool $autoSearch 是否自动深度搜索控制器 + * @return array + */ + public static function parseUrl($url, $depr = '/', $autoSearch = false) + { + + if (isset(self::$bind['module'])) { + $bind = str_replace('/', $depr, self::$bind['module']); + // 如果有模块/控制器绑定 + $url = $bind . ('.' != substr($bind, -1) ? $depr : '') . ltrim($url, $depr); + } + $url = str_replace($depr, '|', $url); + list($path, $var) = self::parseUrlPath($url); + $route = [null, null, null]; + if (isset($path)) { + // 解析模块 + $module = Config::get('app_multi_module') ? array_shift($path) : null; + if ($autoSearch) { + // 自动搜索控制器 + $dir = APP_PATH . ($module ? $module . DS : '') . Config::get('url_controller_layer'); + $suffix = App::$suffix || Config::get('controller_suffix') ? ucfirst(Config::get('url_controller_layer')) : ''; + $item = []; + $find = false; + foreach ($path as $val) { + $item[] = $val; + $file = $dir . DS . str_replace('.', DS, $val) . $suffix . EXT; + $file = pathinfo($file, PATHINFO_DIRNAME) . DS . Loader::parseName(pathinfo($file, PATHINFO_FILENAME), 1) . EXT; + if (is_file($file)) { + $find = true; + break; + } else { + $dir .= DS . Loader::parseName($val); + } + } + if ($find) { + $controller = implode('.', $item); + $path = array_slice($path, count($item)); + } else { + $controller = array_shift($path); + } + } else { + // 解析控制器 + $controller = !empty($path) ? array_shift($path) : null; + } + // 解析操作 + $action = !empty($path) ? array_shift($path) : null; + // 解析额外参数 + self::parseUrlParams(empty($path) ? '' : implode('|', $path)); + // 封装路由 + $route = [$module, $controller, $action]; + // 检查地址是否被定义过路由 + $name = strtolower($module . '/' . Loader::parseName($controller, 1) . '/' . $action); + $name2 = ''; + if (empty($module) || isset($bind) && $module == $bind) { + $name2 = strtolower(Loader::parseName($controller, 1) . '/' . $action); + } + + if (isset(self::$rules['name'][$name]) || isset(self::$rules['name'][$name2])) { + throw new HttpException(404, 'invalid request:' . str_replace('|', $depr, $url)); + } + } + return ['type' => 'module', 'module' => $route]; + } + + /** + * 解析URL的pathinfo参数和变量 + * @access private + * @param string $url URL地址 + * @return array + */ + private static function parseUrlPath($url) + { + // 分隔符替换 确保路由定义使用统一的分隔符 + $url = str_replace('|', '/', $url); + $url = trim($url, '/'); + $var = []; + if (false !== strpos($url, '?')) { + // [模块/控制器/操作?]参数1=值1&参数2=值2... + $info = parse_url($url); + $path = explode('/', $info['path']); + parse_str($info['query'], $var); + } elseif (strpos($url, '/')) { + // [模块/控制器/操作] + $path = explode('/', $url); + } else { + $path = [$url]; + } + return [$path, $var]; + } + + /** + * 检测URL和规则路由是否匹配 + * @access private + * @param string $url URL地址 + * @param string $rule 路由规则 + * @param array $pattern 变量规则 + * @return array|false + */ + private static function match($url, $rule, $pattern) + { + $m2 = explode('/', $rule); + $m1 = explode('|', $url); + + $var = []; + foreach ($m2 as $key => $val) { + // val中定义了多个变量 + if (false !== strpos($val, '<') && preg_match_all('/<(\w+(\??))>/', $val, $matches)) { + $value = []; + $replace = []; + foreach ($matches[1] as $name) { + if (strpos($name, '?')) { + $name = substr($name, 0, -1); + $replace[] = '(' . (isset($pattern[$name]) ? $pattern[$name] : '\w+') . ')?'; + } else { + $replace[] = '(' . (isset($pattern[$name]) ? $pattern[$name] : '\w+') . ')'; + } + $value[] = $name; + } + $val = str_replace($matches[0], $replace, $val); + if (preg_match('/^' . $val . '$/', isset($m1[$key]) ? $m1[$key] : '', $match)) { + array_shift($match); + foreach ($value as $k => $name) { + if (isset($match[$k])) { + $var[$name] = $match[$k]; + } + } + continue; + } else { + return false; + } + } + + if (0 === strpos($val, '[:')) { + // 可选参数 + $val = substr($val, 1, -1); + $optional = true; + } else { + $optional = false; + } + if (0 === strpos($val, ':')) { + // URL变量 + $name = substr($val, 1); + if (!$optional && !isset($m1[$key])) { + return false; + } + if (isset($m1[$key]) && isset($pattern[$name])) { + // 检查变量规则 + if ($pattern[$name] instanceof \Closure) { + $result = call_user_func_array($pattern[$name], [$m1[$key]]); + if (false === $result) { + return false; + } + } elseif (!preg_match(0 === strpos($pattern[$name], '/') ? $pattern[$name] : '/^' . $pattern[$name] . '$/', $m1[$key])) { + return false; + } + } + $var[$name] = isset($m1[$key]) ? $m1[$key] : ''; + } elseif (!isset($m1[$key]) || 0 !== strcasecmp($val, $m1[$key])) { + return false; + } + } + // 成功匹配后返回URL中的动态变量数组 + return $var; + } + + /** + * 解析规则路由 + * @access private + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @param string $pathinfo URL地址 + * @param array $option 路由参数 + * @param array $matches 匹配的变量 + * @param bool $fromCache 通过缓存解析 + * @return array + */ + private static function parseRule($rule, $route, $pathinfo, $option = [], $matches = [], $fromCache = false) + { + $request = Request::instance(); + + //保存解析缓存 + if (Config::get('route_check_cache') && !$fromCache) { + try { + $key = self::getCheckCacheKey($request); + Cache::tag('route_check')->set($key, [$rule, $route, $pathinfo, $option, $matches]); + } catch (\Exception $e) { + + } + } + + // 解析路由规则 + if ($rule) { + $rule = explode('/', $rule); + // 获取URL地址中的参数 + $paths = explode('|', $pathinfo); + foreach ($rule as $item) { + $fun = ''; + if (0 === strpos($item, '[:')) { + $item = substr($item, 1, -1); + } + if (0 === strpos($item, ':')) { + $var = substr($item, 1); + $matches[$var] = array_shift($paths); + } else { + // 过滤URL中的静态变量 + array_shift($paths); + } + } + } else { + $paths = explode('|', $pathinfo); + } + + // 获取路由地址规则 + if (is_string($route) && isset($option['prefix'])) { + // 路由地址前缀 + $route = $option['prefix'] . $route; + } + // 替换路由地址中的变量 + if (is_string($route) && !empty($matches)) { + foreach ($matches as $key => $val) { + if (false !== strpos($route, ':' . $key)) { + $route = str_replace(':' . $key, $val, $route); + } + } + } + + // 绑定模型数据 + if (isset($option['bind_model'])) { + $bind = []; + foreach ($option['bind_model'] as $key => $val) { + if ($val instanceof \Closure) { + $result = call_user_func_array($val, [$matches]); + } else { + if (is_array($val)) { + $fields = explode('&', $val[1]); + $model = $val[0]; + $exception = isset($val[2]) ? $val[2] : true; + } else { + $fields = ['id']; + $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) { + $query = strpos($model, '\\') ? $model::where($where) : Loader::model($model)->where($where); + $result = $query->failException($exception)->find(); + } + } + if (!empty($result)) { + $bind[$key] = $result; + } + } + $request->bind($bind); + } + + if (!empty($option['response'])) { + Hook::add('response_send', $option['response']); + } + + // 解析额外参数 + self::parseUrlParams(empty($paths) ? '' : implode('|', $paths), $matches); + // 记录匹配的路由信息 + $request->routeInfo(['rule' => $rule, 'route' => $route, 'option' => $option, 'var' => $matches]); + + // 检测路由after行为 + if (!empty($option['after_behavior'])) { + if ($option['after_behavior'] instanceof \Closure) { + $result = call_user_func_array($option['after_behavior'], []); + } else { + foreach ((array) $option['after_behavior'] as $behavior) { + $result = Hook::exec($behavior, ''); + if (!is_null($result)) { + break; + } + } + } + // 路由规则重定向 + if ($result instanceof Response) { + return ['type' => 'response', 'response' => $result]; + } elseif (is_array($result)) { + return $result; + } + } + + if ($route instanceof \Closure) { + // 执行闭包 + $result = ['type' => 'function', 'function' => $route]; + } elseif (0 === strpos($route, '/') || strpos($route, '://')) { + // 路由到重定向地址 + $result = ['type' => 'redirect', 'url' => $route, 'status' => isset($option['status']) ? $option['status'] : 301]; + } elseif (false !== strpos($route, '\\')) { + // 路由到方法 + list($path, $var) = self::parseUrlPath($route); + $route = str_replace('/', '@', implode('/', $path)); + $method = strpos($route, '@') ? explode('@', $route) : $route; + $result = ['type' => 'method', 'method' => $method, 'var' => $var]; + } elseif (0 === strpos($route, '@')) { + // 路由到控制器 + $route = substr($route, 1); + list($route, $var) = self::parseUrlPath($route); + $result = ['type' => 'controller', 'controller' => implode('/', $route), 'var' => $var]; + $request->action(array_pop($route)); + $request->controller($route ? array_pop($route) : Config::get('default_controller')); + $request->module($route ? array_pop($route) : Config::get('default_module')); + App::$modulePath = APP_PATH . (Config::get('app_multi_module') ? $request->module() . DS : ''); + } else { + // 路由到模块/控制器/操作 + $result = self::parseModule($route, isset($option['convert']) ? $option['convert'] : false); + } + // 开启请求缓存 + if ($request->isGet() && isset($option['cache'])) { + $cache = $option['cache']; + if (is_array($cache)) { + list($key, $expire, $tag) = array_pad($cache, 3, null); + } else { + $key = str_replace('|', '/', $pathinfo); + $expire = $cache; + $tag = null; + } + $request->cache($key, $expire, $tag); + } + return $result; + } + + /** + * 解析URL地址为 模块/控制器/操作 + * @access private + * @param string $url URL地址 + * @param bool $convert 是否自动转换URL地址 + * @return array + */ + private static function parseModule($url, $convert = false) + { + list($path, $var) = self::parseUrlPath($url); + $action = array_pop($path); + $controller = !empty($path) ? array_pop($path) : null; + $module = Config::get('app_multi_module') && !empty($path) ? array_pop($path) : null; + $method = Request::instance()->method(); + if (Config::get('use_action_prefix') && !empty(self::$methodPrefix[$method])) { + // 操作方法前缀支持 + $action = 0 !== strpos($action, self::$methodPrefix[$method]) ? self::$methodPrefix[$method] . $action : $action; + } + // 设置当前请求的路由变量 + Request::instance()->route($var); + // 路由到模块/控制器/操作 + return ['type' => 'module', 'module' => [$module, $controller, $action], 'convert' => $convert]; + } + + /** + * 解析URL地址中的参数Request对象 + * @access private + * @param string $url 路由规则 + * @param array $var 变量 + * @return void + */ + private static function parseUrlParams($url, &$var = []) + { + if ($url) { + if (Config::get('url_param_type')) { + $var += explode('|', $url); + } else { + preg_replace_callback('/(\w+)\|([^\|]+)/', function ($match) use (&$var) { + $var[$match[1]] = strip_tags($match[2]); + }, $url); + } + } + // 设置当前请求的参数 + Request::instance()->route($var); + } + + // 分析路由规则中的变量 + private static function parseVar($rule) + { + // 提取路由规则中的变量 + $var = []; + foreach (explode('/', $rule) as $val) { + $optional = false; + if (false !== strpos($val, '<') && preg_match_all('/<(\w+(\??))>/', $val, $matches)) { + foreach ($matches[1] as $name) { + if (strpos($name, '?')) { + $name = substr($name, 0, -1); + $optional = true; + } else { + $optional = false; + } + $var[$name] = $optional ? 2 : 1; + } + } + + if (0 === strpos($val, '[:')) { + // 可选参数 + $optional = true; + $val = substr($val, 1, -1); + } + if (0 === strpos($val, ':')) { + // URL变量 + $name = substr($val, 1); + $var[$name] = $optional ? 2 : 1; + } + } + return $var; + } + + /** + * 获取路由解析缓存的key + * @param Request $request + * @return string + */ + private static function getCheckCacheKey(Request $request) + { + static $key; + + if (empty($key)) { + if ($callback = Config::get('route_check_cache_key')) { + $key = call_user_func($callback, $request); + } else { + $key = "{$request->host(true)}|{$request->method()}|{$request->path()}"; + } + } + + return $key; + } +} diff --git a/thinkphp/library/think/Session.php b/thinkphp/library/think/Session.php new file mode 100644 index 0000000..61150bc --- /dev/null +++ b/thinkphp/library/think/Session.php @@ -0,0 +1,366 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\ClassNotFoundException; + +class Session +{ + protected static $prefix = ''; + protected static $init = null; + + /** + * 设置或者获取session作用域(前缀) + * @param string $prefix + * @return string|void + */ + public static function prefix($prefix = '') + { + empty(self::$init) && self::boot(); + if (empty($prefix) && null !== $prefix) { + return self::$prefix; + } else { + self::$prefix = $prefix; + } + } + + /** + * session初始化 + * @param array $config + * @return void + * @throws \think\Exception + */ + public static function init(array $config = []) + { + if (empty($config)) { + $config = Config::get('session'); + } + // 记录初始化信息 + App::$debug && Log::record('[ SESSION ] INIT ' . var_export($config, true), 'info'); + $isDoStart = false; + if (isset($config['use_trans_sid'])) { + ini_set('session.use_trans_sid', $config['use_trans_sid'] ? 1 : 0); + } + + // 启动session + if (!empty($config['auto_start']) && PHP_SESSION_ACTIVE != session_status()) { + ini_set('session.auto_start', 0); + $isDoStart = true; + } + + if (isset($config['prefix']) && ('' === self::$prefix || null === self::$prefix)) { + self::$prefix = $config['prefix']; + } + if (isset($config['var_session_id']) && isset($_REQUEST[$config['var_session_id']])) { + session_id($_REQUEST[$config['var_session_id']]); + } elseif (isset($config['id']) && !empty($config['id'])) { + session_id($config['id']); + } + if (isset($config['name'])) { + session_name($config['name']); + } + if (isset($config['path'])) { + session_save_path($config['path']); + } + if (isset($config['domain'])) { + ini_set('session.cookie_domain', $config['domain']); + } + if (isset($config['expire'])) { + ini_set('session.gc_maxlifetime', $config['expire']); + ini_set('session.cookie_lifetime', $config['expire']); + } + if (isset($config['secure'])) { + ini_set('session.cookie_secure', $config['secure']); + } + if (isset($config['httponly'])) { + ini_set('session.cookie_httponly', $config['httponly']); + } + if (isset($config['use_cookies'])) { + ini_set('session.use_cookies', $config['use_cookies'] ? 1 : 0); + } + if (isset($config['cache_limiter'])) { + session_cache_limiter($config['cache_limiter']); + } + if (isset($config['cache_expire'])) { + session_cache_expire($config['cache_expire']); + } + if (!empty($config['type'])) { + // 读取session驱动 + $class = false !== strpos($config['type'], '\\') ? $config['type'] : '\\think\\session\\driver\\' . ucwords($config['type']); + + // 检查驱动类 + if (!class_exists($class) || !session_set_save_handler(new $class($config))) { + throw new ClassNotFoundException('error session handler:' . $class, $class); + } + } + if ($isDoStart) { + session_start(); + self::$init = true; + } else { + self::$init = false; + } + } + + /** + * session自动启动或者初始化 + * @return void + */ + public static function boot() + { + if (is_null(self::$init)) { + self::init(); + } elseif (false === self::$init) { + if (PHP_SESSION_ACTIVE != session_status()) { + session_start(); + } + self::$init = true; + } + } + + /** + * session设置 + * @param string $name session名称 + * @param mixed $value session值 + * @param string|null $prefix 作用域(前缀) + * @return void + */ + public static function set($name, $value = '', $prefix = null) + { + empty(self::$init) && self::boot(); + + $prefix = !is_null($prefix) ? $prefix : self::$prefix; + if (strpos($name, '.')) { + // 二维数组赋值 + list($name1, $name2) = explode('.', $name); + if ($prefix) { + $_SESSION[$prefix][$name1][$name2] = $value; + } else { + $_SESSION[$name1][$name2] = $value; + } + } elseif ($prefix) { + $_SESSION[$prefix][$name] = $value; + } else { + $_SESSION[$name] = $value; + } + } + + /** + * session获取 + * @param string $name session名称 + * @param string|null $prefix 作用域(前缀) + * @return mixed + */ + public static function get($name = '', $prefix = null) + { + empty(self::$init) && self::boot(); + $prefix = !is_null($prefix) ? $prefix : self::$prefix; + if ('' == $name) { + // 获取全部的session + $value = $prefix ? (!empty($_SESSION[$prefix]) ? $_SESSION[$prefix] : []) : $_SESSION; + } elseif ($prefix) { + // 获取session + if (strpos($name, '.')) { + list($name1, $name2) = explode('.', $name); + $value = isset($_SESSION[$prefix][$name1][$name2]) ? $_SESSION[$prefix][$name1][$name2] : null; + } else { + $value = isset($_SESSION[$prefix][$name]) ? $_SESSION[$prefix][$name] : null; + } + } else { + if (strpos($name, '.')) { + list($name1, $name2) = explode('.', $name); + $value = isset($_SESSION[$name1][$name2]) ? $_SESSION[$name1][$name2] : null; + } else { + $value = isset($_SESSION[$name]) ? $_SESSION[$name] : null; + } + } + return $value; + } + + /** + * session获取并删除 + * @param string $name session名称 + * @param string|null $prefix 作用域(前缀) + * @return mixed + */ + public static function pull($name, $prefix = null) + { + $result = self::get($name, $prefix); + if ($result) { + self::delete($name, $prefix); + return $result; + } else { + return; + } + } + + /** + * session设置 下一次请求有效 + * @param string $name session名称 + * @param mixed $value session值 + * @param string|null $prefix 作用域(前缀) + * @return void + */ + public static function flash($name, $value) + { + self::set($name, $value); + if (!self::has('__flash__.__time__')) { + self::set('__flash__.__time__', $_SERVER['REQUEST_TIME_FLOAT']); + } + self::push('__flash__', $name); + } + + /** + * 清空当前请求的session数据 + * @return void + */ + public static function flush() + { + if (self::$init) { + $item = self::get('__flash__'); + + if (!empty($item)) { + $time = $item['__time__']; + if ($_SERVER['REQUEST_TIME_FLOAT'] > $time) { + unset($item['__time__']); + self::delete($item); + self::set('__flash__', []); + } + } + } + } + + /** + * 删除session数据 + * @param string|array $name session名称 + * @param string|null $prefix 作用域(前缀) + * @return void + */ + public static function delete($name, $prefix = null) + { + empty(self::$init) && self::boot(); + $prefix = !is_null($prefix) ? $prefix : self::$prefix; + if (is_array($name)) { + foreach ($name as $key) { + self::delete($key, $prefix); + } + } elseif (strpos($name, '.')) { + list($name1, $name2) = explode('.', $name); + if ($prefix) { + unset($_SESSION[$prefix][$name1][$name2]); + } else { + unset($_SESSION[$name1][$name2]); + } + } else { + if ($prefix) { + unset($_SESSION[$prefix][$name]); + } else { + unset($_SESSION[$name]); + } + } + } + + /** + * 清空session数据 + * @param string|null $prefix 作用域(前缀) + * @return void + */ + public static function clear($prefix = null) + { + empty(self::$init) && self::boot(); + $prefix = !is_null($prefix) ? $prefix : self::$prefix; + if ($prefix) { + unset($_SESSION[$prefix]); + } else { + $_SESSION = []; + } + } + + /** + * 判断session数据 + * @param string $name session名称 + * @param string|null $prefix + * @return bool + */ + public static function has($name, $prefix = null) + { + empty(self::$init) && self::boot(); + $prefix = !is_null($prefix) ? $prefix : self::$prefix; + if (strpos($name, '.')) { + // 支持数组 + list($name1, $name2) = explode('.', $name); + return $prefix ? isset($_SESSION[$prefix][$name1][$name2]) : isset($_SESSION[$name1][$name2]); + } else { + return $prefix ? isset($_SESSION[$prefix][$name]) : isset($_SESSION[$name]); + } + } + + /** + * 添加数据到一个session数组 + * @param string $key + * @param mixed $value + * @return void + */ + public static function push($key, $value) + { + $array = self::get($key); + if (is_null($array)) { + $array = []; + } + $array[] = $value; + self::set($key, $array); + } + + /** + * 启动session + * @return void + */ + public static function start() + { + session_start(); + self::$init = true; + } + + /** + * 销毁session + * @return void + */ + public static function destroy() + { + if (!empty($_SESSION)) { + $_SESSION = []; + } + session_unset(); + session_destroy(); + self::$init = null; + } + + /** + * 重新生成session_id + * @param bool $delete 是否删除关联会话文件 + * @return void + */ + public static function regenerate($delete = false) + { + session_regenerate_id($delete); + } + + /** + * 暂停session + * @return void + */ + public static function pause() + { + // 暂停session + session_write_close(); + self::$init = false; + } +} diff --git a/thinkphp/library/think/Template.php b/thinkphp/library/think/Template.php new file mode 100644 index 0000000..9ba0ff3 --- /dev/null +++ b/thinkphp/library/think/Template.php @@ -0,0 +1,1139 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\TemplateNotFoundException; +use think\template\TagLib; + +/** + * ThinkPHP分离出来的模板引擎 + * 支持XML标签和普通标签的模板解析 + * 编译型模板引擎 支持动态缓存 + */ +class Template +{ + // 模板变量 + protected $data = []; + // 引擎配置 + protected $config = [ + 'view_path' => '', // 模板路径 + 'view_base' => '', + 'view_suffix' => 'html', // 默认模板文件后缀 + 'view_depr' => DS, + '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|'', 为空时自动识别 + ]; + + private $literal = []; + private $includeFile = []; // 记录所有模板包含的文件路径及更新时间 + protected $storage; + + /** + * 构造函数 + * @access public + * @param array $config + */ + public function __construct(array $config = []) + { + $this->config['cache_path'] = TEMP_PATH; + $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 mixed $name + * @param mixed $value + * @return void + */ + public function assign($name, $value = '') + { + if (is_array($name)) { + $this->data = array_merge($this->data, $name); + } else { + $this->data[$name] = $value; + } + } + + /** + * 模板引擎参数赋值 + * @access public + * @param mixed $name + * @param mixed $value + */ + public function __set($name, $value) + { + $this->config[$name] = $value; + } + + /** + * 模板引擎配置项 + * @access public + * @param array|string $config + * @return string|void|array + */ + public function config($config) + { + if (is_array($config)) { + $this->config = array_merge($this->config, $config); + } elseif (isset($this->config[$config])) { + return $this->config[$config]; + } else { + return; + } + } + + /** + * 模板变量获取 + * @access public + * @param string $name 变量名 + * @return mixed + */ + public function get($name = '') + { + if ('' == $name) { + return $this->data; + } else { + $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 $template 模板文件 + * @param array $vars 模板变量 + * @param array $config 模板参数 + * @return void + */ + public function fetch($template, $vars = [], $config = []) + { + if ($vars) { + $this->data = $vars; + } + if ($config) { + $this->config($config); + } + if (!empty($this->config['cache_id']) && $this->config['display_cache']) { + // 读取渲染缓存 + $cacheContent = Cache::get($this->config['cache_id']); + if (false !== $cacheContent) { + echo $cacheContent; + return; + } + } + $template = $this->parseTemplateFile($template); + if ($template) { + $cacheFile = $this->config['cache_path'] . $this->config['cache_prefix'] . md5($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']) { + // 缓存页面输出 + Cache::set($this->config['cache_id'], $content, $this->config['cache_time']); + } + echo $content; + } + } + + /** + * 渲染模板内容 + * @access public + * @param string $content 模板内容 + * @param array $vars 模板变量 + * @param array $config 模板参数 + * @return void + */ + public function display($content, $vars = [], $config = []) + { + if ($vars) { + $this->data = $vars; + } + if ($config) { + $this->config($config); + } + $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 Template + */ + public function layout($name, $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 boolean + */ + private function checkCache($cacheFile) + { + // 未开启缓存功能 + if (!$this->config['tpl_cache']) { + return false; + } + // 缓存文件不存在 + if (!is_file($cacheFile)) { + return false; + } + // 读取缓存文件失败 + if (!$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 public + * @param string $cacheId 缓存的id + * @return boolean + */ + public function isCache($cacheId) + { + if ($cacheId && $this->config['display_cache']) { + // 缓存页面输出 + return Cache::has($cacheId); + } + return false; + } + + /** + * 编译模板文件内容 + * @access private + * @param string $content 模板内容 + * @param string $cacheFile 缓存文件名 + * @return void + */ + private function compiler(&$content, $cacheFile) + { + // 判断是否启用布局 + 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)/s', '', $content); + // 模板过滤输出 + $replace = $this->config['tpl_replace_string']; + $content = str_replace(array_keys($replace), array_values($replace), $content); + // 添加安全代码及模板引用记录 + $content = 'includeFile) . '*/ ?>' . "\n" . $content; + // 编译存储 + $this->storage->write($cacheFile, $content); + $this->includeFile = []; + return; + } + + /** + * 模板解析入口 + * 支持普通标签和TagLib解析 支持自定义标签库 + * @access public + * @param string $content 要解析的模板内容 + * @return void + */ + public function parse(&$content) + { + // 内容为空不解析 + 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_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); + return; + } + + /** + * 检查PHP语法 + * @access private + * @param string $content 要解析的模板内容 + * @return void + * @throws \think\Exception + */ + private function parsePhp(&$content) + { + // 短标签的情况要将' . "\n", $content); + // PHP语法检查 + if ($this->config['tpl_deny_php'] && false !== strpos($content, '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); + } + return; + } + + /** + * 解析模板中的include标签 + * @access private + * @param string $content 要解析的模板内容 + * @return void + */ + private function parseInclude(&$content) + { + $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); + return; + } + + /** + * 解析模板中的extend标签 + * @access private + * @param string $content 要解析的模板内容 + * @return void + */ + private function parseExtend(&$content) + { + $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); + } + return; + } + + /** + * 替换页面中的literal标签 + * @access private + * @param string $content 模板内容 + * @param boolean $restore 是否为还原 + * @return void + */ + private function parseLiteral(&$content, $restore = false) + { + $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], "", $content); + $count++; + } + } else { + // 还原literal标签 + foreach ($matches as $match) { + $content = str_replace($match[0], $this->literal[$match[1]], $content); + } + // 清空literal记录 + $this->literal = []; + } + unset($matches); + } + return; + } + + /** + * 获取模板中的block标签 + * @access private + * @param string $content 模板内容 + * @param boolean $sort 是否排序 + * @return array + */ + private function parseBlock(&$content, $sort = false) + { + $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(&$content) + { + // 搜索是否有TagLib标签 + if (preg_match($this->getRegex('taglib'), $content, $matches)) { + // 替换TagLib标签 + $content = str_replace($matches[0], '', $content); + return explode(',', $matches['name']); + } + return; + } + + /** + * TagLib库解析 + * @access public + * @param string $tagLib 要解析的标签库 + * @param string $content 要解析的模板内容 + * @param boolean $hide 是否隐藏标签库前缀 + * @return void + */ + public function parseTagLib($tagLib, &$content, $hide = false) + { + if (false !== strpos($tagLib, '\\')) { + // 支持指定标签库的命名空间 + $className = $tagLib; + $tagLib = substr($tagLib, strrpos($tagLib, '\\') + 1); + } else { + $className = '\\think\\template\\taglib\\' . ucwords($tagLib); + } + /** @var Taglib $tLib */ + $tLib = new $className($this); + $tLib->parseTag($content, $hide ? '' : $tagLib); + return; + } + + /** + * 分析标签属性 + * @access public + * @param string $str 属性字符串 + * @param string $name 不为空时返回指定的属性名 + * @return array + */ + public function parseAttr($str, $name = null) + { + $regex = '/\s+(?>(?P[\w-]+)\s*)=(?>\s*)([\"\'])(?P(?:(?!\\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]; + } else { + return $array; + } + } + + /** + * 模板标签解析 + * 格式: {TagName:args [|content] } + * @access private + * @param string $content 要解析的模板内容 + * @return void + */ + private function parseTag(&$content) + { + $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 '?': + $str = ''; + break; + case '=': + $str = ''; + break; + default: + $str = ''; + } + } else { + if (isset($array[1])) { + $this->parseVar($array[2]); + $express = $name . $array[1] . $array[2]; + } else { + $express = false; + } + // $name为数组 + switch ($first) { + case '?': + // {$varname??'xxx'} $varname有定义则输出$varname,否则输出xxx + $str = ''; + break; + case '=': + // {$varname?='xxx'} $varname为真时才输出xxx + $str = ''; + break; + case ':': + // {$varname?:'xxx'} $varname为真时输出$varname,否则输出xxx + $str = ''; + break; + default: + $str = ''; + } + } + } else { + $this->parseVar($str); + $this->parseVarFunction($str); + $str = ''; + } + break; + case ':': + // 输出某个函数的结果 + $str = substr($str, 1); + $this->parseVar($str); + $str = ''; + break; + case '~': + // 执行某个函数 + $str = substr($str, 1); + $this->parseVar($str); + $str = ''; + break; + case '-': + case '+': + // 输出计算 + $this->parseVar($str); + $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); + } + return; + } + + /** + * 模板变量解析,支持使用函数 + * 格式: {$varname|function1|function2=arg1,arg2} + * @access public + * @param string $varStr 变量数据 + * @return void + */ + public function parseVar(&$varStr) + { + $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 ('$Think' == $first) { + // 所有以Think.打头的以特殊变量对待 无需模板赋值就可以输出 + $parseStr = $this->parseThinkVar($vars); + } elseif ('$Request' == $first) { + // 获取Request请求对象参数 + $method = array_shift($vars); + if (!empty($vars)) { + $params = implode('.', $vars); + if ('true' != $params) { + $params = '\'' . $params . '\''; + } + } else { + $params = ''; + } + $parseStr = '\think\Request::instance()->' . $method . '(' . $params . ')'; + } 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); + } + return; + } + + /** + * 对模板中使用了函数的变量进行解析 + * 格式 {$varname|function1|function2=arg1,arg2} + * @access public + * @param string $varStr 变量字符串 + * @return void + */ + public function parseVarFunction(&$varStr) + { + if (false == strpos($varStr, '|')) { + return; + } + static $_varFunctionList = []; + $_key = md5($varStr); + //如果已经解析过该变量字串,则直接返回变量值 + if (isset($_varFunctionList[$_key])) { + $varStr = $_varFunctionList[$_key]; + } else { + $varArray = explode('|', $varStr); + // 取得变量名称 + $name = 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]); + switch ($fun) { + case 'default': // 特殊模板函数 + if (false === strpos($name, '(')) { + $name = '(isset(' . $name . ') && (' . $name . ' !== \'\')?' . $name . ':' . $args[1] . ')'; + } else { + $name = '(' . $name . ' ?: ' . $args[1] . ')'; + } + break; + default: // 通用模板函数 + if (!in_array($fun, $template_deny_funs)) { + 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; + } + + /** + * 特殊模板变量解析 + * 格式 以 $Think. 打头的变量属于特殊模板变量 + * @access public + * @param array $vars 变量数组 + * @return string + */ + public function parseThinkVar($vars) + { + $type = strtoupper(trim(array_shift($vars))); + $param = implode('.', $vars); + if ($vars) { + switch ($type) { + case 'SERVER': + $parseStr = '\\think\\Request::instance()->server(\'' . $param . '\')'; + break; + case 'GET': + $parseStr = '\\think\\Request::instance()->get(\'' . $param . '\')'; + break; + case 'POST': + $parseStr = '\\think\\Request::instance()->post(\'' . $param . '\')'; + break; + case 'COOKIE': + $parseStr = '\\think\\Cookie::get(\'' . $param . '\')'; + break; + case 'SESSION': + $parseStr = '\\think\\Session::get(\'' . $param . '\')'; + break; + case 'ENV': + $parseStr = '\\think\\Request::instance()->env(\'' . $param . '\')'; + break; + case 'REQUEST': + $parseStr = '\\think\\Request::instance()->request(\'' . $param . '\')'; + break; + case 'CONST': + $parseStr = strtoupper($param); + break; + case 'LANG': + $parseStr = '\\think\\Lang::get(\'' . $param . '\')'; + break; + case 'CONFIG': + $parseStr = '\\think\\Config::get(\'' . $param . '\')'; + break; + default: + $parseStr = '\'\''; + break; + } + } else { + switch ($type) { + case 'NOW': + $parseStr = "date('Y-m-d g:i a',time())"; + break; + case 'VERSION': + $parseStr = 'THINK_VERSION'; + break; + case 'LDELIM': + $parseStr = '\'' . ltrim($this->config['tpl_begin'], '\\') . '\''; + break; + case 'RDELIM': + $parseStr = '\'' . ltrim($this->config['tpl_end'], '\\') . '\''; + break; + default: + if (defined($type)) { + $parseStr = $type; + } else { + $parseStr = ''; + } + } + } + return $parseStr; + } + + /** + * 分析加载的模板文件并读取内容 支持多个模板文件读取 + * @access private + * @param string $templateName 模板文件名 + * @return string + */ + private function parseTemplateName($templateName) + { + $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|false + */ + private function parseTemplateFile($template) + { + if ('' == pathinfo($template, PATHINFO_EXTENSION)) { + if (strpos($template, '@')) { + list($module, $template) = explode('@', $template); + } + if (0 !== strpos($template, '/')) { + $template = str_replace(['/', ':'], $this->config['view_depr'], $template); + } else { + $template = str_replace(['/', ':'], $this->config['view_depr'], substr($template, 1)); + } + if ($this->config['view_base']) { + $module = isset($module) ? $module : Request::instance()->module(); + $path = $this->config['view_base'] . ($module ? $module . DS : ''); + } else { + $path = isset($module) ? APP_PATH . $module . DS . basename($this->config['view_path']) . DS : $this->config['view_path']; + } + $template = realpath($path . $template . '.' . ltrim($this->config['view_suffix'], '.')); + } + + if (is_file($template)) { + // 记录模板文件的更新时间 + $this->includeFile[$template] = filemtime($template); + return $template; + } else { + throw new TemplateNotFoundException('template not exists:' . $template, $template); + } + } + + /** + * 按标签生成正则 + * @access private + * @param string $tagName 标签名 + * @return string + */ + private function getRegex($tagName) + { + $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(?>(?:(?!name=).)*)\bname=([\'\"])(?P[\$\w\-\/\.]+)\\1(?>[^' . $end . ']*)|\/' . $tagName . ')' . $end; + } else { + $regex = $begin . '(?:' . $tagName . '\b(?>(?:(?!name=).)*)\bname=([\'\"])(?P[\$\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 = ''; + break; + case 'include': + $name = 'file'; + case 'taglib': + case 'layout': + case 'extend': + if (empty($name)) { + $name = 'name'; + } + if ($single) { + $regex = $begin . $tagName . '\b(?>(?:(?!' . $name . '=).)*)\b' . $name . '=([\'\"])(?P[\$\w\-\/\.\:@,\\\\]+)\\1(?>[^' . $end . ']*)' . $end; + } else { + $regex = $begin . $tagName . '\b(?>(?:(?!' . $name . '=).)*)\b' . $name . '=([\'\"])(?P[\$\w\-\/\.\:@,\\\\]+)\\1(?>(?:(?!' . $end . ').)*)' . $end; + } + break; + } + } + return '/' . $regex . '/is'; + } +} diff --git a/thinkphp/library/think/Url.php b/thinkphp/library/think/Url.php new file mode 100644 index 0000000..53a545f --- /dev/null +++ b/thinkphp/library/think/Url.php @@ -0,0 +1,333 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class Url +{ + // 生成URL地址的root + protected static $root; + protected static $bindCheck; + + /** + * URL生成 支持路由反射 + * @param string $url 路由地址 + * @param string|array $vars 参数(支持数组和字符串)a=val&b=val2... ['a'=>'val1', 'b'=>'val2'] + * @param string|bool $suffix 伪静态后缀,默认为true表示获取配置值 + * @param boolean|string $domain 是否显示域名 或者直接传入域名 + * @return string + */ + public static function build($url = '', $vars = '', $suffix = true, $domain = false) + { + if (false === $domain && Route::rules('domain')) { + $domain = true; + } + // 解析URL + 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 (is_string($vars)) { + // aaa=1&bbb=2 转换成数组 + parse_str($vars, $vars); + } + + if ($url) { + $rule = Route::name(isset($name) ? $name : $url . (isset($info['query']) ? '?' . $info['query'] : '')); + if (is_null($rule) && isset($info['query'])) { + $rule = Route::name($url); + // 解析地址里面参数 合并到vars + parse_str($info['query'], $params); + $vars = array_merge($params, $vars); + unset($info['query']); + } + } + if (!empty($rule) && $match = self::getRuleUrl($rule, $vars)) { + // 匹配路由命名标识 + $url = $match[0]; + // 替换可选分隔符 + $url = preg_replace(['/(\W)\?$/', '/(\W)\?/'], ['', '\1'], $url); + if (!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 { + // 检查别名路由 + $alias = Route::rules('alias'); + $matchAlias = false; + if ($alias) { + // 别名路由解析 + foreach ($alias as $key => $val) { + if (is_array($val)) { + $val = $val[0]; + } + if (0 === strpos($url, $val)) { + $url = $key . substr($url, strlen($val)); + $matchAlias = true; + break; + } + } + } + if (!$matchAlias) { + // 路由标识不存在 直接解析 + $url = self::parseUrl($url, $domain); + } + if (isset($info['query'])) { + // 解析地址里面参数 合并到vars + parse_str($info['query'], $params); + $vars = array_merge($params, $vars); + } + } + + // 检测URL绑定 + if (!self::$bindCheck) { + $type = Route::getBind('type'); + if ($type) { + $bind = Route::getBind($type); + if ($bind && 0 === strpos($url, $bind)) { + $url = substr($url, strlen($bind) + 1); + } + } + } + // 还原URL分隔符 + $depr = Config::get('pathinfo_depr'); + $url = str_replace('/', $depr, $url); + + // URL后缀 + $suffix = in_array($url, ['/', '']) ? '' : self::parseSuffix($suffix); + // 锚点 + $anchor = !empty($anchor) ? '#' . $anchor : ''; + // 参数组装 + if (!empty($vars)) { + // 添加参数 + if (Config::get('url_common_param')) { + $vars = http_build_query($vars); + $url .= $suffix . '?' . $vars . $anchor; + } else { + $paramType = Config::get('url_param_type'); + foreach ($vars as $var => $val) { + if ('' !== trim($val)) { + if ($paramType) { + $url .= $depr . urlencode($val); + } else { + $url .= $depr . $var . $depr . urlencode($val); + } + } + } + $url .= $suffix . $anchor; + } + } else { + $url .= $suffix . $anchor; + } + // 检测域名 + $domain = self::parseDomain($url, $domain); + // URL组装 + $url = $domain . rtrim(self::$root ?: Request::instance()->root(), '/') . '/' . ltrim($url, '/'); + + self::$bindCheck = false; + return $url; + } + + // 直接解析URL地址 + protected static function parseUrl($url, &$domain) + { + $request = Request::instance(); + 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); + } else { + // 解析到 模块/控制器/操作 + $module = $request->module(); + $domains = Route::rules('domain'); + if (true === $domain && 2 == substr_count($url, '/')) { + $current = $request->host(); + $match = []; + $pos = []; + foreach ($domains as $key => $item) { + if (isset($item['[bind]']) && 0 === strpos($url, $item['[bind]'][0])) { + $pos[$key] = strlen($item['[bind]'][0]) + 1; + $match[] = $key; + $module = ''; + } + } + if ($match) { + $domain = current($match); + foreach ($match as $item) { + if (0 === strpos($current, $item)) { + $domain = $item; + } + } + self::$bindCheck = true; + $url = substr($url, $pos[$domain]); + } + } elseif ($domain) { + if (isset($domains[$domain]['[bind]'][0])) { + $bindModule = $domains[$domain]['[bind]'][0]; + if ($bindModule && !in_array($bindModule[0], ['\\', '@'])) { + $module = ''; + } + } + } + $module = $module ? $module . '/' : ''; + + $controller = $request->controller(); + if ('' == $url) { + // 空字符串输出当前的 模块/控制器/操作 + $action = $request->action(); + } else { + $path = explode('/', $url); + $action = array_pop($path); + $controller = empty($path) ? $controller : array_pop($path); + $module = empty($path) ? $module : array_pop($path) . '/'; + } + if (Config::get('url_convert')) { + $action = strtolower($action); + $controller = Loader::parseName($controller); + } + $url = $module . $controller . '/' . $action; + } + return $url; + } + + // 检测域名 + protected static function parseDomain(&$url, $domain) + { + if (!$domain) { + return ''; + } + $request = Request::instance(); + $rootDomain = Config::get('url_domain_root'); + if (true === $domain) { + // 自动判断域名 + $domain = Config::get('app_host') ?: $request->host(); + + $domains = Route::rules('domain'); + if ($domains) { + $route_domain = array_keys($domains); + foreach ($route_domain as $domain_prefix) { + if (0 === strpos($domain_prefix, '*.') && strpos($domain, ltrim($domain_prefix, '*.')) !== 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; + } + } + } + } + } + + } else { + if (empty($rootDomain)) { + $host = Config::get('app_host') ?: $request->host(); + $rootDomain = substr_count($host, '.') > 1 ? substr(strstr($host, '.'), 1) : $host; + } + if (substr_count($domain, '.') < 2 && !strpos($domain, $rootDomain)) { + $domain .= '.' . $rootDomain; + } + } + if (false !== strpos($domain, '://')) { + $scheme = ''; + } else { + $scheme = $request->isSsl() || Config::get('is_https') ? 'https://' : 'http://'; + } + return $scheme . $domain; + } + + // 解析URL后缀 + protected static function parseSuffix($suffix) + { + if ($suffix) { + $suffix = true === $suffix ? Config::get('url_html_suffix') : $suffix; + if ($pos = strpos($suffix, '|')) { + $suffix = substr($suffix, 0, $pos); + } + } + return (empty($suffix) || 0 === strpos($suffix, '.')) ? $suffix : '.' . $suffix; + } + + // 匹配路由地址 + public static function getRuleUrl($rule, &$vars = []) + { + foreach ($rule as $item) { + list($url, $pattern, $domain, $suffix) = $item; + if (empty($pattern)) { + return [rtrim($url, '$'), $domain, $suffix]; + } + $type = Config::get('url_common_param'); + foreach ($pattern as $key => $val) { + if (isset($vars[$key])) { + $url = str_replace(['[:' . $key . ']', '<' . $key . '?>', ':' . $key . '', '<' . $key . '>'], $type ? $vars[$key] : urlencode($vars[$key]), $url); + unset($vars[$key]); + $result = [$url, $domain, $suffix]; + } elseif (2 == $val) { + $url = str_replace(['/[:' . $key . ']', '[:' . $key . ']', '<' . $key . '?>'], '', $url); + $result = [$url, $domain, $suffix]; + } else { + break; + } + } + if (isset($result)) { + return $result; + } + } + return false; + } + + // 指定当前生成URL地址的root + public static function root($root) + { + self::$root = $root; + Request::instance()->root($root); + } +} diff --git a/thinkphp/library/think/Validate.php b/thinkphp/library/think/Validate.php new file mode 100644 index 0000000..608e1e4 --- /dev/null +++ b/thinkphp/library/think/Validate.php @@ -0,0 +1,1371 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\ClassNotFoundException; + +class Validate +{ + // 实例 + protected static $instance; + + // 自定义的验证类型 + protected static $type = []; + + // 验证类型别名 + protected $alias = [ + '>' => 'gt', '>=' => 'egt', '<' => 'lt', '<=' => 'elt', '=' => 'eq', 'same' => 'eq', + ]; + + // 当前验证的规则 + protected $rule = []; + + // 验证提示信息 + protected $message = []; + // 验证字段描述 + protected $field = []; + + // 验证规则默认提示信息 + protected static $typeMsg = [ + 'require' => ':attribute require', + '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', + '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', + 'afterWith' => ':attribute cannot be less than :rule', + 'beforeWith' => ':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', + ]; + + // 当前验证场景 + protected $currentScene = null; + + // 正则表达式 regex = ['zip'=>'\d{6}',...] + protected $regex = []; + + // 验证场景 scene = ['edit'=>'name1,name2,...'] + protected $scene = []; + + // 验证失败错误信息 + protected $error = []; + + // 批量验证 + protected $batch = false; + + /** + * 构造函数 + * @access public + * @param array $rules 验证规则 + * @param array $message 验证提示信息 + * @param array $field 验证字段描述信息 + */ + public function __construct(array $rules = [], $message = [], $field = []) + { + $this->rule = array_merge($this->rule, $rules); + $this->message = array_merge($this->message, $message); + $this->field = array_merge($this->field, $field); + } + + /** + * 实例化验证 + * @access public + * @param array $rules 验证规则 + * @param array $message 验证提示信息 + * @param array $field 验证字段描述信息 + * @return Validate + */ + public static function make($rules = [], $message = [], $field = []) + { + if (is_null(self::$instance)) { + self::$instance = new self($rules, $message, $field); + } + return self::$instance; + } + + /** + * 添加字段验证规则 + * @access protected + * @param string|array $name 字段名称或者规则数组 + * @param mixed $rule 验证规则 + * @return Validate + */ + public function rule($name, $rule = '') + { + if (is_array($name)) { + $this->rule = array_merge($this->rule, $name); + } else { + $this->rule[$name] = $rule; + } + return $this; + } + + /** + * 注册验证(类型)规则 + * @access public + * @param string $type 验证规则类型 + * @param mixed $callback callback方法(或闭包) + * @return void + */ + public static function extend($type, $callback = null) + { + if (is_array($type)) { + self::$type = array_merge(self::$type, $type); + } else { + self::$type[$type] = $callback; + } + } + + /** + * 设置验证规则的默认提示信息 + * @access protected + * @param string|array $type 验证规则类型名称或者数组 + * @param string $msg 验证提示信息 + * @return void + */ + public static function setTypeMsg($type, $msg = null) + { + if (is_array($type)) { + self::$typeMsg = array_merge(self::$typeMsg, $type); + } else { + self::$typeMsg[$type] = $msg; + } + } + + /** + * 设置提示信息 + * @access public + * @param string|array $name 字段名称 + * @param string $message 提示信息 + * @return Validate + */ + public function message($name, $message = '') + { + if (is_array($name)) { + $this->message = array_merge($this->message, $name); + } else { + $this->message[$name] = $message; + } + return $this; + } + + /** + * 设置验证场景 + * @access public + * @param string|array $name 场景名或者场景设置数组 + * @param mixed $fields 要验证的字段 + * @return Validate + */ + public function scene($name, $fields = null) + { + if (is_array($name)) { + $this->scene = array_merge($this->scene, $name); + }if (is_null($fields)) { + // 设置当前场景 + $this->currentScene = $name; + } else { + // 设置验证场景 + $this->scene[$name] = $fields; + } + return $this; + } + + /** + * 判断是否存在某个验证场景 + * @access public + * @param string $name 场景名 + * @return bool + */ + public function hasScene($name) + { + return isset($this->scene[$name]); + } + + /** + * 设置批量验证 + * @access public + * @param bool $batch 是否批量验证 + * @return Validate + */ + public function batch($batch = true) + { + $this->batch = $batch; + return $this; + } + + /** + * 数据自动验证 + * @access public + * @param array $data 数据 + * @param mixed $rules 验证规则 + * @param string $scene 验证场景 + * @return bool + */ + public function check($data, $rules = [], $scene = '') + { + $this->error = []; + + if (empty($rules)) { + // 读取验证规则 + $rules = $this->rule; + } + + // 分析验证规则 + $scene = $this->getScene($scene); + if (is_array($scene)) { + // 处理场景验证字段 + $change = []; + $array = []; + foreach ($scene as $k => $val) { + if (is_numeric($k)) { + $array[] = $val; + } else { + $array[] = $k; + $change[$k] = $val; + } + } + } + + foreach ($rules as $key => $item) { + // field => rule1|rule2... field=>['rule1','rule2',...] + if (is_numeric($key)) { + // [field,rule1|rule2,msg1|msg2] + $key = $item[0]; + $rule = $item[1]; + if (isset($item[2])) { + $msg = is_string($item[2]) ? explode('|', $item[2]) : $item[2]; + } else { + $msg = []; + } + } else { + $rule = $item; + $msg = []; + } + if (strpos($key, '|')) { + // 字段|描述 用于指定属性名称 + list($key, $title) = explode('|', $key); + } else { + $title = isset($this->field[$key]) ? $this->field[$key] : $key; + } + + // 场景检测 + if (!empty($scene)) { + if ($scene instanceof \Closure && !call_user_func_array($scene, [$key, $data])) { + continue; + } elseif (is_array($scene)) { + if (!in_array($key, $array)) { + continue; + } elseif (isset($change[$key])) { + // 重载某个验证规则 + $rule = $change[$key]; + } + } + } + + // 获取数据 支持二维数组 + $value = $this->getDataValue($data, $key); + + // 字段验证 + if ($rule instanceof \Closure) { + // 匿名函数验证 支持传入当前字段和所有字段两个数据 + $result = call_user_func_array($rule, [$value, $data]); + } else { + $result = $this->checkItem($key, $value, $rule, $data, $title, $msg); + } + + if (true !== $result) { + // 没有返回true 则表示验证失败 + if (!empty($this->batch)) { + // 批量验证 + if (is_array($result)) { + $this->error = array_merge($this->error, $result); + } else { + $this->error[$key] = $result; + } + } else { + $this->error = $result; + return false; + } + } + } + return !empty($this->error) ? false : true; + } + + /** + * 根据验证规则验证数据 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rules 验证规则 + * @return bool + */ + protected function checkRule($value, $rules) + { + if ($rules instanceof \Closure) { + return call_user_func_array($rules, [$value]); + } elseif (is_string($rules)) { + $rules = explode('|', $rules); + } + + foreach ($rules as $key => $rule) { + if ($rule instanceof \Closure) { + $result = call_user_func_array($rule, [$value]); + } else { + // 判断验证类型 + list($type, $rule) = $this->getValidateType($key, $rule); + + $callback = isset(self::$type[$type]) ? self::$type[$type] : [$this, $type]; + + $result = call_user_func_array($callback, [$value, $rule]); + } + + if (true !== $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($field, $value, $rules, $data, $title = '', $msg = []) + { + // 支持多规则验证 require|in:a,b,c|... 或者 ['require','in'=>'a,b,c',...] + if (is_string($rules)) { + $rules = explode('|', $rules); + } + $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 { + // 判断验证类型 + list($type, $rule, $info) = $this->getValidateType($key, $rule); + + // 如果不是require 有数据才会行验证 + if (0 === strpos($info, 'require') || (!is_null($value) && '' !== $value)) { + // 验证类型 + $callback = isset(self::$type[$type]) ? self::$type[$type] : [$this, $type]; + // 验证数据 + $result = call_user_func_array($callback, [$value, $rule, $data, $field, $title]); + } else { + $result = true; + } + } + + if (false === $result) { + // 验证失败 返回错误信息 + if (isset($msg[$i])) { + $message = $msg[$i]; + if (is_string($message) && strpos($message, '{%') === 0) { + $message = 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', ':rule'], [$title, (string) $rule], $result); + } + return $result; + } + $i++; + } + return $result; + } + + /** + * 获取当前验证类型及规则 + * @access public + * @param mixed $key + * @param mixed $rule + * @return array + */ + protected function getValidateType($key, $rule) + { + // 判断验证类型 + if (!is_numeric($key)) { + return [$key, $rule, $key]; + } + + if (strpos($rule, ':')) { + list($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 protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @param string $field 字段名 + * @return bool + */ + protected function confirm($value, $rule, $data, $field = '') + { + if ('' == $rule) { + if (strpos($field, '_confirm')) { + $rule = strstr($field, '_confirm', true); + } else { + $rule = $field . '_confirm'; + } + } + return $this->getDataValue($data, $rule) === $value; + } + + /** + * 验证是否和某个字段的值是否不同 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function different($value, $rule, $data) + { + return $this->getDataValue($data, $rule) != $value; + } + + /** + * 验证是否大于等于某个值 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function egt($value, $rule, $data) + { + $val = $this->getDataValue($data, $rule); + return !is_null($val) && $value >= $val; + } + + /** + * 验证是否大于某个值 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function gt($value, $rule, $data) + { + $val = $this->getDataValue($data, $rule); + return !is_null($val) && $value > $val; + } + + /** + * 验证是否小于等于某个值 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function elt($value, $rule, $data) + { + $val = $this->getDataValue($data, $rule); + return !is_null($val) && $value <= $val; + } + + /** + * 验证是否小于某个值 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function lt($value, $rule, $data) + { + $val = $this->getDataValue($data, $rule); + return !is_null($val) && $value < $val; + } + + /** + * 验证是否等于某个值 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function eq($value, $rule) + { + return $value == $rule; + } + + /** + * 验证字段值是否为有效格式 + * @access protected + * @param mixed $value 字段值 + * @param string $rule 验证规则 + * @param array $data 验证数据 + * @return bool + */ + protected function is($value, $rule, $data = []) + { + switch ($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 'alpha': + // 只允许字母 + $result = $this->regex($value, '/^[A-Za-z]+$/'); + break; + case 'alphaNum': + // 只允许字母和数字 + $result = $this->regex($value, '/^[A-Za-z0-9]+$/'); + break; + case 'alphaDash': + // 只允许字母、数字和下划线 破折号 + $result = $this->regex($value, '/^[A-Za-z0-9\-\_]+$/'); + break; + case 'chs': + // 只允许汉字 + $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}]+$/u'); + break; + case 'chsAlpha': + // 只允许汉字、字母 + $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}a-zA-Z]+$/u'); + break; + case 'chsAlphaNum': + // 只允许汉字、字母和数字 + $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9]+$/u'); + break; + case 'chsDash': + // 只允许汉字、字母、数字和下划线_及破折号- + $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9\_\-]+$/u'); + break; + case 'activeUrl': + // 是否为有效的网址 + $result = checkdnsrr($value); + break; + case 'ip': + // 是否为IP地址 + $result = $this->filter($value, [FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6]); + break; + case 'url': + // 是否为一个URL地址 + $result = $this->filter($value, FILTER_VALIDATE_URL); + break; + case 'float': + // 是否为float + $result = $this->filter($value, FILTER_VALIDATE_FLOAT); + break; + case 'number': + $result = is_numeric($value); + break; + case 'integer': + // 是否为整型 + $result = $this->filter($value, FILTER_VALIDATE_INT); + break; + case 'email': + // 是否为邮箱地址 + $result = $this->filter($value, FILTER_VALIDATE_EMAIL); + break; + case 'boolean': + // 是否为布尔值 + $result = in_array($value, [true, false, 0, 1, '0', '1'], true); + 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(self::$type[$rule])) { + // 注册的验证规则 + $result = call_user_func_array(self::$type[$rule], [$value]); + } else { + // 正则验证 + $result = $this->regex($value, $rule); + } + } + return $result; + } + + // 判断图像类型 + protected function getImageType($image) + { + if (function_exists('exif_imagetype')) { + return exif_imagetype($image); + } else { + try { + $info = getimagesize($image); + return $info ? $info[2] : false; + } catch (\Exception $e) { + return false; + } + } + } + + /** + * 验证是否为合格的域名或者IP 支持A,MX,NS,SOA,PTR,CNAME,AAAA,A6, SRV,NAPTR,TXT 或者 ANY类型 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function activeUrl($value, $rule) + { + if (!in_array($rule, ['A', 'MX', 'NS', 'SOA', 'PTR', 'CNAME', 'AAAA', 'A6', 'SRV', 'NAPTR', 'TXT', 'ANY'])) { + $rule = 'MX'; + } + return checkdnsrr($value, $rule); + } + + /** + * 验证是否有效IP + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 ipv4 ipv6 + * @return bool + */ + protected function ip($value, $rule) + { + if (!in_array($rule, ['ipv4', 'ipv6'])) { + $rule = 'ipv4'; + } + return $this->filter($value, [FILTER_VALIDATE_IP, 'ipv6' == $rule ? FILTER_FLAG_IPV6 : FILTER_FLAG_IPV4]); + } + + /** + * 验证上传文件后缀 + * @access protected + * @param mixed $file 上传文件 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function fileExt($file, $rule) + { + if (is_array($file)) { + foreach ($file as $item) { + if (!($item instanceof File) || !$item->checkExt($rule)) { + return false; + } + } + return true; + } elseif ($file instanceof File) { + return $file->checkExt($rule); + } else { + return false; + } + } + + /** + * 验证上传文件类型 + * @access protected + * @param mixed $file 上传文件 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function fileMime($file, $rule) + { + if (is_array($file)) { + foreach ($file as $item) { + if (!($item instanceof File) || !$item->checkMime($rule)) { + return false; + } + } + return true; + } elseif ($file instanceof File) { + return $file->checkMime($rule); + } else { + return false; + } + } + + /** + * 验证上传文件大小 + * @access protected + * @param mixed $file 上传文件 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function fileSize($file, $rule) + { + if (is_array($file)) { + foreach ($file as $item) { + if (!($item instanceof File) || !$item->checkSize($rule)) { + return false; + } + } + return true; + } elseif ($file instanceof File) { + return $file->checkSize($rule); + } else { + return false; + } + } + + /** + * 验证图片的宽高及类型 + * @access protected + * @param mixed $file 上传文件 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function image($file, $rule) + { + if (!($file instanceof File)) { + return false; + } + if ($rule) { + $rule = explode(',', $rule); + list($width, $height, $type) = getimagesize($file->getRealPath()); + if (isset($rule[2])) { + $imageType = strtolower($rule[2]); + if ('jpeg' == $imageType) { + $imageType = 'jpg'; + } + if (image_type_to_extension($type, false) != $imageType) { + return false; + } + } + + list($w, $h) = $rule; + return $w == $width && $h == $height; + } else { + return in_array($this->getImageType($file->getRealPath()), [1, 2, 3, 6]); + } + } + + /** + * 验证请求类型 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function method($value, $rule) + { + $method = Request::instance()->method(); + return strtoupper($rule) == $method; + } + + /** + * 验证时间和日期是否符合指定格式 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function dateFormat($value, $rule) + { + $info = date_parse_from_format($rule, $value); + return 0 == $info['warning_count'] && 0 == $info['error_count']; + } + + /** + * 验证是否唯一 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 格式:数据表,字段名,排除ID,主键名 + * @param array $data 数据 + * @param string $field 验证字段名 + * @return bool + */ + protected function unique($value, $rule, $data, $field) + { + if (is_string($rule)) { + $rule = explode(',', $rule); + } + if (false !== strpos($rule[0], '\\')) { + // 指定模型类 + $db = new $rule[0]; + } else { + try { + $db = Loader::model($rule[0]); + } catch (ClassNotFoundException $e) { + $db = Db::name($rule[0]); + } + } + $key = isset($rule[1]) ? $rule[1] : $field; + + if (strpos($key, '^')) { + // 支持多个字段验证 + $fields = explode('^', $key); + foreach ($fields as $key) { + if (isset($data[$key])) { + $map[$key] = $data[$key]; + } + } + } elseif (strpos($key, '=')) { + parse_str($key, $map); + } elseif (isset($data[$field])) { + $map[$key] = $data[$field]; + } else { + $map = []; + } + + $pk = isset($rule[3]) ? $rule[3] : $db->getPk(); + if (is_string($pk)) { + if (isset($rule[2])) { + $map[$pk] = ['neq', $rule[2]]; + } elseif (isset($data[$pk])) { + $map[$pk] = ['neq', $data[$pk]]; + } + } + if ($db->where($map)->field($pk)->find()) { + return false; + } + return true; + } + + /** + * 使用行为类验证 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return mixed + */ + protected function behavior($value, $rule, $data) + { + return Hook::exec($rule, '', $data); + } + + /** + * 使用filter_var方式验证 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function filter($value, $rule) + { + if (is_string($rule) && strpos($rule, ',')) { + list($rule, $param) = explode(',', $rule); + } elseif (is_array($rule)) { + $param = isset($rule[1]) ? $rule[1] : null; + $rule = $rule[0]; + } else { + $param = null; + } + return false !== filter_var($value, is_int($rule) ? $rule : filter_id($rule), $param); + } + + /** + * 验证某个字段等于某个值的时候必须 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function requireIf($value, $rule, $data) + { + list($field, $val) = explode(',', $rule); + if ($this->getDataValue($data, $field) == $val) { + return !empty($value) || '0' == $value; + } else { + return true; + } + } + + /** + * 通过回调方法验证某个字段是否必须 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function requireCallback($value, $rule, $data) + { + $result = call_user_func_array($rule, [$value, $data]); + if ($result) { + return !empty($value) || '0' == $value; + } else { + return true; + } + } + + /** + * 验证某个字段有值的情况下必须 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function requireWith($value, $rule, $data) + { + $val = $this->getDataValue($data, $rule); + if (!empty($val)) { + return !empty($value) || '0' == $value; + } else { + return true; + } + } + + /** + * 验证是否在范围内 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function in($value, $rule) + { + return in_array($value, is_array($rule) ? $rule : explode(',', $rule)); + } + + /** + * 验证是否不在某个范围 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function notIn($value, $rule) + { + return !in_array($value, is_array($rule) ? $rule : explode(',', $rule)); + } + + /** + * between验证数据 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function between($value, $rule) + { + if (is_string($rule)) { + $rule = explode(',', $rule); + } + list($min, $max) = $rule; + return $value >= $min && $value <= $max; + } + + /** + * 使用notbetween验证数据 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function notBetween($value, $rule) + { + if (is_string($rule)) { + $rule = explode(',', $rule); + } + list($min, $max) = $rule; + return $value < $min || $value > $max; + } + + /** + * 验证数据长度 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function length($value, $rule) + { + if (is_array($value)) { + $length = count($value); + } elseif ($value instanceof File) { + $length = $value->getSize(); + } else { + $length = mb_strlen((string) $value); + } + + if (strpos($rule, ',')) { + // 长度区间 + list($min, $max) = explode(',', $rule); + return $length >= $min && $length <= $max; + } else { + // 指定长度 + return $length == $rule; + } + } + + /** + * 验证数据最大长度 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function max($value, $rule) + { + if (is_array($value)) { + $length = count($value); + } elseif ($value instanceof File) { + $length = $value->getSize(); + } else { + $length = mb_strlen((string) $value); + } + return $length <= $rule; + } + + /** + * 验证数据最小长度 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function min($value, $rule) + { + if (is_array($value)) { + $length = count($value); + } elseif ($value instanceof File) { + $length = $value->getSize(); + } else { + $length = mb_strlen((string) $value); + } + return $length >= $rule; + } + + /** + * 验证日期 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function after($value, $rule, $data) + { + return strtotime($value) >= strtotime($rule); + } + + /** + * 验证日期 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function before($value, $rule, $data) + { + return strtotime($value) <= strtotime($rule); + } + + /** + * 验证日期字段 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function afterWith($value, $rule, $data) + { + $rule = $this->getDataValue($data, $rule); + return !is_null($rule) && strtotime($value) >= strtotime($rule); + } + + /** + * 验证日期字段 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function beforeWith($value, $rule, $data) + { + $rule = $this->getDataValue($data, $rule); + return !is_null($rule) && strtotime($value) <= strtotime($rule); + } + + /** + * 验证有效期 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function expire($value, $rule) + { + if (is_string($rule)) { + $rule = explode(',', $rule); + } + list($start, $end) = $rule; + if (!is_numeric($start)) { + $start = strtotime($start); + } + + if (!is_numeric($end)) { + $end = strtotime($end); + } + return $_SERVER['REQUEST_TIME'] >= $start && $_SERVER['REQUEST_TIME'] <= $end; + } + + /** + * 验证IP许可 + * @access protected + * @param string $value 字段值 + * @param mixed $rule 验证规则 + * @return mixed + */ + protected function allowIp($value, $rule) + { + return in_array($_SERVER['REMOTE_ADDR'], is_array($rule) ? $rule : explode(',', $rule)); + } + + /** + * 验证IP禁用 + * @access protected + * @param string $value 字段值 + * @param mixed $rule 验证规则 + * @return mixed + */ + protected function denyIp($value, $rule) + { + return !in_array($_SERVER['REMOTE_ADDR'], is_array($rule) ? $rule : explode(',', $rule)); + } + + /** + * 使用正则验证数据 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 正则规则或者预定义正则名 + * @return mixed + */ + protected function regex($value, $rule) + { + if (isset($this->regex[$rule])) { + $rule = $this->regex[$rule]; + } + if (0 !== strpos($rule, '/') && !preg_match('/\/[imsU]{0,4}$/', $rule)) { + // 不是正则表达式则两端补上/ + $rule = '/^' . $rule . '$/'; + } + return is_scalar($value) && 1 === preg_match($rule, (string) $value); + } + + /** + * 验证表单令牌 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function token($value, $rule, $data) + { + $rule = !empty($rule) ? $rule : '__token__'; + if (!isset($data[$rule]) || !Session::has($rule)) { + // 令牌数据无效 + return false; + } + + // 令牌验证 + if (isset($data[$rule]) && Session::get($rule) === $data[$rule]) { + // 防止重复提交 + Session::delete($rule); // 验证完成销毁session + return true; + } + // 开启TOKEN重置 + Session::delete($rule); + return false; + } + + // 获取错误信息 + public function getError() + { + return $this->error; + } + + /** + * 获取数据值 + * @access protected + * @param array $data 数据 + * @param string $key 数据标识 支持二维 + * @return mixed + */ + protected function getDataValue($data, $key) + { + if (is_numeric($key)) { + $value = $key; + } elseif (strpos($key, '.')) { + // 支持二维数组验证 + list($name1, $name2) = explode('.', $key); + $value = isset($data[$name1][$name2]) ? $data[$name1][$name2] : null; + } else { + $value = isset($data[$key]) ? $data[$key] : null; + } + return $value; + } + + /** + * 获取验证规则的错误提示信息 + * @access protected + * @param string $attribute 字段英文名 + * @param string $title 字段描述名 + * @param string $type 验证规则名称 + * @param mixed $rule 验证规则数据 + * @return string + */ + protected function getRuleMsg($attribute, $title, $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(self::$typeMsg[$type])) { + $msg = self::$typeMsg[$type]; + } elseif (0 === strpos($type, 'require')) { + $msg = self::$typeMsg['require']; + } else { + $msg = $title . Lang::get('not conform to the rules'); + } + + if (is_string($msg) && 0 === strpos($msg, '{%')) { + $msg = Lang::get(substr($msg, 2, -1)); + } elseif (Lang::has($msg)) { + $msg = Lang::get($msg); + } + + if (is_string($msg) && 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', ':rule', ':1', ':2', ':3'], + [$title, (string) $rule, $array[0], $array[1], $array[2]], + $msg); + } + return $msg; + } + + /** + * 获取数据验证的场景 + * @access protected + * @param string $scene 验证场景 + * @return array + */ + protected function getScene($scene = '') + { + if (empty($scene)) { + // 读取指定场景 + $scene = $this->currentScene; + } + + if (!empty($scene) && isset($this->scene[$scene])) { + // 如果设置了验证适用场景 + $scene = $this->scene[$scene]; + if (is_string($scene)) { + $scene = explode(',', $scene); + } + } else { + $scene = []; + } + return $scene; + } + + public static function __callStatic($method, $params) + { + $class = self::make(); + if (method_exists($class, $method)) { + return call_user_func_array([$class, $method], $params); + } else { + throw new \BadMethodCallException('method not exists:' . __CLASS__ . '->' . $method); + } + } +} diff --git a/thinkphp/library/think/View.php b/thinkphp/library/think/View.php new file mode 100644 index 0000000..ca2dadb --- /dev/null +++ b/thinkphp/library/think/View.php @@ -0,0 +1,239 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class View +{ + // 视图实例 + protected static $instance; + // 模板引擎实例 + public $engine; + // 模板变量 + protected $data = []; + // 用于静态赋值的模板变量 + protected static $var = []; + // 视图输出替换 + protected $replace = []; + + /** + * 构造函数 + * @access public + * @param array $engine 模板引擎参数 + * @param array $replace 字符串替换参数 + */ + public function __construct($engine = [], $replace = []) + { + // 初始化模板引擎 + $this->engine($engine); + // 基础替换字符串 + $request = Request::instance(); + $base = $request->root(); + $root = strpos($base, '.') ? ltrim(dirname($base), DS) : $base; + if ('' != $root) { + $root = '/' . ltrim($root, '/'); + } + $baseReplace = [ + '__ROOT__' => $root, + '__URL__' => $base . '/' . $request->module() . '/' . Loader::parseName($request->controller()), + '__STATIC__' => $root . '/static', + '__CSS__' => $root . '/static/css', + '__JS__' => $root . '/static/js', + ]; + $this->replace = array_merge($baseReplace, (array) $replace); + } + + /** + * 初始化视图 + * @access public + * @param array $engine 模板引擎参数 + * @param array $replace 字符串替换参数 + * @return object + */ + public static function instance($engine = [], $replace = []) + { + if (is_null(self::$instance)) { + self::$instance = new self($engine, $replace); + } + return self::$instance; + } + + /** + * 模板变量静态赋值 + * @access public + * @param mixed $name 变量名 + * @param mixed $value 变量值 + * @return void + */ + public static function share($name, $value = '') + { + if (is_array($name)) { + self::$var = array_merge(self::$var, $name); + } else { + self::$var[$name] = $value; + } + } + + /** + * 模板变量赋值 + * @access public + * @param mixed $name 变量名 + * @param mixed $value 变量值 + * @return $this + */ + public function assign($name, $value = '') + { + if (is_array($name)) { + $this->data = array_merge($this->data, $name); + } else { + $this->data[$name] = $value; + } + return $this; + } + + /** + * 设置当前模板解析的引擎 + * @access public + * @param array|string $options 引擎参数 + * @return $this + */ + public function engine($options = []) + { + if (is_string($options)) { + $type = $options; + $options = []; + } else { + $type = !empty($options['type']) ? $options['type'] : 'Think'; + } + + $class = false !== strpos($type, '\\') ? $type : '\\think\\view\\driver\\' . ucfirst($type); + if (isset($options['type'])) { + unset($options['type']); + } + $this->engine = new $class($options); + return $this; + } + + /** + * 配置模板引擎 + * @access private + * @param string|array $name 参数名 + * @param mixed $value 参数值 + * @return $this + */ + public function config($name, $value = null) + { + $this->engine->config($name, $value); + return $this; + } + + /** + * 解析和获取模板内容 用于输出 + * @param string $template 模板文件名或者内容 + * @param array $vars 模板输出变量 + * @param array $replace 替换内容 + * @param array $config 模板参数 + * @param bool $renderContent 是否渲染内容 + * @return string + * @throws Exception + */ + public function fetch($template = '', $vars = [], $replace = [], $config = [], $renderContent = false) + { + // 模板变量 + $vars = array_merge(self::$var, $this->data, $vars); + + // 页面缓存 + ob_start(); + ob_implicit_flush(0); + + // 渲染输出 + try { + $method = $renderContent ? 'display' : 'fetch'; + // 允许用户自定义模板的字符串替换 + $replace = array_merge($this->replace, $replace, (array) $this->engine->config('tpl_replace_string')); + $this->engine->config('tpl_replace_string', $replace); + $this->engine->$method($template, $vars, $config); + } catch (\Exception $e) { + ob_end_clean(); + throw $e; + } + + // 获取并清空缓存 + $content = ob_get_clean(); + // 内容过滤标签 + Hook::listen('view_filter', $content); + return $content; + } + + /** + * 视图内容替换 + * @access public + * @param string|array $content 被替换内容(支持批量替换) + * @param string $replace 替换内容 + * @return $this + */ + public function replace($content, $replace = '') + { + if (is_array($content)) { + $this->replace = array_merge($this->replace, $content); + } else { + $this->replace[$content] = $replace; + } + return $this; + } + + /** + * 渲染内容输出 + * @access public + * @param string $content 内容 + * @param array $vars 模板输出变量 + * @param array $replace 替换内容 + * @param array $config 模板参数 + * @return mixed + */ + public function display($content, $vars = [], $replace = [], $config = []) + { + return $this->fetch($content, $vars, $replace, $config, true); + } + + /** + * 模板变量赋值 + * @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]); + } +} diff --git a/thinkphp/library/think/cache/Driver.php b/thinkphp/library/think/cache/Driver.php new file mode 100644 index 0000000..07805e4 --- /dev/null +++ b/thinkphp/library/think/cache/Driver.php @@ -0,0 +1,231 @@ + +// +---------------------------------------------------------------------- + +namespace think\cache; + +/** + * 缓存基础类 + */ +abstract class Driver +{ + protected $handler = null; + protected $options = []; + protected $tag; + + /** + * 判断缓存是否存在 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + abstract public function has($name); + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + abstract public function get($name, $default = false); + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int $expire 有效时间 0为永久 + * @return boolean + */ + abstract public function set($name, $value, $expire = null); + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + abstract public function inc($name, $step = 1); + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + abstract public function dec($name, $step = 1); + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + abstract public function rm($name); + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return boolean + */ + abstract public function clear($tag = null); + + /** + * 获取实际的缓存标识 + * @access public + * @param string $name 缓存名 + * @return string + */ + protected function getCacheKey($name) + { + return $this->options['prefix'] . $name; + } + + /** + * 读取缓存并删除 + * @access public + * @param string $name 缓存变量名 + * @return mixed + */ + public function pull($name) + { + $result = $this->get($name, false); + if ($result) { + $this->rm($name); + return $result; + } else { + return; + } + } + + /** + * 如果不存在则写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int $expire 有效时间 0为永久 + * @return mixed + */ + public function remember($name, $value, $expire = null) + { + if (!$this->has($name)) { + $time = time(); + while ($time + 5 > time() && $this->has($name . '_lock')) { + // 存在锁定则等待 + usleep(200000); + } + + try { + // 锁定 + $this->set($name . '_lock', true); + if ($value instanceof \Closure) { + $value = call_user_func($value); + } + $this->set($name, $value, $expire); + // 解锁 + $this->rm($name . '_lock'); + } catch (\Exception $e) { + // 解锁 + $this->rm($name . '_lock'); + throw $e; + } catch (\throwable $e) { + $this->rm($name . '_lock'); + throw $e; + } + } else { + $value = $this->get($name); + } + return $value; + } + + /** + * 缓存标签 + * @access public + * @param string $name 标签名 + * @param string|array $keys 缓存标识 + * @param bool $overlay 是否覆盖 + * @return $this + */ + public function tag($name, $keys = null, $overlay = false) + { + if (is_null($name)) { + + } elseif (is_null($keys)) { + $this->tag = $name; + } else { + $key = 'tag_' . md5($name); + if (is_string($keys)) { + $keys = explode(',', $keys); + } + $keys = array_map([$this, 'getCacheKey'], $keys); + if ($overlay) { + $value = $keys; + } else { + $value = array_unique(array_merge($this->getTagItem($name), $keys)); + } + $this->set($key, implode(',', $value), 0); + } + return $this; + } + + /** + * 更新标签 + * @access public + * @param string $name 缓存标识 + * @return void + */ + protected function setTagItem($name) + { + if ($this->tag) { + $key = 'tag_' . md5($this->tag); + $this->tag = null; + if ($this->has($key)) { + $value = explode(',', $this->get($key)); + $value[] = $name; + $value = implode(',', array_unique($value)); + } else { + $value = $name; + } + $this->set($key, $value, 0); + } + } + + /** + * 获取标签包含的缓存标识 + * @access public + * @param string $tag 缓存标签 + * @return array + */ + protected function getTagItem($tag) + { + $key = 'tag_' . md5($tag); + $value = $this->get($key); + if ($value) { + return array_filter(explode(',', $value)); + } else { + return []; + } + } + + /** + * 返回句柄对象,可执行其它高级方法 + * + * @access public + * @return object + */ + public function handler() + { + return $this->handler; + } +} diff --git a/thinkphp/library/think/cache/driver/File.php b/thinkphp/library/think/cache/driver/File.php new file mode 100644 index 0000000..fee6489 --- /dev/null +++ b/thinkphp/library/think/cache/driver/File.php @@ -0,0 +1,268 @@ + +// +---------------------------------------------------------------------- + +namespace think\cache\driver; + +use think\cache\Driver; + +/** + * 文件类型缓存类 + * @author liu21st + */ +class File extends Driver +{ + protected $options = [ + 'expire' => 0, + 'cache_subdir' => true, + 'prefix' => '', + 'path' => CACHE_PATH, + 'data_compress' => false, + ]; + + protected $expire; + + /** + * 构造函数 + * @param array $options + */ + public function __construct($options = []) + { + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + if (substr($this->options['path'], -1) != DS) { + $this->options['path'] .= DS; + } + $this->init(); + } + + /** + * 初始化检查 + * @access private + * @return boolean + */ + private function init() + { + // 创建项目缓存目录 + if (!is_dir($this->options['path'])) { + if (mkdir($this->options['path'], 0755, true)) { + return true; + } + } + return false; + } + + /** + * 取得变量的存储文件名 + * @access protected + * @param string $name 缓存变量名 + * @param bool $auto 是否自动创建目录 + * @return string + */ + protected function getCacheKey($name, $auto = false) + { + $name = md5($name); + if ($this->options['cache_subdir']) { + // 使用子目录 + $name = substr($name, 0, 2) . DS . substr($name, 2); + } + if ($this->options['prefix']) { + $name = $this->options['prefix'] . DS . $name; + } + $filename = $this->options['path'] . $name . '.php'; + $dir = dirname($filename); + + if ($auto && !is_dir($dir)) { + mkdir($dir, 0755, true); + } + return $filename; + } + + /** + * 判断缓存是否存在 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name) + { + return $this->get($name) ? true : false; + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = false) + { + $filename = $this->getCacheKey($name); + if (!is_file($filename)) { + return $default; + } + $content = file_get_contents($filename); + $this->expire = null; + if (false !== $content) { + $expire = (int) substr($content, 8, 12); + if (0 != $expire && time() > filemtime($filename) + $expire) { + return $default; + } + $this->expire = $expire; + $content = substr($content, 32); + if ($this->options['data_compress'] && function_exists('gzcompress')) { + //启用数据压缩 + $content = gzuncompress($content); + } + $content = unserialize($content); + return $content; + } else { + return $default; + } + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return boolean + */ + public function set($name, $value, $expire = null) + { + if (is_null($expire)) { + $expire = $this->options['expire']; + } + if ($expire instanceof \DateTime) { + $expire = $expire->getTimestamp() - time(); + } + $filename = $this->getCacheKey($name, true); + if ($this->tag && !is_file($filename)) { + $first = true; + } + $data = serialize($value); + if ($this->options['data_compress'] && function_exists('gzcompress')) { + //数据压缩 + $data = gzcompress($data, 3); + } + $data = "\n" . $data; + $result = file_put_contents($filename, $data); + if ($result) { + isset($first) && $this->setTagItem($filename); + clearstatcache(); + return true; + } else { + return false; + } + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc($name, $step = 1) + { + if ($this->has($name)) { + $value = $this->get($name) + $step; + $expire = $this->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($name, $step = 1) + { + if ($this->has($name)) { + $value = $this->get($name) - $step; + $expire = $this->expire; + } else { + $value = -$step; + $expire = 0; + } + + return $this->set($name, $value, $expire) ? $value : false; + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name) + { + $filename = $this->getCacheKey($name); + try { + return $this->unlink($filename); + } catch (\Exception $e) { + } + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return boolean + */ + public function clear($tag = null) + { + if ($tag) { + // 指定标签清除 + $keys = $this->getTagItem($tag); + foreach ($keys as $key) { + $this->unlink($key); + } + $this->rm('tag_' . md5($tag)); + return true; + } + $files = (array) glob($this->options['path'] . ($this->options['prefix'] ? $this->options['prefix'] . DS : '') . '*'); + foreach ($files as $path) { + if (is_dir($path)) { + $matches = glob($path . '/*.php'); + if (is_array($matches)) { + array_map('unlink', $matches); + } + rmdir($path); + } else { + unlink($path); + } + } + return true; + } + + /** + * 判断文件是否存在后,删除 + * @param $path + * @return bool + * @author byron sampson + * @return boolean + */ + private function unlink($path) + { + return is_file($path) && unlink($path); + } + +} diff --git a/thinkphp/library/think/cache/driver/Lite.php b/thinkphp/library/think/cache/driver/Lite.php new file mode 100644 index 0000000..8cbf08f --- /dev/null +++ b/thinkphp/library/think/cache/driver/Lite.php @@ -0,0 +1,187 @@ + +// +---------------------------------------------------------------------- + +namespace think\cache\driver; + +use think\cache\Driver; + +/** + * 文件类型缓存类 + * @author liu21st + */ +class Lite extends Driver +{ + protected $options = [ + 'prefix' => '', + 'path' => '', + 'expire' => 0, // 等于 10*365*24*3600(10年) + ]; + + /** + * 构造函数 + * @access public + * + * @param array $options + */ + public function __construct($options = []) + { + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + if (substr($this->options['path'], -1) != DS) { + $this->options['path'] .= DS; + } + + } + + /** + * 取得变量的存储文件名 + * @access protected + * @param string $name 缓存变量名 + * @return string + */ + protected function getCacheKey($name) + { + return $this->options['path'] . $this->options['prefix'] . md5($name) . '.php'; + } + + /** + * 判断缓存是否存在 + * @access public + * @param string $name 缓存变量名 + * @return mixed + */ + public function has($name) + { + return $this->get($name) ? true : false; + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = false) + { + $filename = $this->getCacheKey($name); + if (is_file($filename)) { + // 判断是否过期 + $mtime = filemtime($filename); + if ($mtime < time()) { + // 清除已经过期的文件 + unlink($filename); + return $default; + } + return include $filename; + } else { + return $default; + } + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return bool + */ + public function set($name, $value, $expire = null) + { + if (is_null($expire)) { + $expire = $this->options['expire']; + } + if ($expire instanceof \DateTime) { + $expire = $expire->getTimestamp(); + } else { + $expire = 0 === $expire ? 10 * 365 * 24 * 3600 : $expire; + $expire = time() + $expire; + } + $filename = $this->getCacheKey($name); + if ($this->tag && !is_file($filename)) { + $first = true; + } + $ret = file_put_contents($filename, ("setTagItem($filename); + touch($filename, $expire); + } + return $ret; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc($name, $step = 1) + { + if ($this->has($name)) { + $value = $this->get($name) + $step; + } else { + $value = $step; + } + return $this->set($name, $value, 0) ? $value : false; + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec($name, $step = 1) + { + if ($this->has($name)) { + $value = $this->get($name) - $step; + } else { + $value = -$step; + } + return $this->set($name, $value, 0) ? $value : false; + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name) + { + return unlink($this->getCacheKey($name)); + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return bool + */ + public function clear($tag = null) + { + if ($tag) { + // 指定标签清除 + $keys = $this->getTagItem($tag); + foreach ($keys as $key) { + unlink($key); + } + $this->rm('tag_' . md5($tag)); + return true; + } + array_map("unlink", glob($this->options['path'] . ($this->options['prefix'] ? $this->options['prefix'] . DS : '') . '*.php')); + } +} diff --git a/thinkphp/library/think/cache/driver/Memcache.php b/thinkphp/library/think/cache/driver/Memcache.php new file mode 100644 index 0000000..58703ea --- /dev/null +++ b/thinkphp/library/think/cache/driver/Memcache.php @@ -0,0 +1,177 @@ + +// +---------------------------------------------------------------------- + +namespace think\cache\driver; + +use think\cache\Driver; + +class Memcache extends Driver +{ + protected $options = [ + 'host' => '127.0.0.1', + 'port' => 11211, + 'expire' => 0, + 'timeout' => 0, // 超时时间(单位:毫秒) + 'persistent' => true, + 'prefix' => '', + ]; + + /** + * 构造函数 + * @param array $options 缓存参数 + * @access public + * @throws \BadFunctionCallException + */ + public function __construct($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 = explode(',', $this->options['host']); + $ports = explode(',', $this->options['port']); + if (empty($ports[0])) { + $ports[0] = 11211; + } + // 建立连接 + foreach ((array) $hosts as $i => $host) { + $port = isset($ports[$i]) ? $ports[$i] : $ports[0]; + $this->options['timeout'] > 0 ? + $this->handler->addServer($host, $port, $this->options['persistent'], 1, $this->options['timeout']) : + $this->handler->addServer($host, $port, $this->options['persistent'], 1); + } + } + + /** + * 判断缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name) + { + $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 = false) + { + $result = $this->handler->get($this->getCacheKey($name)); + return false !== $result ? $result : $default; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return bool + */ + public function set($name, $value, $expire = null) + { + if (is_null($expire)) { + $expire = $this->options['expire']; + } + if ($expire instanceof \DateTime) { + $expire = $expire->getTimestamp() - time(); + } + if ($this->tag && !$this->has($name)) { + $first = true; + } + $key = $this->getCacheKey($name); + if ($this->handler->set($key, $value, 0, $expire)) { + isset($first) && $this->setTagItem($key); + return true; + } + return false; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc($name, $step = 1) + { + $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($name, $step = 1) + { + $key = $this->getCacheKey($name); + $value = $this->handler->get($key) - $step; + $res = $this->handler->set($key, $value); + if (!$res) { + return false; + } else { + return $value; + } + } + + /** + * 删除缓存 + * @param string $name 缓存变量名 + * @param bool|false $ttl + * @return bool + */ + public function rm($name, $ttl = false) + { + $key = $this->getCacheKey($name); + return false === $ttl ? + $this->handler->delete($key) : + $this->handler->delete($key, $ttl); + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return bool + */ + public function clear($tag = null) + { + if ($tag) { + // 指定标签清除 + $keys = $this->getTagItem($tag); + foreach ($keys as $key) { + $this->handler->delete($key); + } + $this->rm('tag_' . md5($tag)); + return true; + } + return $this->handler->flush(); + } +} diff --git a/thinkphp/library/think/cache/driver/Memcached.php b/thinkphp/library/think/cache/driver/Memcached.php new file mode 100644 index 0000000..5aab5a3 --- /dev/null +++ b/thinkphp/library/think/cache/driver/Memcached.php @@ -0,0 +1,187 @@ + +// +---------------------------------------------------------------------- + +namespace think\cache\driver; + +use think\cache\Driver; + +class Memcached extends Driver +{ + protected $options = [ + 'host' => '127.0.0.1', + 'port' => 11211, + 'expire' => 0, + 'timeout' => 0, // 超时时间(单位:毫秒) + 'prefix' => '', + 'username' => '', //账号 + 'password' => '', //密码 + 'option' => [], + ]; + + /** + * 构造函数 + * @param array $options 缓存参数 + * @access public + */ + public function __construct($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 = explode(',', $this->options['host']); + $ports = explode(',', $this->options['port']); + if (empty($ports[0])) { + $ports[0] = 11211; + } + // 建立连接 + $servers = []; + foreach ((array) $hosts as $i => $host) { + $servers[] = [$host, (isset($ports[$i]) ? $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) + { + $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 = false) + { + $result = $this->handler->get($this->getCacheKey($name)); + return false !== $result ? $result : $default; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return bool + */ + public function set($name, $value, $expire = null) + { + if (is_null($expire)) { + $expire = $this->options['expire']; + } + if ($expire instanceof \DateTime) { + $expire = $expire->getTimestamp() - time(); + } + if ($this->tag && !$this->has($name)) { + $first = true; + } + $key = $this->getCacheKey($name); + $expire = 0 == $expire ? 0 : $_SERVER['REQUEST_TIME'] + $expire; + if ($this->handler->set($key, $value, $expire)) { + isset($first) && $this->setTagItem($key); + return true; + } + return false; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc($name, $step = 1) + { + $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($name, $step = 1) + { + $key = $this->getCacheKey($name); + $value = $this->handler->get($key) - $step; + $res = $this->handler->set($key, $value); + if (!$res) { + return false; + } else { + return $value; + } + } + + /** + * 删除缓存 + * @param string $name 缓存变量名 + * @param bool|false $ttl + * @return bool + */ + public function rm($name, $ttl = false) + { + $key = $this->getCacheKey($name); + return false === $ttl ? + $this->handler->delete($key) : + $this->handler->delete($key, $ttl); + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return bool + */ + public function clear($tag = null) + { + if ($tag) { + // 指定标签清除 + $keys = $this->getTagItem($tag); + $this->handler->deleteMulti($keys); + $this->rm('tag_' . md5($tag)); + return true; + } + return $this->handler->flush(); + } +} diff --git a/thinkphp/library/think/cache/driver/Redis.php b/thinkphp/library/think/cache/driver/Redis.php new file mode 100644 index 0000000..d7bf6d1 --- /dev/null +++ b/thinkphp/library/think/cache/driver/Redis.php @@ -0,0 +1,188 @@ + +// +---------------------------------------------------------------------- + +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 +{ + protected $options = [ + 'host' => '127.0.0.1', + 'port' => 6379, + 'password' => '', + 'select' => 0, + 'timeout' => 0, + 'expire' => 0, + 'persistent' => false, + 'prefix' => '', + ]; + + /** + * 构造函数 + * @param array $options 缓存参数 + * @access public + */ + public function __construct($options = []) + { + if (!extension_loaded('redis')) { + throw new \BadFunctionCallException('not support: redis'); + } + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + $this->handler = new \Redis; + if ($this->options['persistent']) { + $this->handler->pconnect($this->options['host'], $this->options['port'], $this->options['timeout'], 'persistent_id_' . $this->options['select']); + } else { + $this->handler->connect($this->options['host'], $this->options['port'], $this->options['timeout']); + } + + if ('' != $this->options['password']) { + $this->handler->auth($this->options['password']); + } + + if (0 != $this->options['select']) { + $this->handler->select($this->options['select']); + } + } + + /** + * 判断缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name) + { + return (bool) $this->handler->exists($this->getCacheKey($name)); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = false) + { + $value = $this->handler->get($this->getCacheKey($name)); + if (is_null($value) || false === $value) { + return $default; + } + + try { + $result = 0 === strpos($value, 'think_serialize:') ? unserialize(substr($value, 16)) : $value; + } catch (\Exception $e) { + $result = $default; + } + + return $result; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return boolean + */ + public function set($name, $value, $expire = null) + { + if (is_null($expire)) { + $expire = $this->options['expire']; + } + if ($expire instanceof \DateTime) { + $expire = $expire->getTimestamp() - time(); + } + if ($this->tag && !$this->has($name)) { + $first = true; + } + $key = $this->getCacheKey($name); + $value = is_scalar($value) ? $value : 'think_serialize:' . serialize($value); + if ($expire) { + $result = $this->handler->setex($key, $expire, $value); + } else { + $result = $this->handler->set($key, $value); + } + isset($first) && $this->setTagItem($key); + return $result; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc($name, $step = 1) + { + $key = $this->getCacheKey($name); + + return $this->handler->incrby($key, $step); + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec($name, $step = 1) + { + $key = $this->getCacheKey($name); + + return $this->handler->decrby($key, $step); + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name) + { + return $this->handler->del($this->getCacheKey($name)); + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return boolean + */ + public function clear($tag = null) + { + if ($tag) { + // 指定标签清除 + $keys = $this->getTagItem($tag); + foreach ($keys as $key) { + $this->handler->del($key); + } + $this->rm('tag_' . md5($tag)); + return true; + } + return $this->handler->flushDB(); + } + +} diff --git a/thinkphp/library/think/cache/driver/Sqlite.php b/thinkphp/library/think/cache/driver/Sqlite.php new file mode 100644 index 0000000..dc2ee05 --- /dev/null +++ b/thinkphp/library/think/cache/driver/Sqlite.php @@ -0,0 +1,199 @@ + +// +---------------------------------------------------------------------- + +namespace think\cache\driver; + +use think\cache\Driver; + +/** + * Sqlite缓存驱动 + * @author liu21st + */ +class Sqlite extends Driver +{ + protected $options = [ + 'db' => ':memory:', + 'table' => 'sharedmemory', + 'prefix' => '', + 'expire' => 0, + 'persistent' => false, + ]; + + /** + * 构造函数 + * @param array $options 缓存参数 + * @throws \BadFunctionCallException + * @access public + */ + public function __construct($options = []) + { + if (!extension_loaded('sqlite')) { + throw new \BadFunctionCallException('not support: sqlite'); + } + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + $func = $this->options['persistent'] ? 'sqlite_popen' : 'sqlite_open'; + $this->handler = $func($this->options['db']); + } + + /** + * 获取实际的缓存标识 + * @access public + * @param string $name 缓存名 + * @return string + */ + protected function getCacheKey($name) + { + return $this->options['prefix'] . sqlite_escape_string($name); + } + + /** + * 判断缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name) + { + $name = $this->getCacheKey($name); + $sql = 'SELECT value FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\' AND (expire=0 OR expire >' . $_SERVER['REQUEST_TIME'] . ') LIMIT 1'; + $result = sqlite_query($this->handler, $sql); + return sqlite_num_rows($result); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = false) + { + $name = $this->getCacheKey($name); + $sql = 'SELECT value FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\' AND (expire=0 OR expire >' . $_SERVER['REQUEST_TIME'] . ') LIMIT 1'; + $result = sqlite_query($this->handler, $sql); + if (sqlite_num_rows($result)) { + $content = sqlite_fetch_single($result); + if (function_exists('gzcompress')) { + //启用数据压缩 + $content = gzuncompress($content); + } + return unserialize($content); + } + return $default; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return boolean + */ + public function set($name, $value, $expire = null) + { + $name = $this->getCacheKey($name); + $value = sqlite_escape_string(serialize($value)); + if (is_null($expire)) { + $expire = $this->options['expire']; + } + if ($expire instanceof \DateTime) { + $expire = $expire->getTimestamp(); + } else { + $expire = (0 == $expire) ? 0 : (time() + $expire); //缓存有效期为0表示永久缓存 + } + if (function_exists('gzcompress')) { + //数据压缩 + $value = gzcompress($value, 3); + } + if ($this->tag) { + $tag = $this->tag; + $this->tag = null; + } else { + $tag = ''; + } + $sql = 'REPLACE INTO ' . $this->options['table'] . ' (var, value, expire, tag) VALUES (\'' . $name . '\', \'' . $value . '\', \'' . $expire . '\', \'' . $tag . '\')'; + if (sqlite_query($this->handler, $sql)) { + return true; + } + return false; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc($name, $step = 1) + { + if ($this->has($name)) { + $value = $this->get($name) + $step; + } else { + $value = $step; + } + return $this->set($name, $value, 0) ? $value : false; + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec($name, $step = 1) + { + if ($this->has($name)) { + $value = $this->get($name) - $step; + } else { + $value = -$step; + } + return $this->set($name, $value, 0) ? $value : false; + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name) + { + $name = $this->getCacheKey($name); + $sql = 'DELETE FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\''; + sqlite_query($this->handler, $sql); + return true; + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return boolean + */ + public function clear($tag = null) + { + if ($tag) { + $name = sqlite_escape_string($tag); + $sql = 'DELETE FROM ' . $this->options['table'] . ' WHERE tag=\'' . $name . '\''; + sqlite_query($this->handler, $sql); + return true; + } + $sql = 'DELETE FROM ' . $this->options['table']; + sqlite_query($this->handler, $sql); + return true; + } +} diff --git a/thinkphp/library/think/cache/driver/Wincache.php b/thinkphp/library/think/cache/driver/Wincache.php new file mode 100644 index 0000000..03f8d35 --- /dev/null +++ b/thinkphp/library/think/cache/driver/Wincache.php @@ -0,0 +1,152 @@ + +// +---------------------------------------------------------------------- + +namespace think\cache\driver; + +use think\cache\Driver; + +/** + * Wincache缓存驱动 + * @author liu21st + */ +class Wincache extends Driver +{ + protected $options = [ + 'prefix' => '', + 'expire' => 0, + ]; + + /** + * 构造函数 + * @param array $options 缓存参数 + * @throws \BadFunctionCallException + * @access public + */ + public function __construct($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) + { + $key = $this->getCacheKey($name); + return wincache_ucache_exists($key); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = false) + { + $key = $this->getCacheKey($name); + return wincache_ucache_exists($key) ? wincache_ucache_get($key) : $default; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return boolean + */ + public function set($name, $value, $expire = null) + { + if (is_null($expire)) { + $expire = $this->options['expire']; + } + if ($expire instanceof \DateTime) { + $expire = $expire->getTimestamp() - time(); + } + $key = $this->getCacheKey($name); + if ($this->tag && !$this->has($name)) { + $first = true; + } + if (wincache_ucache_set($key, $value, $expire)) { + isset($first) && $this->setTagItem($key); + return true; + } + return false; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc($name, $step = 1) + { + $key = $this->getCacheKey($name); + return wincache_ucache_inc($key, $step); + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec($name, $step = 1) + { + $key = $this->getCacheKey($name); + return wincache_ucache_dec($key, $step); + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name) + { + return wincache_ucache_delete($this->getCacheKey($name)); + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return boolean + */ + public function clear($tag = null) + { + if ($tag) { + $keys = $this->getTagItem($tag); + foreach ($keys as $key) { + wincache_ucache_delete($key); + } + $this->rm('tag_' . md5($tag)); + return true; + } else { + return wincache_ucache_clear(); + } + } + +} diff --git a/thinkphp/library/think/cache/driver/Xcache.php b/thinkphp/library/think/cache/driver/Xcache.php new file mode 100644 index 0000000..4d94c03 --- /dev/null +++ b/thinkphp/library/think/cache/driver/Xcache.php @@ -0,0 +1,155 @@ + +// +---------------------------------------------------------------------- + +namespace think\cache\driver; + +use think\cache\Driver; + +/** + * Xcache缓存驱动 + * @author liu21st + */ +class Xcache extends Driver +{ + protected $options = [ + 'prefix' => '', + 'expire' => 0, + ]; + + /** + * 构造函数 + * @param array $options 缓存参数 + * @access public + * @throws \BadFunctionCallException + */ + public function __construct($options = []) + { + if (!function_exists('xcache_info')) { + throw new \BadFunctionCallException('not support: Xcache'); + } + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + } + + /** + * 判断缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name) + { + $key = $this->getCacheKey($name); + return xcache_isset($key); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = false) + { + $key = $this->getCacheKey($name); + return xcache_isset($key) ? xcache_get($key) : $default; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return boolean + */ + public function set($name, $value, $expire = null) + { + if (is_null($expire)) { + $expire = $this->options['expire']; + } + if ($expire instanceof \DateTime) { + $expire = $expire->getTimestamp() - time(); + } + if ($this->tag && !$this->has($name)) { + $first = true; + } + $key = $this->getCacheKey($name); + if (xcache_set($key, $value, $expire)) { + isset($first) && $this->setTagItem($key); + return true; + } + return false; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc($name, $step = 1) + { + $key = $this->getCacheKey($name); + return xcache_inc($key, $step); + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec($name, $step = 1) + { + $key = $this->getCacheKey($name); + return xcache_dec($key, $step); + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name) + { + return xcache_unset($this->getCacheKey($name)); + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return boolean + */ + public function clear($tag = null) + { + if ($tag) { + // 指定标签清除 + $keys = $this->getTagItem($tag); + foreach ($keys as $key) { + xcache_unset($key); + } + $this->rm('tag_' . md5($tag)); + return true; + } + if (function_exists('xcache_unset_by_prefix')) { + return xcache_unset_by_prefix($this->options['prefix']); + } else { + return false; + } + } +} diff --git a/thinkphp/library/think/config/driver/Ini.php b/thinkphp/library/think/config/driver/Ini.php new file mode 100644 index 0000000..bcd12b6 --- /dev/null +++ b/thinkphp/library/think/config/driver/Ini.php @@ -0,0 +1,24 @@ + +// +---------------------------------------------------------------------- + +namespace think\config\driver; + +class Ini +{ + public function parse($config) + { + if (is_file($config)) { + return parse_ini_file($config, true); + } else { + return parse_ini_string($config, true); + } + } +} diff --git a/thinkphp/library/think/config/driver/Json.php b/thinkphp/library/think/config/driver/Json.php new file mode 100644 index 0000000..479dcc8 --- /dev/null +++ b/thinkphp/library/think/config/driver/Json.php @@ -0,0 +1,24 @@ + +// +---------------------------------------------------------------------- + +namespace think\config\driver; + +class Json +{ + public function parse($config) + { + if (is_file($config)) { + $config = file_get_contents($config); + } + $result = json_decode($config, true); + return $result; + } +} diff --git a/thinkphp/library/think/config/driver/Xml.php b/thinkphp/library/think/config/driver/Xml.php new file mode 100644 index 0000000..1158519 --- /dev/null +++ b/thinkphp/library/think/config/driver/Xml.php @@ -0,0 +1,31 @@ + +// +---------------------------------------------------------------------- + +namespace think\config\driver; + +class Xml +{ + public function parse($config) + { + if (is_file($config)) { + $content = simplexml_load_file($config); + } else { + $content = simplexml_load_string($config); + } + $result = (array) $content; + foreach ($result as $key => $val) { + if (is_object($val)) { + $result[$key] = (array) $val; + } + } + return $result; + } +} diff --git a/thinkphp/library/think/console/Command.php b/thinkphp/library/think/console/Command.php new file mode 100644 index 0000000..d0caad2 --- /dev/null +++ b/thinkphp/library/think/console/Command.php @@ -0,0 +1,470 @@ + +// +---------------------------------------------------------------------- + +namespace think\console; + +use think\Console; +use think\console\input\Argument; +use think\console\input\Definition; +use think\console\input\Option; + +class Command +{ + + /** @var Console */ + private $console; + private $name; + private $aliases = []; + private $definition; + private $help; + private $description; + private $ignoreValidationErrors = false; + private $consoleDefinitionMerged = false; + private $consoleDefinitionMergedWithArgs = false; + private $code; + private $synopsis = []; + private $usages = []; + + /** @var Input */ + protected $input; + + /** @var Output */ + protected $output; + + /** + * 构造方法 + * @param string|null $name 命令名称,如果没有设置则比如在 configure() 里设置 + * @throws \LogicException + * @api + */ + public function __construct($name = null) + { + $this->definition = new Definition(); + + if (null !== $name) { + $this->setName($name); + } + + $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() + { + $this->ignoreValidationErrors = true; + } + + /** + * 设置控制台 + * @param Console $console + */ + public function setConsole(Console $console = null) + { + $this->console = $console; + } + + /** + * 获取控制台 + * @return Console + * @api + */ + public function getConsole() + { + return $this->console; + } + + /** + * 是否有效 + * @return bool + */ + public function isEnabled() + { + 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) + { + throw new \LogicException('You must override the execute() method in the concrete command class.'); + } + + /** + * 用户验证 + * @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) + { + $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 ($input->isInteractive()) { + $this->interact($input, $output); + } + + $input->validate(); + + if ($this->code) { + $statusCode = call_user_func($this->code, $input, $output); + } else { + $statusCode = $this->execute($input, $output); + } + + return is_numeric($statusCode) ? (int) $statusCode : 0; + } + + /** + * 设置执行代码 + * @param callable $code callable(InputInterface $input, OutputInterface $output) + * @return Command + * @throws \InvalidArgumentException + * @see execute() + */ + public function setCode(callable $code) + { + if (!is_callable($code)) { + throw new \InvalidArgumentException('Invalid callable provided to Command::setCode.'); + } + + if (PHP_VERSION_ID >= 50400 && $code instanceof \Closure) { + $r = new \ReflectionFunction($code); + if (null === $r->getClosureThis()) { + $code = \Closure::bind($code, $this); + } + } + + $this->code = $code; + + return $this; + } + + /** + * 合并参数定义 + * @param bool $mergeArgs + */ + public function mergeConsoleDefinition($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() + { + return $this->definition; + } + + /** + * 获取当前指令的参数定义 + * @return Definition + */ + public function getNativeDefinition() + { + return $this->getDefinition(); + } + + /** + * 添加参数 + * @param string $name 名称 + * @param int $mode 类型 + * @param string $description 描述 + * @param mixed $default 默认值 + * @return Command + */ + public function addArgument($name, $mode = null, $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($name, $shortcut = null, $mode = null, $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($name) + { + $this->validateName($name); + + $this->name = $name; + + return $this; + } + + /** + * 获取指令名称 + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * 设置描述 + * @param string $description + * @return Command + */ + public function setDescription($description) + { + $this->description = $description; + + return $this; + } + + /** + * 获取描述 + * @return string + */ + public function getDescription() + { + return $this->description; + } + + /** + * 设置帮助信息 + * @param string $help + * @return Command + */ + public function setHelp($help) + { + $this->help = $help; + + return $this; + } + + /** + * 获取帮助信息 + * @return string + */ + public function getHelp() + { + return $this->help; + } + + /** + * 描述信息 + * @return string + */ + public function getProcessedHelp() + { + $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($aliases) + { + if (!is_array($aliases) && !$aliases instanceof \Traversable) { + throw new \InvalidArgumentException('$aliases must be an array or an instance of \Traversable'); + } + + foreach ($aliases as $alias) { + $this->validateName($alias); + } + + $this->aliases = $aliases; + + return $this; + } + + /** + * 获取别名 + * @return array + */ + public function getAliases() + { + return $this->aliases; + } + + /** + * 获取简介 + * @param bool $short 是否简单的 + * @return string + */ + public function getSynopsis($short = false) + { + $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($usage) + { + if (0 !== strpos($usage, $this->name)) { + $usage = sprintf('%s %s', $this->name, $usage); + } + + $this->usages[] = $usage; + + return $this; + } + + /** + * 获取用法介绍 + * @return array + */ + public function getUsages() + { + return $this->usages; + } + + /** + * 验证指令名称 + * @param string $name + * @throws \InvalidArgumentException + */ + private function validateName($name) + { + if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) { + throw new \InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name)); + } + } +} diff --git a/thinkphp/library/think/console/Input.php b/thinkphp/library/think/console/Input.php new file mode 100644 index 0000000..2482dfd --- /dev/null +++ b/thinkphp/library/think/console/Input.php @@ -0,0 +1,464 @@ + +// +---------------------------------------------------------------------- + +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) + { + $this->arguments = []; + $this->options = []; + $this->definition = $definition; + + $this->parse(); + } + + /** + * 解析参数 + */ + protected function parse() + { + $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($token) + { + $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($name) + { + $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($token) + { + $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($token) + { + $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($shortcut, $value) + { + 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($name, $value) + { + 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) + { + $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() + { + return $this->interactive; + } + + /** + * 设置输入的交互 + * @param bool + */ + public function setInteractive($interactive) + { + $this->interactive = (bool) $interactive; + } + + /** + * 获取所有的参数 + * @return Argument[] + */ + public function getArguments() + { + return array_merge($this->definition->getArgumentDefaults(), $this->arguments); + } + + /** + * 根据名称获取参数 + * @param string $name 参数名 + * @return mixed + * @throws \InvalidArgumentException + */ + public function getArgument($name) + { + if (!$this->definition->hasArgument($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + return isset($this->arguments[$name]) ? $this->arguments[$name] : $this->definition->getArgument($name) + ->getDefault(); + } + + /** + * 设置参数的值 + * @param string $name 参数名 + * @param string $value 值 + * @throws \InvalidArgumentException + */ + public function setArgument($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) + { + return $this->definition->hasArgument($name); + } + + /** + * 获取所有的选项 + * @return Option[] + */ + public function getOptions() + { + return array_merge($this->definition->getOptionDefaults(), $this->options); + } + + /** + * 获取选项值 + * @param string $name 选项名称 + * @return mixed + * @throws \InvalidArgumentException + */ + public function getOption($name) + { + if (!$this->definition->hasOption($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); + } + + return isset($this->options[$name]) ? $this->options[$name] : $this->definition->getOption($name)->getDefault(); + } + + /** + * 设置选项值 + * @param string $name 选项名 + * @param string|bool $value 值 + * @throws \InvalidArgumentException + */ + public function setOption($name, $value) + { + 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($name) + { + return $this->definition->hasOption($name) && isset($this->options[$name]); + } + + /** + * 转义指令 + * @param string $token + * @return string + */ + public function escapeToken($token) + { + 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/thinkphp/library/think/console/LICENSE b/thinkphp/library/think/console/LICENSE new file mode 100644 index 0000000..0abe056 --- /dev/null +++ b/thinkphp/library/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/thinkphp/library/think/console/Output.php b/thinkphp/library/think/console/Output.php new file mode 100644 index 0000000..65dc9fb --- /dev/null +++ b/thinkphp/library/think/console/Output.php @@ -0,0 +1,222 @@ + +// +---------------------------------------------------------------------- + +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; + +/** + * 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($style, $message) + { + $this->writeln("<{$style}>{$message}"); + } + + /** + * 输出空行 + * @param int $count + */ + public function newLine($count = 1) + { + $this->write(str_repeat(PHP_EOL, $count)); + } + + /** + * 输出信息并换行 + * @param string $messages + * @param int $type + */ + public function writeln($messages, $type = self::OUTPUT_NORMAL) + { + $this->write($messages, true, $type); + } + + /** + * 输出信息 + * @param string $messages + * @param bool $newline + * @param int $type + */ + public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL) + { + $this->handle->write($messages, $newline, $type); + } + + public function renderException(\Exception $e) + { + $this->handle->renderException($e); + } + + /** + * {@inheritdoc} + */ + public function setVerbosity($level) + { + $this->verbosity = (int) $level; + } + + /** + * {@inheritdoc} + */ + public function getVerbosity() + { + return $this->verbosity; + } + + public function isQuiet() + { + return self::VERBOSITY_QUIET === $this->verbosity; + } + + public function isVerbose() + { + return self::VERBOSITY_VERBOSE <= $this->verbosity; + } + + public function isVeryVerbose() + { + return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity; + } + + public function isDebug() + { + return self::VERBOSITY_DEBUG <= $this->verbosity; + } + + public function describe($object, array $options = []) + { + $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/thinkphp/library/think/console/bin/README.md b/thinkphp/library/think/console/bin/README.md new file mode 100644 index 0000000..9acc52f --- /dev/null +++ b/thinkphp/library/think/console/bin/README.md @@ -0,0 +1 @@ +console 工具使用 hiddeninput.exe 在 windows 上隐藏密码输入,该二进制文件由第三方提供,相关源码和其他细节可以在 [Hidden Input](https://github.com/Seldaek/hidden-input) 找到。 diff --git a/thinkphp/library/think/console/bin/hiddeninput.exe b/thinkphp/library/think/console/bin/hiddeninput.exe new file mode 100644 index 0000000000000000000000000000000000000000..c8cf65e8d819e6e525121cf6b21f1c2429746038 GIT binary patch literal 9216 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 J0sQ~1{4brj`U(I5 literal 0 HcmV?d00001 diff --git a/thinkphp/library/think/console/command/Build.php b/thinkphp/library/think/console/command/Build.php new file mode 100644 index 0000000..39806c3 --- /dev/null +++ b/thinkphp/library/think/console/command/Build.php @@ -0,0 +1,56 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command; + +use think\console\Command; +use think\console\Input; +use think\console\input\Option; +use think\console\Output; + +class Build extends Command +{ + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->setName('build') + ->setDefinition([ + new Option('config', null, Option::VALUE_OPTIONAL, "build.php path"), + new Option('module', null, Option::VALUE_OPTIONAL, "module name"), + ]) + ->setDescription('Build Application Dirs'); + } + + protected function execute(Input $input, Output $output) + { + if ($input->hasOption('module')) { + \think\Build::module($input->getOption('module')); + $output->writeln("Successed"); + return; + } + + if ($input->hasOption('config')) { + $build = include $input->getOption('config'); + } else { + $build = include APP_PATH . 'build.php'; + } + if (empty($build)) { + $output->writeln("Build Config Is Empty"); + return; + } + \think\Build::run($build); + $output->writeln("Successed"); + + } +} diff --git a/thinkphp/library/think/console/command/Clear.php b/thinkphp/library/think/console/command/Clear.php new file mode 100644 index 0000000..1b5102e --- /dev/null +++ b/thinkphp/library/think/console/command/Clear.php @@ -0,0 +1,63 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command; + +use think\Cache; +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('type', Argument::OPTIONAL, 'type to clear', null) + ->addOption('path', 'd', Option::VALUE_OPTIONAL, 'path to clear', null) + ->setDescription('Clear runtime file'); + } + + protected function execute(Input $input, Output $output) + { + $path = $input->getOption('path') ?: RUNTIME_PATH; + + $type = $input->getArgument('type'); + + if ($type == 'route') { + Cache::clear('route_check'); + } else { + if (is_dir($path)) { + $this->clearPath($path); + } + } + + $output->writeln("Clear Successed"); + } + + protected function clearPath($path) + { + $path = realpath($path) . DS; + $files = scandir($path); + if ($files) { + foreach ($files as $file) { + if ('.' != $file && '..' != $file && is_dir($path . $file)) { + $this->clearPath($path . $file); + } elseif ('.gitignore' != $file && is_file($path . $file)) { + unlink($path . $file); + } + } + } + } +} diff --git a/thinkphp/library/think/console/command/Help.php b/thinkphp/library/think/console/command/Help.php new file mode 100644 index 0000000..bae2c65 --- /dev/null +++ b/thinkphp/library/think/console/command/Help.php @@ -0,0 +1,69 @@ + +// +---------------------------------------------------------------------- + +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(<<%command.name% command displays help for a given command: + + php %command.full_name% list + +To display the list of available commands, please use the list command. +EOF + ); + } + + /** + * Sets the command. + * @param Command $command The command to set + */ + public function setCommand(Command $command) + { + $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/thinkphp/library/think/console/command/Lists.php b/thinkphp/library/think/console/command/Lists.php new file mode 100644 index 0000000..084ddaa --- /dev/null +++ b/thinkphp/library/think/console/command/Lists.php @@ -0,0 +1,74 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command; + +use think\console\Command; +use think\console\Input; +use think\console\Output; +use think\console\input\Argument as InputArgument; +use think\console\input\Option as InputOption; +use think\console\input\Definition as InputDefinition; + +class Lists extends Command +{ + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->setName('list')->setDefinition($this->createDefinition())->setDescription('Lists commands')->setHelp(<<%command.name% command lists all commands: + + php %command.full_name% + +You can also display the commands for a specific namespace: + + php %command.full_name% test + +It's also possible to get raw list of commands (useful for embedding command runner): + + php %command.full_name% --raw +EOF + ); + } + + /** + * {@inheritdoc} + */ + public function getNativeDefinition() + { + 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() + { + 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/thinkphp/library/think/console/command/Make.php b/thinkphp/library/think/console/command/Make.php new file mode 100644 index 0000000..d1daf34 --- /dev/null +++ b/thinkphp/library/think/console/command/Make.php @@ -0,0 +1,110 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command; + +use think\App; +use think\Config; +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('' . $this->type . ' already exists!'); + return false; + } + + if (!is_dir(dirname($pathname))) { + mkdir(strtolower(dirname($pathname)), 0755, true); + } + + file_put_contents($pathname, $this->buildClass($classname)); + + $output->writeln('' . $this->type . ' created successfully.'); + + } + + protected function buildClass($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%}', '{%namespace%}', '{%app_namespace%}'], [ + $class, + $namespace, + App::$namespace, + ], $stub); + + } + + protected function getPathName($name) + { + $name = str_replace(App::$namespace . '\\', '', $name); + + return APP_PATH . str_replace('\\', '/', $name) . '.php'; + } + + protected function getClassName($name) + { + $appNamespace = App::$namespace; + + if (strpos($name, $appNamespace . '\\') === 0) { + return $name; + } + + if (Config::get('app_multi_module')) { + if (strpos($name, '/')) { + list($module, $name) = explode('/', $name, 2); + } else { + $module = 'common'; + } + } else { + $module = null; + } + + if (strpos($name, '/') !== false) { + $name = str_replace('/', '\\', $name); + } + + return $this->getNamespace($appNamespace, $module) . '\\' . $name; + } + + protected function getNamespace($appNamespace, $module) + { + return $module ? ($appNamespace . '\\' . $module) : $appNamespace; + } + +} diff --git a/thinkphp/library/think/console/command/make/Controller.php b/thinkphp/library/think/console/command/make/Controller.php new file mode 100644 index 0000000..afa7be9 --- /dev/null +++ b/thinkphp/library/think/console/command/make/Controller.php @@ -0,0 +1,50 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command\make; + +use think\Config; +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('plain', null, Option::VALUE_NONE, 'Generate an empty controller class.') + ->setDescription('Create a new resource controller class'); + } + + protected function getStub() + { + if ($this->input->getOption('plain')) { + return __DIR__ . '/stubs/controller.plain.stub'; + } + + return __DIR__ . '/stubs/controller.stub'; + } + + protected function getClassName($name) + { + return parent::getClassName($name) . (Config::get('controller_suffix') ? ucfirst(Config::get('url_controller_layer')) : ''); + } + + protected function getNamespace($appNamespace, $module) + { + return parent::getNamespace($appNamespace, $module) . '\controller'; + } + +} diff --git a/thinkphp/library/think/console/command/make/Model.php b/thinkphp/library/think/console/command/make/Model.php new file mode 100644 index 0000000..d4e9b5d --- /dev/null +++ b/thinkphp/library/think/console/command/make/Model.php @@ -0,0 +1,36 @@ + +// +---------------------------------------------------------------------- + +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() + { + return __DIR__ . '/stubs/model.stub'; + } + + protected function getNamespace($appNamespace, $module) + { + return parent::getNamespace($appNamespace, $module) . '\model'; + } +} diff --git a/thinkphp/library/think/console/command/make/stubs/controller.plain.stub b/thinkphp/library/think/console/command/make/stubs/controller.plain.stub new file mode 100644 index 0000000..b7539dc --- /dev/null +++ b/thinkphp/library/think/console/command/make/stubs/controller.plain.stub @@ -0,0 +1,10 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command\optimize; + +use think\App; +use think\Config; +use think\console\Command; +use think\console\Input; +use think\console\Output; + +class Autoload extends Command +{ + + protected function configure() + { + $this->setName('optimize:autoload') + ->setDescription('Optimizes PSR0 and PSR4 packages to be loaded with classmaps too, good for production.'); + } + + protected function execute(Input $input, Output $output) + { + + $classmapFile = << realpath(rtrim(APP_PATH)), + 'think\\' => LIB_PATH . 'think', + 'behavior\\' => LIB_PATH . 'behavior', + 'traits\\' => LIB_PATH . 'traits', + '' => realpath(rtrim(EXTEND_PATH)), + ]; + + $root_namespace = Config::get('root_namespace'); + foreach ($root_namespace as $namespace => $dir) { + $namespacesToScan[$namespace . '\\'] = realpath($dir); + } + + krsort($namespacesToScan); + $classMap = []; + foreach ($namespacesToScan as $namespace => $dir) { + + if (!is_dir($dir)) { + continue; + } + + $namespaceFilter = $namespace === '' ? null : $namespace; + $classMap = $this->addClassMapCode($dir, $namespaceFilter, $classMap); + } + + ksort($classMap); + foreach ($classMap as $class => $code) { + $classmapFile .= ' ' . var_export($class, true) . ' => ' . $code; + } + $classmapFile .= "];\n"; + + if (!is_dir(RUNTIME_PATH)) { + @mkdir(RUNTIME_PATH, 0755, true); + } + + file_put_contents(RUNTIME_PATH . 'classmap' . EXT, $classmapFile); + + $output->writeln('Succeed!'); + } + + protected function addClassMapCode($dir, $namespace, $classMap) + { + foreach ($this->createMap($dir, $namespace) as $class => $path) { + + $pathCode = $this->getPathCode($path) . ",\n"; + + if (!isset($classMap[$class])) { + $classMap[$class] = $pathCode; + } elseif ($classMap[$class] !== $pathCode && !preg_match('{/(test|fixture|example|stub)s?/}i', strtr($classMap[$class] . ' ' . $path, '\\', '/'))) { + $this->output->writeln( + 'Warning: Ambiguous class resolution, "' . $class . '"' . + ' was found in both "' . str_replace(["',\n"], [ + '', + ], $classMap[$class]) . '" and "' . $path . '", the first will be used.' + ); + } + } + return $classMap; + } + + protected function getPathCode($path) + { + + $baseDir = ''; + $libPath = $this->normalizePath(realpath(LIB_PATH)); + $appPath = $this->normalizePath(realpath(APP_PATH)); + $extendPath = $this->normalizePath(realpath(EXTEND_PATH)); + $rootPath = $this->normalizePath(realpath(ROOT_PATH)); + $path = $this->normalizePath($path); + + if ($libPath !== null && strpos($path, $libPath . '/') === 0) { + $path = substr($path, strlen(LIB_PATH)); + $baseDir = 'LIB_PATH'; + } elseif ($appPath !== null && strpos($path, $appPath . '/') === 0) { + $path = substr($path, strlen($appPath) + 1); + $baseDir = 'APP_PATH'; + } elseif ($extendPath !== null && strpos($path, $extendPath . '/') === 0) { + $path = substr($path, strlen($extendPath) + 1); + $baseDir = 'EXTEND_PATH'; + } elseif ($rootPath !== null && strpos($path, $rootPath . '/') === 0) { + $path = substr($path, strlen($rootPath) + 1); + $baseDir = 'ROOT_PATH'; + } + + if ($path !== false) { + $baseDir .= " . "; + } + + return $baseDir . (($path !== false) ? var_export($path, true) : ""); + } + + protected function normalizePath($path) + { + if ($path === false) { + return; + } + $parts = []; + $path = strtr($path, '\\', '/'); + $prefix = ''; + $absolute = false; + + if (preg_match('{^([0-9a-z]+:(?://(?:[a-z]:)?)?)}i', $path, $match)) { + $prefix = $match[1]; + $path = substr($path, strlen($prefix)); + } + + if (substr($path, 0, 1) === '/') { + $absolute = true; + $path = substr($path, 1); + } + + $up = false; + foreach (explode('/', $path) as $chunk) { + if ('..' === $chunk && ($absolute || $up)) { + array_pop($parts); + $up = !(empty($parts) || '..' === end($parts)); + } elseif ('.' !== $chunk && '' !== $chunk) { + $parts[] = $chunk; + $up = '..' !== $chunk; + } + } + + return $prefix . ($absolute ? '/' : '') . implode('/', $parts); + } + + protected function createMap($path, $namespace = null) + { + if (is_string($path)) { + if (is_file($path)) { + $path = [new \SplFileInfo($path)]; + } elseif (is_dir($path)) { + + $objects = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path), \RecursiveIteratorIterator::SELF_FIRST); + + $path = []; + + /** @var \SplFileInfo $object */ + foreach ($objects as $object) { + if ($object->isFile() && $object->getExtension() == 'php') { + $path[] = $object; + } + } + } else { + throw new \RuntimeException( + 'Could not scan for classes inside "' . $path . + '" which does not appear to be a file nor a folder' + ); + } + } + + $map = []; + + /** @var \SplFileInfo $file */ + foreach ($path as $file) { + $filePath = $file->getRealPath(); + + if (pathinfo($filePath, PATHINFO_EXTENSION) != 'php') { + continue; + } + + $classes = $this->findClasses($filePath); + + foreach ($classes as $class) { + if (null !== $namespace && 0 !== strpos($class, $namespace)) { + continue; + } + + if (!isset($map[$class])) { + $map[$class] = $filePath; + } elseif ($map[$class] !== $filePath && !preg_match('{/(test|fixture|example|stub)s?/}i', strtr($map[$class] . ' ' . $filePath, '\\', '/'))) { + $this->output->writeln( + 'Warning: Ambiguous class resolution, "' . $class . '"' . + ' was found in both "' . $map[$class] . '" and "' . $filePath . '", the first will be used.' + ); + } + } + } + + return $map; + } + + protected function findClasses($path) + { + $extraTypes = '|trait'; + + $contents = @php_strip_whitespace($path); + if (!$contents) { + if (!file_exists($path)) { + $message = 'File at "%s" does not exist, check your classmap definitions'; + } elseif (!is_readable($path)) { + $message = 'File at "%s" is not readable, check its permissions'; + } elseif ('' === trim(file_get_contents($path))) { + return []; + } else { + $message = 'File at "%s" could not be parsed as PHP, it may be binary or corrupted'; + } + $error = error_get_last(); + if (isset($error['message'])) { + $message .= PHP_EOL . 'The following message may be helpful:' . PHP_EOL . $error['message']; + } + throw new \RuntimeException(sprintf($message, $path)); + } + + if (!preg_match('{\b(?:class|interface' . $extraTypes . ')\s}i', $contents)) { + return []; + } + + // strip heredocs/nowdocs + $contents = preg_replace('{<<<\s*(\'?)(\w+)\\1(?:\r\n|\n|\r)(?:.*?)(?:\r\n|\n|\r)\\2(?=\r\n|\n|\r|;)}s', 'null', $contents); + // strip strings + $contents = preg_replace('{"[^"\\\\]*+(\\\\.[^"\\\\]*+)*+"|\'[^\'\\\\]*+(\\\\.[^\'\\\\]*+)*+\'}s', 'null', $contents); + // strip leading non-php code if needed + if (substr($contents, 0, 2) !== '.+<\?}s', '?>'); + if (false !== $pos && false === strpos(substr($contents, $pos), '])(?Pclass|interface' . $extraTypes . ') \s++ (?P[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*+) + | \b(?])(?Pnamespace) (?P\s++[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\s*+\\\\\s*+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+)? \s*+ [\{;] + ) + }ix', $contents, $matches); + + $classes = []; + $namespace = ''; + + for ($i = 0, $len = count($matches['type']); $i < $len; $i++) { + if (!empty($matches['ns'][$i])) { + $namespace = str_replace([' ', "\t", "\r", "\n"], '', $matches['nsname'][$i]) . '\\'; + } else { + $name = $matches['name'][$i]; + if ($name[0] === ':') { + $name = 'xhp' . substr(str_replace(['-', ':'], ['_', '__'], $name), 1); + } elseif ($matches['type'][$i] === 'enum') { + $name = rtrim($name, ':'); + } + $classes[] = ltrim($namespace . $name, '\\'); + } + } + + return $classes; + } + +} diff --git a/thinkphp/library/think/console/command/optimize/Config.php b/thinkphp/library/think/console/command/optimize/Config.php new file mode 100644 index 0000000..59c69a8 --- /dev/null +++ b/thinkphp/library/think/console/command/optimize/Config.php @@ -0,0 +1,93 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command\optimize; + +use think\Config as ThinkConfig; +use think\console\Command; +use think\console\Input; +use think\console\input\Argument; +use think\console\Output; + +class Config extends Command +{ + /** @var Output */ + protected $output; + + protected function configure() + { + $this->setName('optimize:config') + ->addArgument('module', Argument::OPTIONAL, 'Build module config cache .') + ->setDescription('Build config and common file cache.'); + } + + protected function execute(Input $input, Output $output) + { + if ($input->getArgument('module')) { + $module = $input->getArgument('module') . DS; + } else { + $module = ''; + } + + $content = 'buildCacheContent($module); + + if (!is_dir(RUNTIME_PATH . $module)) { + @mkdir(RUNTIME_PATH . $module, 0755, true); + } + + file_put_contents(RUNTIME_PATH . $module . 'init' . EXT, $content); + + $output->writeln('Succeed!'); + } + + protected function buildCacheContent($module) + { + $content = ''; + $path = realpath(APP_PATH . $module) . DS; + + if ($module) { + // 加载模块配置 + $config = ThinkConfig::load(CONF_PATH . $module . 'config' . CONF_EXT); + + // 读取数据库配置文件 + $filename = CONF_PATH . $module . 'database' . CONF_EXT; + ThinkConfig::load($filename, 'database'); + + // 加载应用状态配置 + if ($config['app_status']) { + $config = ThinkConfig::load(CONF_PATH . $module . $config['app_status'] . CONF_EXT); + } + // 读取扩展配置文件 + if (is_dir(CONF_PATH . $module . 'extra')) { + $dir = CONF_PATH . $module . 'extra'; + $files = scandir($dir); + foreach ($files as $file) { + if (strpos($file, CONF_EXT)) { + $filename = $dir . DS . $file; + ThinkConfig::load($filename, pathinfo($file, PATHINFO_FILENAME)); + } + } + } + } + + // 加载行为扩展文件 + if (is_file(CONF_PATH . $module . 'tags' . EXT)) { + $content .= '\think\Hook::import(' . (var_export(include CONF_PATH . $module . 'tags' . EXT, true)) . ');' . PHP_EOL; + } + + // 加载公共文件 + if (is_file($path . 'common' . EXT)) { + $content .= substr(php_strip_whitespace($path . 'common' . EXT), 5) . PHP_EOL; + } + + $content .= '\think\Config::set(' . var_export(ThinkConfig::get(), true) . ');'; + return $content; + } +} diff --git a/thinkphp/library/think/console/command/optimize/Route.php b/thinkphp/library/think/console/command/optimize/Route.php new file mode 100644 index 0000000..6da1d9a --- /dev/null +++ b/thinkphp/library/think/console/command/optimize/Route.php @@ -0,0 +1,75 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command\optimize; + +use think\console\Command; +use think\console\Input; +use think\console\Output; + +class Route extends Command +{ + /** @var Output */ + protected $output; + + protected function configure() + { + $this->setName('optimize:route') + ->setDescription('Build route cache.'); + } + + protected function execute(Input $input, Output $output) + { + + if (!is_dir(RUNTIME_PATH)) { + @mkdir(RUNTIME_PATH, 0755, true); + } + + file_put_contents(RUNTIME_PATH . 'route.php', $this->buildRouteCache()); + $output->writeln('Succeed!'); + } + + protected function buildRouteCache() + { + $files = \think\Config::get('route_config_file'); + foreach ($files as $file) { + if (is_file(CONF_PATH . $file . CONF_EXT)) { + $config = include CONF_PATH . $file . CONF_EXT; + if (is_array($config)) { + \think\Route::import($config); + } + } + } + $rules = \think\Route::rules(true); + array_walk_recursive($rules, [$this, 'buildClosure']); + $content = 'getStartLine(); + $endLine = $reflection->getEndLine(); + $file = $reflection->getFileName(); + $item = file($file); + $content = ''; + for ($i = $startLine - 1; $i <= $endLine - 1; $i++) { + $content .= $item[$i]; + } + $start = strpos($content, 'function'); + $end = strrpos($content, '}'); + $value = '[__start__' . substr($content, $start, $end - $start + 1) . '__end__]'; + } + } +} diff --git a/thinkphp/library/think/console/command/optimize/Schema.php b/thinkphp/library/think/console/command/optimize/Schema.php new file mode 100644 index 0000000..3353424 --- /dev/null +++ b/thinkphp/library/think/console/command/optimize/Schema.php @@ -0,0 +1,118 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command\optimize; + +use think\App; +use think\console\Command; +use think\console\Input; +use think\console\input\Option; +use think\console\Output; +use think\Db; + +class Schema extends Command +{ + /** @var Output */ + protected $output; + + protected function configure() + { + $this->setName('optimize:schema') + ->addOption('config', null, Option::VALUE_REQUIRED, 'db config .') + ->addOption('db', null, Option::VALUE_REQUIRED, 'db name .') + ->addOption('table', null, Option::VALUE_REQUIRED, 'table name .') + ->addOption('module', null, Option::VALUE_REQUIRED, 'module name .') + ->setDescription('Build database schema cache.'); + } + + protected function execute(Input $input, Output $output) + { + if (!is_dir(RUNTIME_PATH . 'schema')) { + @mkdir(RUNTIME_PATH . 'schema', 0755, true); + } + $config = []; + if ($input->hasOption('config')) { + $config = $input->getOption('config'); + } + if ($input->hasOption('module')) { + $module = $input->getOption('module'); + // 读取模型 + $path = APP_PATH . $module . DS . 'model'; + $list = is_dir($path) ? scandir($path) : []; + $app = App::$namespace; + foreach ($list as $file) { + if (0 === strpos($file, '.')) { + continue; + } + $class = '\\' . $app . '\\' . $module . '\\model\\' . pathinfo($file, PATHINFO_FILENAME); + $this->buildModelSchema($class); + } + $output->writeln('Succeed!'); + return; + } elseif ($input->hasOption('table')) { + $table = $input->getOption('table'); + if (!strpos($table, '.')) { + $dbName = Db::connect($config)->getConfig('database'); + } + $tables[] = $table; + } elseif ($input->hasOption('db')) { + $dbName = $input->getOption('db'); + $tables = Db::connect($config)->getTables($dbName); + } elseif (!\think\Config::get('app_multi_module')) { + $app = App::$namespace; + $path = APP_PATH . 'model'; + $list = is_dir($path) ? scandir($path) : []; + foreach ($list as $file) { + if (0 === strpos($file, '.')) { + continue; + } + $class = '\\' . $app . '\\model\\' . pathinfo($file, PATHINFO_FILENAME); + $this->buildModelSchema($class); + } + $output->writeln('Succeed!'); + return; + } else { + $tables = Db::connect($config)->getTables(); + } + + $db = isset($dbName) ? $dbName . '.' : ''; + $this->buildDataBaseSchema($tables, $db, $config); + + $output->writeln('Succeed!'); + } + + protected function buildModelSchema($class) + { + $reflect = new \ReflectionClass($class); + if (!$reflect->isAbstract() && $reflect->isSubclassOf('\think\Model')) { + $table = $class::getTable(); + $dbName = $class::getConfig('database'); + $content = 'getFields($table); + $content .= var_export($info, true) . ';'; + file_put_contents(RUNTIME_PATH . 'schema' . DS . $dbName . '.' . $table . EXT, $content); + } + } + + protected function buildDataBaseSchema($tables, $db, $config) + { + if ('' == $db) { + $dbName = Db::connect($config)->getConfig('database') . '.'; + } else { + $dbName = $db; + } + foreach ($tables as $table) { + $content = 'getFields($db . $table); + $content .= var_export($info, true) . ';'; + file_put_contents(RUNTIME_PATH . 'schema' . DS . $dbName . $table . EXT, $content); + } + } +} diff --git a/thinkphp/library/think/console/input/Argument.php b/thinkphp/library/think/console/input/Argument.php new file mode 100644 index 0000000..16223bb --- /dev/null +++ b/thinkphp/library/think/console/input/Argument.php @@ -0,0 +1,115 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\input; + +class Argument +{ + + const REQUIRED = 1; + const OPTIONAL = 2; + const IS_ARRAY = 4; + + private $name; + private $mode; + private $default; + 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($name, $mode = null, $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() + { + return $this->name; + } + + /** + * 是否必须 + * @return bool + */ + public function isRequired() + { + return self::REQUIRED === (self::REQUIRED & $this->mode); + } + + /** + * 该参数是否接受数组 + * @return bool + */ + public function isArray() + { + return self::IS_ARRAY === (self::IS_ARRAY & $this->mode); + } + + /** + * 设置默认值 + * @param mixed $default 默认值 + * @throws \LogicException + */ + public function setDefault($default = null) + { + 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() + { + return $this->description; + } +} diff --git a/thinkphp/library/think/console/input/Definition.php b/thinkphp/library/think/console/input/Definition.php new file mode 100644 index 0000000..c71977e --- /dev/null +++ b/thinkphp/library/think/console/input/Definition.php @@ -0,0 +1,375 @@ + +// +---------------------------------------------------------------------- + +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) + { + $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($arguments = []) + { + $this->arguments = []; + $this->requiredCount = 0; + $this->hasOptional = false; + $this->hasAnArrayArgument = false; + $this->addArguments($arguments); + } + + /** + * 添加参数 + * @param Argument[] $arguments 参数数组 + * @api + */ + public function addArguments($arguments = []) + { + if (null !== $arguments) { + foreach ($arguments as $argument) { + $this->addArgument($argument); + } + } + } + + /** + * 添加一个参数 + * @param Argument $argument 参数 + * @throws \LogicException + */ + public function addArgument(Argument $argument) + { + 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) + { + 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) + { + $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments; + + return isset($arguments[$name]); + } + + /** + * 获取所有的参数 + * @return Argument[] 参数数组 + */ + public function getArguments() + { + return $this->arguments; + } + + /** + * 获取参数数量 + * @return int + */ + public function getArgumentCount() + { + return $this->hasAnArrayArgument ? PHP_INT_MAX : count($this->arguments); + } + + /** + * 获取必填的参数的数量 + * @return int + */ + public function getArgumentRequiredCount() + { + return $this->requiredCount; + } + + /** + * 获取参数默认值 + * @return array + */ + public function getArgumentDefaults() + { + $values = []; + foreach ($this->arguments as $argument) { + $values[$argument->getName()] = $argument->getDefault(); + } + + return $values; + } + + /** + * 设置选项 + * @param Option[] $options 选项数组 + */ + public function setOptions($options = []) + { + $this->options = []; + $this->shortcuts = []; + $this->addOptions($options); + } + + /** + * 添加选项 + * @param Option[] $options 选项数组 + * @api + */ + public function addOptions($options = []) + { + foreach ($options as $option) { + $this->addOption($option); + } + } + + /** + * 添加一个选项 + * @param Option $option 选项 + * @throws \LogicException + * @api + */ + public function addOption(Option $option) + { + 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($name) + { + 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($name) + { + return isset($this->options[$name]); + } + + /** + * 获取所有选项 + * @return Option[] + * @api + */ + public function getOptions() + { + return $this->options; + } + + /** + * 根据名称检查某个选项是否有短名称 + * @param string $name 短名称 + * @return bool + */ + public function hasShortcut($name) + { + return isset($this->shortcuts[$name]); + } + + /** + * 根据短名称获取选项 + * @param string $shortcut 短名称 + * @return Option + */ + public function getOptionForShortcut($shortcut) + { + return $this->getOption($this->shortcutToName($shortcut)); + } + + /** + * 获取所有选项的默认值 + * @return array + */ + public function getOptionDefaults() + { + $values = []; + foreach ($this->options as $option) { + $values[$option->getName()] = $option->getDefault(); + } + + return $values; + } + + /** + * 根据短名称获取选项名 + * @param string $shortcut 短名称 + * @return string + * @throws \InvalidArgumentException + */ + private function shortcutToName($shortcut) + { + 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($short = false) + { + $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/thinkphp/library/think/console/input/Option.php b/thinkphp/library/think/console/input/Option.php new file mode 100644 index 0000000..e5707c9 --- /dev/null +++ b/thinkphp/library/think/console/input/Option.php @@ -0,0 +1,190 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\input; + +class Option +{ + + const VALUE_NONE = 1; + const VALUE_REQUIRED = 2; + const VALUE_OPTIONAL = 4; + const VALUE_IS_ARRAY = 8; + + private $name; + private $shortcut; + private $mode; + private $default; + 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 = null; + } + + if (null !== $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() + { + return $this->shortcut; + } + + /** + * 获取选项名 + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * 是否可以设置值 + * @return bool 类型不是 self::VALUE_NONE 的时候返回true,其他均返回false + */ + public function acceptValue() + { + return $this->isValueRequired() || $this->isValueOptional(); + } + + /** + * 是否必须 + * @return bool 类型是 self::VALUE_REQUIRED 的时候返回true,其他均返回false + */ + public function isValueRequired() + { + return self::VALUE_REQUIRED === (self::VALUE_REQUIRED & $this->mode); + } + + /** + * 是否可选 + * @return bool 类型是 self::VALUE_OPTIONAL 的时候返回true,其他均返回false + */ + public function isValueOptional() + { + return self::VALUE_OPTIONAL === (self::VALUE_OPTIONAL & $this->mode); + } + + /** + * 选项值是否接受数组 + * @return bool 类型是 self::VALUE_IS_ARRAY 的时候返回true,其他均返回false + */ + public function isArray() + { + 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() + { + return $this->description; + } + + /** + * 检查所给选项是否是当前这个 + * @param Option $option + * @return bool + */ + public function equals(Option $option) + { + 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/thinkphp/library/think/console/output/Ask.php b/thinkphp/library/think/console/output/Ask.php new file mode 100644 index 0000000..3933eb2 --- /dev/null +++ b/thinkphp/library/think/console/output/Ask.php @@ -0,0 +1,340 @@ + +// +---------------------------------------------------------------------- + +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(''); + + if (isset($tmpExe)) { + unlink($tmpExe); + } + + 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(' %s:', $text); + + break; + + case $this->question instanceof Confirmation: + $text = sprintf(' %s (yes/no) [%s]:', $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(' %s [%s]:', $text, implode(', ', $default)); + + break; + + case $this->question instanceof Choice: + $choices = $this->question->getChoices(); + $text = sprintf(' %s [%s]:', $text, $choices[$default]); + + break; + + default: + $text = sprintf(' %s [%s]:', $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(" [%-${width}s] %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/thinkphp/library/think/console/output/Descriptor.php b/thinkphp/library/think/console/output/Descriptor.php new file mode 100644 index 0000000..23dc648 --- /dev/null +++ b/thinkphp/library/think/console/output/Descriptor.php @@ -0,0 +1,319 @@ + +// +---------------------------------------------------------------------- + +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 void + */ + protected function describeInputArgument(InputArgument $argument, array $options = []) + { + if (null !== $argument->getDefault() + && (!is_array($argument->getDefault()) + || count($argument->getDefault())) + ) { + $default = sprintf(' [default: %s]', $this->formatDefaultValue($argument->getDefault())); + } else { + $default = ''; + } + + $totalWidth = isset($options['total_width']) ? $options['total_width'] : strlen($argument->getName()); + $spacingWidth = $totalWidth - strlen($argument->getName()) + 2; + + $this->writeText(sprintf(" %s%s%s%s", $argument->getName(), str_repeat(' ', $spacingWidth), // + 17 = 2 spaces + + + 2 spaces + preg_replace('/\s*\R\s*/', PHP_EOL . str_repeat(' ', $totalWidth + 17), $argument->getDescription()), $default), $options); + } + + /** + * 描述选项 + * @param InputOption $option + * @param array $options + * @return void + */ + protected function describeInputOption(InputOption $option, array $options = []) + { + if ($option->acceptValue() && null !== $option->getDefault() + && (!is_array($option->getDefault()) + || count($option->getDefault())) + ) { + $default = sprintf(' [default: %s]', $this->formatDefaultValue($option->getDefault())); + } else { + $default = ''; + } + + $value = ''; + if ($option->acceptValue()) { + $value = '=' . strtoupper($option->getName()); + + if ($option->isValueOptional()) { + $value = '[' . $value . ']'; + } + } + + $totalWidth = isset($options['total_width']) ? $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(" %s%s%s%s%s", $synopsis, str_repeat(' ', $spacingWidth), // + 17 = 2 spaces + + + 2 spaces + preg_replace('/\s*\R\s*/', "\n" . str_repeat(' ', $totalWidth + 17), $option->getDescription()), $default, $option->isArray() ? ' (multiple values allowed)' : ''), $options); + } + + /** + * 描述输入 + * @param InputDefinition $definition + * @param array $options + * @return void + */ + 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('Arguments:', $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('Options:', $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 void + */ + protected function describeCommand(Command $command, array $options = []) + { + $command->getSynopsis(true); + $command->getSynopsis(false); + $command->mergeConsoleDefinition(false); + + $this->writeText('Usage:', $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('Help:', $options); + $this->writeText("\n"); + $this->writeText(' ' . str_replace("\n", "\n ", $help), $options); + $this->writeText("\n"); + } + } + + /** + * 描述控制台 + * @param Console $console + * @param array $options + * @return void + */ + 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->getCommands()); + + 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("Usage:\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->getCommands()); + + if ($describedNamespace) { + $this->writeText(sprintf('Available commands for the "%s" namespace:', $describedNamespace), $options); + } else { + $this->writeText('Available commands:', $options); + } + + // add commands by namespace + foreach ($description->getNamespaces() as $namespace) { + if (!$describedNamespace && ConsoleDescription::GLOBAL_NAMESPACE !== $namespace['id']) { + $this->writeText("\n"); + $this->writeText(' ' . $namespace['id'] . '', $options); + } + + foreach ($namespace['commands'] as $name) { + $this->writeText("\n"); + $spacingWidth = $width - strlen($name); + $this->writeText(sprintf(" %s%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 Command[] $commands + * @return int + */ + private function getColumnWidth(array $commands) + { + $width = 0; + foreach ($commands as $command) { + $width = strlen($command->getName()) > $width ? strlen($command->getName()) : $width; + } + + 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/thinkphp/library/think/console/output/Formatter.php b/thinkphp/library/think/console/output/Formatter.php new file mode 100644 index 0000000..f8bee55 --- /dev/null +++ b/thinkphp/library/think/console/output/Formatter.php @@ -0,0 +1,198 @@ + +// +---------------------------------------------------------------------- +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('/([^\\\\]?)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 = isset($matches[3][$i][0]) ? $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/thinkphp/library/think/console/output/Question.php b/thinkphp/library/think/console/output/Question.php new file mode 100644 index 0000000..03975f2 --- /dev/null +++ b/thinkphp/library/think/console/output/Question.php @@ -0,0 +1,211 @@ + +// +---------------------------------------------------------------------- + +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/thinkphp/library/think/console/output/descriptor/Console.php b/thinkphp/library/think/console/output/descriptor/Console.php new file mode 100644 index 0000000..4648b68 --- /dev/null +++ b/thinkphp/library/think/console/output/descriptor/Console.php @@ -0,0 +1,149 @@ + +// +---------------------------------------------------------------------- + +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() + { + if (null === $this->namespaces) { + $this->inspectConsole(); + } + + return $this->namespaces; + } + + /** + * @return Command[] + */ + public function getCommands() + { + if (null === $this->commands) { + $this->inspectConsole(); + } + + return $this->commands; + } + + /** + * @param string $name + * @return Command + * @throws \InvalidArgumentException + */ + public function getCommand($name) + { + if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) { + throw new \InvalidArgumentException(sprintf('Command %s does not exist.', $name)); + } + + return isset($this->commands[$name]) ? $this->commands[$name] : $this->aliases[$name]; + } + + private function inspectConsole() + { + $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 (!$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) + { + $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/thinkphp/library/think/console/output/driver/Buffer.php b/thinkphp/library/think/console/output/driver/Buffer.php new file mode 100644 index 0000000..c77a2ec --- /dev/null +++ b/thinkphp/library/think/console/output/driver/Buffer.php @@ -0,0 +1,52 @@ + +// +---------------------------------------------------------------------- + +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, $newline = false, $options = Output::OUTPUT_NORMAL) + { + $messages = (array) $messages; + + foreach ($messages as $message) { + $this->buffer .= $message; + } + if ($newline) { + $this->buffer .= "\n"; + } + } + + public function renderException(\Exception $e) + { + // do nothing + } + +} diff --git a/thinkphp/library/think/console/output/driver/Console.php b/thinkphp/library/think/console/output/driver/Console.php new file mode 100644 index 0000000..8f29fd0 --- /dev/null +++ b/thinkphp/library/think/console/output/driver/Console.php @@ -0,0 +1,373 @@ + +// +---------------------------------------------------------------------- + +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 getFormatter() + { + return $this->formatter; + } + + public function setDecorated($decorated) + { + $this->formatter->setDecorated($decorated); + } + + public function write($messages, $newline = false, $type = Output::OUTPUT_NORMAL, $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(\Exception $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('%s', str_repeat(' ', $len)); + $messages[] = sprintf('%s%s', $title, str_repeat(' ', max(0, $len - $this->stringWidth($title)))); + foreach ($lines as $line) { + $messages[] = sprintf(' %s %s', $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('Exception trace:', 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 %s:%s', $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() + { + if ($this->terminalDimensions) { + return $this->terminalDimensions; + } + + if ('\\' === DS) { + 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 x 或 null + */ + 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) + { + 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, $width) + { + 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() + { + $checks = [ + function_exists('php_uname') ? php_uname('s') : '', + getenv('OSTYPE'), + PHP_OS, + ]; + return false !== stripos(implode(';', $checks), 'OS400'); + } + + /** + * 当前环境是否支持写入控制台输出到stdout. + * + * @return bool + */ + protected function hasStdoutSupport() + { + return false === $this->isRunningOS400(); + } + + /** + * 当前环境是否支持写入控制台输出到stderr. + * + * @return bool + */ + protected function hasStderrSupport() + { + 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) + { + 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/thinkphp/library/think/console/output/driver/Nothing.php b/thinkphp/library/think/console/output/driver/Nothing.php new file mode 100644 index 0000000..9a55f77 --- /dev/null +++ b/thinkphp/library/think/console/output/driver/Nothing.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output\driver; + +use think\console\Output; + +class Nothing +{ + + public function __construct(Output $output) + { + // do nothing + } + + public function write($messages, $newline = false, $options = Output::OUTPUT_NORMAL) + { + // do nothing + } + + public function renderException(\Exception $e) + { + // do nothing + } +} diff --git a/thinkphp/library/think/console/output/formatter/Stack.php b/thinkphp/library/think/console/output/formatter/Stack.php new file mode 100644 index 0000000..4864a3f --- /dev/null +++ b/thinkphp/library/think/console/output/formatter/Stack.php @@ -0,0 +1,116 @@ + +// +---------------------------------------------------------------------- + +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() + { + $this->styles = []; + } + + /** + * 推一个样式进入堆栈 + * @param Style $style + */ + public function push(Style $style) + { + $this->styles[] = $style; + } + + /** + * 从堆栈中弹出一个样式 + * @param Style|null $style + * @return Style + * @throws \InvalidArgumentException + */ + public function pop(Style $style = null) + { + 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() + { + 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() + { + return $this->emptyStyle; + } +} diff --git a/thinkphp/library/think/console/output/formatter/Style.php b/thinkphp/library/think/console/output/formatter/Style.php new file mode 100644 index 0000000..d9b0999 --- /dev/null +++ b/thinkphp/library/think/console/output/formatter/Style.php @@ -0,0 +1,189 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output\formatter; + +class Style +{ + + private 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], + ]; + private 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], + ]; + private 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($option) + { + 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($option) + { + 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($text) + { + $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/thinkphp/library/think/console/output/question/Choice.php b/thinkphp/library/think/console/output/question/Choice.php new file mode 100644 index 0000000..f6760e5 --- /dev/null +++ b/thinkphp/library/think/console/output/question/Choice.php @@ -0,0 +1,163 @@ + +// +---------------------------------------------------------------------- + +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() + { + return $this->choices; + } + + /** + * 设置可否多选 + * @param bool $multiselect + * @return self + */ + public function setMultiselect($multiselect) + { + $this->multiselect = $multiselect; + $this->setValidator($this->getDefaultValidator()); + + return $this; + } + + public function isMultiselect() + { + return $this->multiselect; + } + + /** + * 获取提示 + * @return string + */ + public function getPrompt() + { + return $this->prompt; + } + + /** + * 设置提示 + * @param string $prompt + * @return self + */ + public function setPrompt($prompt) + { + $this->prompt = $prompt; + + return $this; + } + + /** + * 设置错误提示信息 + * @param string $errorMessage + * @return self + */ + public function setErrorMessage($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 (empty($result)) { + throw new \InvalidArgumentException(sprintf($errorMessage, $value)); + } + array_push($multiselectChoices, $result); + } + + if ($multiselect) { + return $multiselectChoices; + } + + return current($multiselectChoices); + }; + } +} diff --git a/thinkphp/library/think/console/output/question/Confirmation.php b/thinkphp/library/think/console/output/question/Confirmation.php new file mode 100644 index 0000000..6598f9b --- /dev/null +++ b/thinkphp/library/think/console/output/question/Confirmation.php @@ -0,0 +1,57 @@ + +// +---------------------------------------------------------------------- + +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($question, $default = true, $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/thinkphp/library/think/controller/Rest.php b/thinkphp/library/think/controller/Rest.php new file mode 100644 index 0000000..43ab2f6 --- /dev/null +++ b/thinkphp/library/think/controller/Rest.php @@ -0,0 +1,99 @@ + +// +---------------------------------------------------------------------- + +namespace think\controller; + +use think\App; +use think\Request; +use think\Response; + +abstract class Rest +{ + + protected $method; // 当前请求类型 + protected $type; // 当前资源类型 + // 输出类型 + protected $restMethodList = 'get|post|put|delete'; + protected $restDefaultMethod = 'get'; + protected $restTypeList = 'html|xml|json|rss'; + protected $restDefaultType = 'html'; + protected $restOutputType = [ // REST允许输出的资源类型列表 + 'xml' => 'application/xml', + 'json' => 'application/json', + 'html' => 'text/html', + ]; + + /** + * 构造函数 取得模板对象实例 + * @access public + */ + public function __construct() + { + // 资源类型检测 + $request = Request::instance(); + $ext = $request->ext(); + if ('' == $ext) { + // 自动检测资源类型 + $this->type = $request->type(); + } elseif (!preg_match('/(' . $this->restTypeList . ')$/i', $ext)) { + // 资源类型非法 则用默认资源类型访问 + $this->type = $this->restDefaultType; + } else { + $this->type = $ext; + } + // 请求方式检测 + $method = strtolower($request->method()); + if (!preg_match('/(' . $this->restMethodList . ')$/i', $method)) { + // 请求方式非法 则用默认请求方法 + $method = $this->restDefaultMethod; + } + $this->method = $method; + } + + /** + * REST 调用 + * @access public + * @param string $method 方法名 + * @return mixed + * @throws \Exception + */ + public function _empty($method) + { + if (method_exists($this, $method . '_' . $this->method . '_' . $this->type)) { + // RESTFul方法支持 + $fun = $method . '_' . $this->method . '_' . $this->type; + } elseif ($this->method == $this->restDefaultMethod && method_exists($this, $method . '_' . $this->type)) { + $fun = $method . '_' . $this->type; + } elseif ($this->type == $this->restDefaultType && method_exists($this, $method . '_' . $this->method)) { + $fun = $method . '_' . $this->method; + } + if (isset($fun)) { + return App::invokeMethod([$this, $fun]); + } else { + // 抛出异常 + throw new \Exception('error action :' . $method); + } + } + + /** + * 输出返回数据 + * @access protected + * @param mixed $data 要返回的数据 + * @param String $type 返回类型 JSON XML + * @param integer $code HTTP状态码 + * @return Response + */ + protected function response($data, $type = 'json', $code = 200) + { + return Response::create($data, $type)->code($code); + } + +} diff --git a/thinkphp/library/think/controller/Yar.php b/thinkphp/library/think/controller/Yar.php new file mode 100644 index 0000000..af4e9a1 --- /dev/null +++ b/thinkphp/library/think/controller/Yar.php @@ -0,0 +1,51 @@ + +// +---------------------------------------------------------------------- + +namespace think\controller; + +/** + * ThinkPHP Yar控制器类 + */ +abstract class Yar +{ + + /** + * 构造函数 + * @access public + */ + public function __construct() + { + //控制器初始化 + if (method_exists($this, '_initialize')) { + $this->_initialize(); + } + + //判断扩展是否存在 + if (!extension_loaded('yar')) { + throw new \Exception('not support yar'); + } + + //实例化Yar_Server + $server = new \Yar_Server($this); + // 启动server + $server->handle(); + } + + /** + * 魔术方法 有不存在的操作的时候执行 + * @access public + * @param string $method 方法名 + * @param array $args 参数 + * @return mixed + */ + public function __call($method, $args) + {} +} diff --git a/thinkphp/library/think/db/Builder.php b/thinkphp/library/think/db/Builder.php new file mode 100644 index 0000000..16480d3 --- /dev/null +++ b/thinkphp/library/think/db/Builder.php @@ -0,0 +1,905 @@ + +// +---------------------------------------------------------------------- + +namespace think\db; + +use PDO; +use think\Exception; + +abstract class Builder +{ + // connection对象实例 + protected $connection; + // 查询对象实例 + protected $query; + + // 数据库表达式 + protected $exp = ['eq' => '=', 'neq' => '<>', 'gt' => '>', 'egt' => '>=', 'lt' => '<', 'elt' => '<=', 'notlike' => 'NOT LIKE', 'not like' => 'NOT LIKE', 'like' => 'LIKE', 'in' => 'IN', 'exp' => 'EXP', 'notin' => 'NOT IN', 'not in' => 'NOT IN', 'between' => 'BETWEEN', 'not between' => 'NOT BETWEEN', 'notbetween' => 'NOT BETWEEN', 'exists' => 'EXISTS', 'notexists' => 'NOT EXISTS', 'not exists' => 'NOT EXISTS', 'null' => 'NULL', 'notnull' => 'NOT NULL', 'not null' => 'NOT NULL', '> time' => '> TIME', '< time' => '< TIME', '>= time' => '>= TIME', '<= time' => '<= TIME', 'between time' => 'BETWEEN TIME', 'not between time' => 'NOT BETWEEN TIME', 'notbetween time' => 'NOT BETWEEN TIME']; + + // SQL表达式 + protected $selectSql = 'SELECT%DISTINCT% %FIELD% FROM %TABLE%%FORCE%%JOIN%%WHERE%%GROUP%%HAVING%%UNION%%ORDER%%LIMIT%%LOCK%%COMMENT%'; + protected $insertSql = '%INSERT% INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%'; + protected $insertAllSql = '%INSERT% INTO %TABLE% (%FIELD%) %DATA% %COMMENT%'; + protected $updateSql = 'UPDATE %TABLE% SET %SET% %JOIN% %WHERE% %ORDER%%LIMIT% %LOCK%%COMMENT%'; + protected $deleteSql = 'DELETE FROM %TABLE% %USING% %JOIN% %WHERE% %ORDER%%LIMIT% %LOCK%%COMMENT%'; + + /** + * 构造函数 + * @access public + * @param Connection $connection 数据库连接对象实例 + * @param Query $query 数据库查询对象实例 + */ + public function __construct(Connection $connection, Query $query) + { + $this->connection = $connection; + $this->query = $query; + } + + /** + * 获取当前的连接对象实例 + * @access public + * @return Connection + */ + public function getConnection() + { + return $this->connection; + } + + /** + * 获取当前的Query对象实例 + * @access public + * @return Query + */ + public function getQuery() + { + return $this->query; + } + + /** + * 将SQL语句中的__TABLE_NAME__字符串替换成带前缀的表名(小写) + * @access protected + * @param string $sql sql语句 + * @return string + */ + protected function parseSqlTable($sql) + { + return $this->query->parseSqlTable($sql); + } + + /** + * 数据分析 + * @access protected + * @param array $data 数据 + * @param array $options 查询参数 + * @return array + * @throws Exception + */ + protected function parseData($data, $options) + { + if (empty($data)) { + return []; + } + + // 获取绑定信息 + $bind = $this->query->getFieldsBind($options['table']); + if ('*' == $options['field']) { + $fields = array_keys($bind); + } else { + $fields = $options['field']; + } + + $result = []; + foreach ($data as $key => $val) { + if ('*' != $options['field'] && !in_array($key, $fields, true)) { + continue; + } + + $item = $this->parseKey($key, $options, true); + if ($val instanceof Expression) { + $result[$item] = $val->getValue(); + continue; + } elseif (is_object($val) && method_exists($val, '__toString')) { + // 对象数据写入 + $val = $val->__toString(); + } + if (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)) { + switch (strtolower($val[0])) { + case 'inc': + $result[$item] = $item . '+' . floatval($val[1]); + break; + case 'dec': + $result[$item] = $item . '-' . floatval($val[1]); + break; + case 'exp': + throw new Exception('not support data:[' . $val[0] . ']'); + } + } elseif (is_scalar($val)) { + // 过滤非标量数据 + if (0 === strpos($val, ':') && $this->query->isBind(substr($val, 1))) { + $result[$item] = $val; + } else { + $key = str_replace('.', '_', $key); + $this->query->bind('data__' . $key, $val, isset($bind[$key]) ? $bind[$key] : PDO::PARAM_STR); + $result[$item] = ':data__' . $key; + } + } + } + return $result; + } + + /** + * 字段名分析 + * @access protected + * @param string $key + * @param array $options + * @return string + */ + protected function parseKey($key, $options = [], $strict = false) + { + return $key; + } + + /** + * value分析 + * @access protected + * @param mixed $value + * @param string $field + * @return string|array + */ + protected function parseValue($value, $field = '') + { + if (is_string($value)) { + $value = strpos($value, ':') === 0 && $this->query->isBind(substr($value, 1)) ? $value : $this->connection->quote($value); + } elseif (is_array($value)) { + $value = array_map([$this, 'parseValue'], $value); + } elseif (is_bool($value)) { + $value = $value ? '1' : '0'; + } elseif (is_null($value)) { + $value = 'null'; + } + return $value; + } + + /** + * field分析 + * @access protected + * @param mixed $fields + * @param array $options + * @return string + */ + protected function parseField($fields, $options = []) + { + if ('*' == $fields || empty($fields)) { + $fieldsStr = '*'; + } elseif (is_array($fields)) { + // 支持 'field1'=>'field2' 这样的字段别名定义 + $array = []; + foreach ($fields as $key => $field) { + if ($field instanceof Expression) { + $array[] = $field->getValue(); + } elseif (!is_numeric($key)) { + $array[] = $this->parseKey($key, $options) . ' AS ' . $this->parseKey($field, $options, true); + } else { + $array[] = $this->parseKey($field, $options); + } + } + $fieldsStr = implode(',', $array); + } + return $fieldsStr; + } + + /** + * table分析 + * @access protected + * @param mixed $tables + * @param array $options + * @return string + */ + protected function parseTable($tables, $options = []) + { + $item = []; + foreach ((array) $tables as $key => $table) { + if (!is_numeric($key)) { + $key = $this->parseSqlTable($key); + $item[] = $this->parseKey($key) . ' ' . (isset($options['alias'][$table]) ? $this->parseKey($options['alias'][$table]) : $this->parseKey($table)); + } else { + $table = $this->parseSqlTable($table); + if (isset($options['alias'][$table])) { + $item[] = $this->parseKey($table) . ' ' . $this->parseKey($options['alias'][$table]); + } else { + $item[] = $this->parseKey($table); + } + } + } + return implode(',', $item); + } + + /** + * where分析 + * @access protected + * @param mixed $where 查询条件 + * @param array $options 查询参数 + * @return string + */ + protected function parseWhere($where, $options) + { + $whereStr = $this->buildWhere($where, $options); + if (!empty($options['soft_delete'])) { + // 附加软删除条件 + list($field, $condition) = $options['soft_delete']; + + $binds = $this->query->getFieldsBind($options['table']); + $whereStr = $whereStr ? '( ' . $whereStr . ' ) AND ' : ''; + $whereStr = $whereStr . $this->parseWhereItem($field, $condition, '', $options, $binds); + } + return empty($whereStr) ? '' : ' WHERE ' . $whereStr; + } + + /** + * 生成查询条件SQL + * @access public + * @param mixed $where + * @param array $options + * @return string + */ + public function buildWhere($where, $options) + { + if (empty($where)) { + $where = []; + } + + if ($where instanceof Query) { + return $this->buildWhere($where->getOptions('where'), $options); + } + + $whereStr = ''; + $binds = $this->query->getFieldsBind($options['table']); + foreach ($where as $key => $val) { + $str = []; + foreach ($val as $field => $value) { + if ($value instanceof Expression) { + $str[] = ' ' . $key . ' ( ' . $value->getValue() . ' )'; + } elseif ($value instanceof \Closure) { + // 使用闭包查询 + $query = new Query($this->connection); + call_user_func_array($value, [ & $query]); + $whereClause = $this->buildWhere($query->getOptions('where'), $options); + if (!empty($whereClause)) { + $str[] = ' ' . $key . ' ( ' . $whereClause . ' )'; + } + } elseif (strpos($field, '|')) { + // 不同字段使用相同查询条件(OR) + $array = explode('|', $field); + $item = []; + foreach ($array as $k) { + $item[] = $this->parseWhereItem($k, $value, '', $options, $binds); + } + $str[] = ' ' . $key . ' ( ' . implode(' OR ', $item) . ' )'; + } elseif (strpos($field, '&')) { + // 不同字段使用相同查询条件(AND) + $array = explode('&', $field); + $item = []; + foreach ($array as $k) { + $item[] = $this->parseWhereItem($k, $value, '', $options, $binds); + } + $str[] = ' ' . $key . ' ( ' . implode(' AND ', $item) . ' )'; + } else { + // 对字段使用表达式查询 + $field = is_string($field) ? $field : ''; + $str[] = ' ' . $key . ' ' . $this->parseWhereItem($field, $value, $key, $options, $binds); + } + } + + $whereStr .= empty($whereStr) ? substr(implode(' ', $str), strlen($key) + 1) : implode(' ', $str); + } + + return $whereStr; + } + + // where子单元分析 + protected function parseWhereItem($field, $val, $rule = '', $options = [], $binds = [], $bindName = null) + { + // 字段分析 + $key = $field ? $this->parseKey($field, $options, true) : ''; + + // 查询规则和条件 + if (!is_array($val)) { + $val = is_null($val) ? ['null', ''] : ['=', $val]; + } + list($exp, $value) = $val; + + // 对一个字段使用多个查询条件 + if (is_array($exp)) { + $item = array_pop($val); + // 传入 or 或者 and + if (is_string($item) && in_array($item, ['AND', 'and', 'OR', 'or'])) { + $rule = $item; + } else { + array_push($val, $item); + } + foreach ($val as $k => $item) { + $bindName = 'where_' . str_replace('.', '_', $field) . '_' . $k; + $str[] = $this->parseWhereItem($field, $item, $rule, $options, $binds, $bindName); + } + return '( ' . implode(' ' . $rule . ' ', $str) . ' )'; + } + + // 检测操作符 + if (!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); + } + } + $bindName = $bindName ?: 'where_' . $rule . '_' . str_replace(['.', '-'], '_', $field); + if (preg_match('/\W/', $bindName)) { + // 处理带非单词字符的字段名 + $bindName = md5($bindName); + } + + if ($value instanceof Expression) { + + } elseif (is_object($value) && method_exists($value, '__toString')) { + // 对象数据写入 + $value = $value->__toString(); + } + + $bindType = isset($binds[$field]) ? $binds[$field] : PDO::PARAM_STR; + if (is_scalar($value) && array_key_exists($field, $binds) && !in_array($exp, ['EXP', 'NOT NULL', 'NULL', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN']) && strpos($exp, 'TIME') === false) { + if (strpos($value, ':') !== 0 || !$this->query->isBind(substr($value, 1))) { + if ($this->query->isBind($bindName)) { + $bindName .= '_' . str_replace('.', '_', uniqid('', true)); + } + $this->query->bind($bindName, $value, $bindType); + $value = ':' . $bindName; + } + } + + $whereStr = ''; + if (in_array($exp, ['=', '<>', '>', '>=', '<', '<='])) { + // 比较运算 + if ($value instanceof \Closure) { + $whereStr .= $key . ' ' . $exp . ' ' . $this->parseClosure($value); + } else { + $whereStr .= $key . ' ' . $exp . ' ' . $this->parseValue($value, $field); + } + } elseif ('LIKE' == $exp || 'NOT LIKE' == $exp) { + // 模糊匹配 + if (is_array($value)) { + foreach ($value as $item) { + $array[] = $key . ' ' . $exp . ' ' . $this->parseValue($item, $field); + } + $logic = isset($val[2]) ? $val[2] : 'AND'; + $whereStr .= '(' . implode(' ' . strtoupper($logic) . ' ', $array) . ')'; + } else { + $whereStr .= $key . ' ' . $exp . ' ' . $this->parseValue($value, $field); + } + } elseif ('EXP' == $exp) { + // 表达式查询 + if ($value instanceof Expression) { + $whereStr .= '( ' . $key . ' ' . $value->getValue() . ' )'; + } else { + throw new Exception('where express error:' . $exp); + } + } elseif (in_array($exp, ['NOT NULL', 'NULL'])) { + // NULL 查询 + $whereStr .= $key . ' IS ' . $exp; + } elseif (in_array($exp, ['NOT IN', 'IN'])) { + // IN 查询 + if ($value instanceof \Closure) { + $whereStr .= $key . ' ' . $exp . ' ' . $this->parseClosure($value); + } else { + $value = array_unique(is_array($value) ? $value : explode(',', $value)); + if (array_key_exists($field, $binds)) { + $bind = []; + $array = []; + $i = 0; + foreach ($value as $v) { + $i++; + if ($this->query->isBind($bindName . '_in_' . $i)) { + $bindKey = $bindName . '_in_' . uniqid() . '_' . $i; + } else { + $bindKey = $bindName . '_in_' . $i; + } + $bind[$bindKey] = [$v, $bindType]; + $array[] = ':' . $bindKey; + } + $this->query->bind($bind); + $zone = implode(',', $array); + } else { + $zone = implode(',', $this->parseValue($value, $field)); + } + $whereStr .= $key . ' ' . $exp . ' (' . (empty($zone) ? "''" : $zone) . ')'; + } + } elseif (in_array($exp, ['NOT BETWEEN', 'BETWEEN'])) { + // BETWEEN 查询 + $data = is_array($value) ? $value : explode(',', $value); + if (array_key_exists($field, $binds)) { + if ($this->query->isBind($bindName . '_between_1')) { + $bindKey1 = $bindName . '_between_1' . uniqid(); + $bindKey2 = $bindName . '_between_2' . uniqid(); + } else { + $bindKey1 = $bindName . '_between_1'; + $bindKey2 = $bindName . '_between_2'; + } + $bind = [ + $bindKey1 => [$data[0], $bindType], + $bindKey2 => [$data[1], $bindType], + ]; + $this->query->bind($bind); + $between = ':' . $bindKey1 . ' AND :' . $bindKey2; + } else { + $between = $this->parseValue($data[0], $field) . ' AND ' . $this->parseValue($data[1], $field); + } + $whereStr .= $key . ' ' . $exp . ' ' . $between; + } elseif (in_array($exp, ['NOT EXISTS', 'EXISTS'])) { + // EXISTS 查询 + if ($value instanceof \Closure) { + $whereStr .= $exp . ' ' . $this->parseClosure($value); + } else { + $whereStr .= $exp . ' (' . $value . ')'; + } + } elseif (in_array($exp, ['< TIME', '> TIME', '<= TIME', '>= TIME'])) { + $whereStr .= $key . ' ' . substr($exp, 0, 2) . ' ' . $this->parseDateTime($value, $field, $options, $bindName, $bindType); + } elseif (in_array($exp, ['BETWEEN TIME', 'NOT BETWEEN TIME'])) { + if (is_string($value)) { + $value = explode(',', $value); + } + + $whereStr .= $key . ' ' . substr($exp, 0, -4) . $this->parseDateTime($value[0], $field, $options, $bindName . '_between_1', $bindType) . ' AND ' . $this->parseDateTime($value[1], $field, $options, $bindName . '_between_2', $bindType); + } + return $whereStr; + } + + // 执行闭包子查询 + protected function parseClosure($call, $show = true) + { + $query = new Query($this->connection); + call_user_func_array($call, [ & $query]); + return $query->buildSql($show); + } + + /** + * 日期时间条件解析 + * @access protected + * @param string $value + * @param string $key + * @param array $options + * @param string $bindName + * @param integer $bindType + * @return string + */ + protected function parseDateTime($value, $key, $options = [], $bindName = null, $bindType = null) + { + // 获取时间字段类型 + if (strpos($key, '.')) { + list($table, $key) = explode('.', $key); + if (isset($options['alias']) && $pos = array_search($table, $options['alias'])) { + $table = $pos; + } + } else { + $table = $options['table']; + } + $type = $this->query->getTableInfo($table, 'type'); + if (isset($type[$key])) { + $info = $type[$key]; + } + if (isset($info)) { + if (is_string($value)) { + $value = strtotime($value) ?: $value; + } + + if (preg_match('/(datetime|timestamp)/is', $info)) { + // 日期及时间戳类型 + $value = date('Y-m-d H:i:s', $value); + } elseif (preg_match('/(date)/is', $info)) { + // 日期及时间戳类型 + $value = date('Y-m-d', $value); + } + } + $bindName = $bindName ?: $key; + + if ($this->query->isBind($bindName)) { + $bindName .= '_' . str_replace('.', '_', uniqid('', true)); + } + + $this->query->bind($bindName, $value, $bindType); + return ':' . $bindName; + } + + /** + * limit分析 + * @access protected + * @param mixed $limit + * @return string + */ + protected function parseLimit($limit) + { + return (!empty($limit) && false === strpos($limit, '(')) ? ' LIMIT ' . $limit . ' ' : ''; + } + + /** + * join分析 + * @access protected + * @param array $join + * @param array $options 查询条件 + * @return string + */ + protected function parseJoin($join, & $options = []) + { + $joinStr = ''; + if (!empty($join)) { + foreach ($join as $item) { + list($table, $type, $on) = $item; + if (is_array($table)) { + $origin = key($table); + if ($origin && $origin != $table[$origin]) { + $options['alias'][$origin] = $table[$origin]; + } + } + $condition = []; + foreach ((array) $on as $val) { + if ($val instanceof Expression) { + $condition[] = $val->getValue(); + } elseif (strpos($val, '=')) { + list($val1, $val2) = explode('=', $val, 2); + $condition[] = $this->parseKey($val1, $options) . '=' . $this->parseKey($val2, $options); + } else { + $condition[] = $val; + } + } + + $table = $this->parseTable($table, $options); + $joinStr .= ' ' . $type . ' JOIN ' . $table . ' ON ' . implode(' AND ', $condition); + } + } + return $joinStr; + } + + /** + * order分析 + * @access protected + * @param mixed $order + * @param array $options 查询条件 + * @return string + */ + protected function parseOrder($order, $options = []) + { + if (empty($order)) { + return ''; + } + + $array = []; + foreach ($order as $key => $val) { + if ($val instanceof Expression) { + $array[] = $val->getValue(); + } elseif ('[rand]' == $val) { + $array[] = $this->parseRand(); + } else { + if (is_numeric($key)) { + list($key, $sort) = explode(' ', strpos($val, ' ') ? $val : $val . ' '); + } else { + $sort = $val; + } + $sort = strtoupper($sort); + $sort = in_array($sort, ['ASC', 'DESC'], true) ? ' ' . $sort : ''; + $array[] = $this->parseKey($key, $options, true) . $sort; + } + } + $order = implode(',', $array); + + return !empty($order) ? ' ORDER BY ' . $order : ''; + } + + /** + * group分析 + * @access protected + * @param mixed $group + * @return string + */ + protected function parseGroup($group) + { + return !empty($group) ? ' GROUP BY ' . $this->parseKey($group) : ''; + } + + /** + * having分析 + * @access protected + * @param string $having + * @return string + */ + protected function parseHaving($having) + { + return !empty($having) ? ' HAVING ' . $having : ''; + } + + /** + * comment分析 + * @access protected + * @param string $comment + * @return string + */ + protected function parseComment($comment) + { + if (false !== strpos($comment, '*/')) { + $comment = strstr($comment, '*/', true); + } + return !empty($comment) ? ' /* ' . $comment . ' */' : ''; + } + + /** + * distinct分析 + * @access protected + * @param mixed $distinct + * @return string + */ + protected function parseDistinct($distinct) + { + return !empty($distinct) ? ' DISTINCT ' : ''; + } + + /** + * union分析 + * @access protected + * @param mixed $union + * @return string + */ + protected function parseUnion($union) + { + if (empty($union)) { + return ''; + } + $type = $union['type']; + unset($union['type']); + foreach ($union as $u) { + if ($u instanceof \Closure) { + $sql[] = $type . ' ' . $this->parseClosure($u); + } elseif (is_string($u)) { + $sql[] = $type . ' ( ' . $this->parseSqlTable($u) . ' )'; + } + } + return ' ' . implode(' ', $sql); + } + + /** + * index分析,可在操作链中指定需要强制使用的索引 + * @access protected + * @param mixed $index + * @return string + */ + protected function parseForce($index) + { + if (empty($index)) { + return ''; + } + + return sprintf(" FORCE INDEX ( %s ) ", is_array($index) ? implode(',', $index) : $index); + } + + /** + * 设置锁机制 + * @access protected + * @param bool|string $lock + * @return string + */ + protected function parseLock($lock = false) + { + if (is_bool($lock)) { + return $lock ? ' FOR UPDATE ' : ''; + } elseif (is_string($lock)) { + return ' ' . trim($lock) . ' '; + } + } + + /** + * 生成查询SQL + * @access public + * @param array $options 表达式 + * @return string + */ + public function select($options = []) + { + $sql = str_replace( + ['%TABLE%', '%DISTINCT%', '%FIELD%', '%JOIN%', '%WHERE%', '%GROUP%', '%HAVING%', '%ORDER%', '%LIMIT%', '%UNION%', '%LOCK%', '%COMMENT%', '%FORCE%'], + [ + $this->parseTable($options['table'], $options), + $this->parseDistinct($options['distinct']), + $this->parseField($options['field'], $options), + $this->parseJoin($options['join'], $options), + $this->parseWhere($options['where'], $options), + $this->parseGroup($options['group']), + $this->parseHaving($options['having']), + $this->parseOrder($options['order'], $options), + $this->parseLimit($options['limit']), + $this->parseUnion($options['union']), + $this->parseLock($options['lock']), + $this->parseComment($options['comment']), + $this->parseForce($options['force']), + ], $this->selectSql); + return $sql; + } + + /** + * 生成insert SQL + * @access public + * @param array $data 数据 + * @param array $options 表达式 + * @param bool $replace 是否replace + * @return string + */ + public function insert(array $data, $options = [], $replace = false) + { + // 分析并处理数据 + $data = $this->parseData($data, $options); + if (empty($data)) { + return 0; + } + $fields = array_keys($data); + $values = array_values($data); + + $sql = str_replace( + ['%INSERT%', '%TABLE%', '%FIELD%', '%DATA%', '%COMMENT%'], + [ + $replace ? 'REPLACE' : 'INSERT', + $this->parseTable($options['table'], $options), + implode(' , ', $fields), + implode(' , ', $values), + $this->parseComment($options['comment']), + ], $this->insertSql); + + return $sql; + } + + /** + * 生成insertall SQL + * @access public + * @param array $dataSet 数据集 + * @param array $options 表达式 + * @param bool $replace 是否replace + * @return string + * @throws Exception + */ + public function insertAll($dataSet, $options = [], $replace = false) + { + // 获取合法的字段 + if ('*' == $options['field']) { + $fields = array_keys($this->query->getFieldsType($options['table'])); + } else { + $fields = $options['field']; + } + + foreach ($dataSet as $data) { + foreach ($data as $key => $val) { + if (!in_array($key, $fields, true)) { + if ($options['strict']) { + throw new Exception('fields not exists:[' . $key . ']'); + } + unset($data[$key]); + } elseif (is_null($val)) { + $data[$key] = 'NULL'; + } elseif (is_scalar($val)) { + $data[$key] = $this->parseValue($val, $key); + } elseif (is_object($val) && method_exists($val, '__toString')) { + // 对象数据写入 + $data[$key] = $val->__toString(); + } else { + // 过滤掉非标量数据 + unset($data[$key]); + } + } + $value = array_values($data); + $values[] = 'SELECT ' . implode(',', $value); + + if (!isset($insertFields)) { + $insertFields = array_keys($data); + } + } + + foreach ($insertFields as $field) { + $fields[] = $this->parseKey($field, $options, true); + } + + return str_replace( + ['%INSERT%', '%TABLE%', '%FIELD%', '%DATA%', '%COMMENT%'], + [ + $replace ? 'REPLACE' : 'INSERT', + $this->parseTable($options['table'], $options), + implode(' , ', $insertFields), + implode(' UNION ALL ', $values), + $this->parseComment($options['comment']), + ], $this->insertAllSql); + } + + /** + * 生成select insert SQL + * @access public + * @param array $fields 数据 + * @param string $table 数据表 + * @param array $options 表达式 + * @return string + */ + public function selectInsert($fields, $table, $options) + { + if (is_string($fields)) { + $fields = explode(',', $fields); + } + + $fields = array_map([$this, 'parseKey'], $fields); + $sql = 'INSERT INTO ' . $this->parseTable($table, $options) . ' (' . implode(',', $fields) . ') ' . $this->select($options); + return $sql; + } + + /** + * 生成update SQL + * @access public + * @param array $data 数据 + * @param array $options 表达式 + * @return string + */ + public function update($data, $options) + { + $table = $this->parseTable($options['table'], $options); + $data = $this->parseData($data, $options); + if (empty($data)) { + return ''; + } + foreach ($data as $key => $val) { + $set[] = $key . '=' . $val; + } + + $sql = str_replace( + ['%TABLE%', '%SET%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'], + [ + $this->parseTable($options['table'], $options), + implode(',', $set), + $this->parseJoin($options['join'], $options), + $this->parseWhere($options['where'], $options), + $this->parseOrder($options['order'], $options), + $this->parseLimit($options['limit']), + $this->parseLock($options['lock']), + $this->parseComment($options['comment']), + ], $this->updateSql); + + return $sql; + } + + /** + * 生成delete SQL + * @access public + * @param array $options 表达式 + * @return string + */ + public function delete($options) + { + $sql = str_replace( + ['%TABLE%', '%USING%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'], + [ + $this->parseTable($options['table'], $options), + !empty($options['using']) ? ' USING ' . $this->parseTable($options['using'], $options) . ' ' : '', + $this->parseJoin($options['join'], $options), + $this->parseWhere($options['where'], $options), + $this->parseOrder($options['order'], $options), + $this->parseLimit($options['limit']), + $this->parseLock($options['lock']), + $this->parseComment($options['comment']), + ], $this->deleteSql); + + return $sql; + } +} diff --git a/thinkphp/library/think/db/Connection.php b/thinkphp/library/think/db/Connection.php new file mode 100644 index 0000000..578cc8f --- /dev/null +++ b/thinkphp/library/think/db/Connection.php @@ -0,0 +1,1059 @@ + +// +---------------------------------------------------------------------- + +namespace think\db; + +use PDO; +use PDOStatement; +use think\Db; +use think\db\exception\BindParamException; +use think\Debug; +use think\Exception; +use think\exception\PDOException; +use think\Log; + +/** + * Class Connection + * @package think + * @method Query table(string $table) 指定数据表(含前缀) + * @method Query name(string $name) 指定数据表(不含前缀) + * + */ +abstract class Connection +{ + + /** @var PDOStatement PDO操作实例 */ + protected $PDOStatement; + + /** @var string 当前SQL指令 */ + protected $queryStr = ''; + // 返回或者影响记录数 + protected $numRows = 0; + // 事务指令数 + protected $transTimes = 0; + // 错误信息 + protected $error = ''; + + /** @var PDO[] 数据库连接ID 支持多个连接 */ + protected $links = []; + + /** @var PDO 当前连接ID */ + protected $linkID; + protected $linkRead; + protected $linkWrite; + + // 查询结果类型 + protected $fetchType = PDO::FETCH_ASSOC; + // 字段属性大小写 + protected $attrCase = PDO::CASE_LOWER; + // 监听回调 + protected static $event = []; + // 使用Builder类 + protected $builder; + // 数据库连接参数配置 + protected $config = [ + // 数据库类型 + 'type' => '', + // 服务器地址 + 'hostname' => '', + // 数据库名 + 'database' => '', + // 用户名 + 'username' => '', + // 密码 + 'password' => '', + // 端口 + 'hostport' => '', + // 连接dsn + 'dsn' => '', + // 数据库连接参数 + 'params' => [], + // 数据库编码默认采用utf8 + 'charset' => 'utf8', + // 数据库表前缀 + 'prefix' => '', + // 数据库调试模式 + 'debug' => false, + // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) + 'deploy' => 0, + // 数据库读写是否分离 主从式有效 + 'rw_separate' => false, + // 读写分离后 主服务器数量 + 'master_num' => 1, + // 指定从服务器序号 + 'slave_no' => '', + // 模型写入后自动读取主服务器 + 'read_master' => false, + // 是否严格检查字段是否存在 + 'fields_strict' => true, + // 数据返回类型 + 'result_type' => PDO::FETCH_ASSOC, + // 数据集返回类型 + 'resultset_type' => 'array', + // 自动写入时间戳字段 + 'auto_timestamp' => false, + // 时间字段取出后的默认时间格式 + 'datetime_format' => 'Y-m-d H:i:s', + // 是否需要进行SQL性能分析 + 'sql_explain' => false, + // Builder类 + 'builder' => '', + // Query类 + 'query' => '\\think\\db\\Query', + // 是否需要断线重连 + 'break_reconnect' => false, + ]; + + // PDO连接参数 + 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, + ]; + + // 绑定参数 + protected $bind = []; + + /** + * 构造函数 读取数据库配置信息 + * @access public + * @param array $config 数据库配置数组 + */ + public function __construct(array $config = []) + { + if (!empty($config)) { + $this->config = array_merge($this->config, $config); + } + } + + /** + * 获取新的查询对象 + * @access protected + * @return Query + */ + protected function getQuery() + { + $class = $this->config['query']; + return new $class($this); + } + + /** + * 获取当前连接器类对应的Builder类 + * @access public + * @return string + */ + public function getBuilder() + { + if (!empty($this->builder)) { + return $this->builder; + } else { + return $this->getConfig('builder') ?: '\\think\\db\\builder\\' . ucfirst($this->getConfig('type')); + } + } + + /** + * 调用Query类的查询方法 + * @access public + * @param string $method 方法名称 + * @param array $args 调用参数 + * @return mixed + */ + public function __call($method, $args) + { + return call_user_func_array([$this->getQuery(), $method], $args); + } + + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + abstract protected function parseDsn($config); + + /** + * 取得数据表的字段信息 + * @access public + * @param string $tableName + * @return array + */ + abstract public function getFields($tableName); + + /** + * 取得数据库的表信息 + * @access public + * @param string $dbName + * @return array + */ + abstract public function getTables($dbName); + + /** + * SQL性能分析 + * @access protected + * @param string $sql + * @return array + */ + abstract protected function getExplain($sql); + + /** + * 对返数据表字段信息进行大小写转换出来 + * @access public + * @param array $info 字段信息 + * @return array + */ + public function fieldCase($info) + { + // 字段大小写转换 + 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 public + * @param string $config 配置名称 + * @return mixed + */ + public function getConfig($config = '') + { + return $config ? $this->config[$config] : $this->config; + } + + /** + * 设置数据库的配置参数 + * @access public + * @param string|array $config 配置名称 + * @param mixed $value 配置值 + * @return void + */ + public function setConfig($config, $value = '') + { + if (is_array($config)) { + $this->config = array_merge($this->config, $config); + } else { + $this->config[$config] = $value; + } + } + + /** + * 连接数据库方法 + * @access public + * @param array $config 连接参数 + * @param integer $linkNum 连接序号 + * @param array|bool $autoConnection 是否自动连接主数据库(用于分布式) + * @return PDO + * @throws Exception + */ + public function connect(array $config = [], $linkNum = 0, $autoConnection = false) + { + if (!isset($this->links[$linkNum])) { + if (!$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 (isset($config['result_type'])) { + $this->fetchType = $config['result_type']; + } + try { + if (empty($config['dsn'])) { + $config['dsn'] = $this->parseDsn($config); + } + if ($config['debug']) { + $startTime = microtime(true); + } + $this->links[$linkNum] = new PDO($config['dsn'], $config['username'], $config['password'], $params); + if ($config['debug']) { + // 记录数据库连接信息 + Log::record('[ DB ] CONNECT:[ UseTime:' . number_format(microtime(true) - $startTime, 6) . 's ] ' . $config['dsn'], 'sql'); + } + } catch (\PDOException $e) { + if ($autoConnection) { + Log::record($e->getMessage(), 'error'); + return $this->connect($autoConnection, $linkNum); + } else { + throw $e; + } + } + } + return $this->links[$linkNum]; + } + + /** + * 释放查询结果 + * @access public + */ + public function free() + { + $this->PDOStatement = null; + } + + /** + * 获取PDO对象 + * @access public + * @return \PDO|false + */ + public function getPdo() + { + if (!$this->linkID) { + return false; + } else { + return $this->linkID; + } + } + + /** + * 执行查询 返回数据集 + * @access public + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @param bool $master 是否在主服务器读操作 + * @param bool $pdo 是否返回PDO对象 + * @return mixed + * @throws PDOException + * @throws \Exception + */ + public function query($sql, $bind = [], $master = false, $pdo = false) + { + $this->initConnect($master); + if (!$this->linkID) { + return false; + } + + // 记录SQL语句 + $this->queryStr = $sql; + if ($bind) { + $this->bind = $bind; + } + + Db::$queryTimes++; + try { + // 调试开始 + $this->debug(true); + + // 预处理 + $this->PDOStatement = $this->linkID->prepare($sql); + + // 是否为存储过程调用 + $procedure = in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']); + // 参数绑定 + if ($procedure) { + $this->bindParam($bind); + } else { + $this->bindValue($bind); + } + // 执行查询 + $this->PDOStatement->execute(); + // 调试结束 + $this->debug(false, '', $master); + // 返回结果集 + return $this->getResult($pdo, $procedure); + } catch (\PDOException $e) { + if ($this->isBreak($e)) { + return $this->close()->query($sql, $bind, $master, $pdo); + } + throw new PDOException($e, $this->config, $this->getLastsql()); + } catch (\Throwable $e) { + if ($this->isBreak($e)) { + return $this->close()->query($sql, $bind, $master, $pdo); + } + throw $e; + } catch (\Exception $e) { + if ($this->isBreak($e)) { + return $this->close()->query($sql, $bind, $master, $pdo); + } + throw $e; + } + } + + /** + * 执行语句 + * @access public + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @param Query $query 查询对象 + * @return int + * @throws PDOException + * @throws \Exception + */ + public function execute($sql, $bind = [], Query $query = null) + { + $this->initConnect(true); + if (!$this->linkID) { + return false; + } + + // 记录SQL语句 + $this->queryStr = $sql; + if ($bind) { + $this->bind = $bind; + } + + Db::$executeTimes++; + try { + // 调试开始 + $this->debug(true); + + // 预处理 + $this->PDOStatement = $this->linkID->prepare($sql); + + // 是否为存储过程调用 + $procedure = in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']); + // 参数绑定 + if ($procedure) { + $this->bindParam($bind); + } else { + $this->bindValue($bind); + } + // 执行语句 + $this->PDOStatement->execute(); + // 调试结束 + $this->debug(false, '', true); + + if ($query && !empty($this->config['deploy']) && !empty($this->config['read_master'])) { + $query->readMaster(); + } + + $this->numRows = $this->PDOStatement->rowCount(); + return $this->numRows; + } catch (\PDOException $e) { + if ($this->isBreak($e)) { + return $this->close()->execute($sql, $bind, $query); + } + throw new PDOException($e, $this->config, $this->getLastsql()); + } catch (\Throwable $e) { + if ($this->isBreak($e)) { + return $this->close()->execute($sql, $bind, $query); + } + throw $e; + } catch (\Exception $e) { + if ($this->isBreak($e)) { + return $this->close()->execute($sql, $bind, $query); + } + throw $e; + } + } + + /** + * 根据参数绑定组装最终的SQL语句 便于调试 + * @access public + * @param string $sql 带参数绑定的sql语句 + * @param array $bind 参数绑定列表 + * @return string + */ + public function getRealSql($sql, array $bind = []) + { + if (is_array($sql)) { + $sql = implode(';', $sql); + } + + foreach ($bind as $key => $val) { + $value = is_array($val) ? $val[0] : $val; + $type = is_array($val) ? $val[1] : PDO::PARAM_STR; + if (PDO::PARAM_STR == $type) { + $value = $this->quote($value); + } elseif (PDO::PARAM_INT == $type) { + $value = (float) $value; + } + // 判断占位符 + $sql = is_numeric($key) ? + substr_replace($sql, $value, strpos($sql, '?'), 1) : + str_replace( + [':' . $key . ')', ':' . $key . ',', ':' . $key . ' ', ':' . $key . PHP_EOL], + [$value . ')', $value . ',', $value . ' ', $value . PHP_EOL], + $sql . ' '); + } + return rtrim($sql); + } + + /** + * 参数绑定 + * 支持 ['name'=>'value','id'=>123] 对应命名占位符 + * 或者 ['value',123] 对应问号占位符 + * @access public + * @param array $bind 要绑定的参数列表 + * @return void + * @throws BindParamException + */ + protected function bindValue(array $bind = []) + { + 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; + } + $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($bind) + { + 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 $pdo 是否返回PDOStatement + * @param bool $procedure 是否存储过程 + * @return PDOStatement|array + */ + protected function getResult($pdo = false, $procedure = false) + { + if ($pdo) { + // 返回PDOStatement对象处理 + return $this->PDOStatement; + } + if ($procedure) { + // 存储过程返回结果 + return $this->procedure(); + } + $result = $this->PDOStatement->fetchAll($this->fetchType); + $this->numRows = count($result); + return $result; + } + + /** + * 获得存储过程数据集 + * @access protected + * @return array + */ + protected function procedure() + { + $item = []; + do { + $result = $this->getResult(); + if ($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($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 bool|mixed + * @throws \Exception + */ + public function startTrans() + { + $this->initConnect(true); + if (!$this->linkID) { + return false; + } + + ++$this->transTimes; + try { + if (1 == $this->transTimes) { + $this->linkID->beginTransaction(); + } elseif ($this->transTimes > 1 && $this->supportSavepoint()) { + $this->linkID->exec( + $this->parseSavepoint('trans' . $this->transTimes) + ); + } + + } catch (\Exception $e) { + if ($this->isBreak($e)) { + --$this->transTimes; + return $this->close()->startTrans(); + } + throw $e; + } catch (\Error $e) { + if ($this->isBreak($e)) { + --$this->transTimes; + return $this->close()->startTrans(); + } + throw $e; + } + } + + /** + * 用于非自动提交状态下面的查询提交 + * @access public + * @return void + * @throws PDOException + */ + public function commit() + { + $this->initConnect(true); + + if (1 == $this->transTimes) { + $this->linkID->commit(); + } + + --$this->transTimes; + } + + /** + * 事务回滚 + * @access public + * @return void + * @throws PDOException + */ + public function rollback() + { + $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() + { + return false; + } + + /** + * 生成定义保存点的SQL + * @param $name + * @return string + */ + protected function parseSavepoint($name) + { + return 'SAVEPOINT ' . $name; + } + + /** + * 生成回滚到保存点的SQL + * @param $name + * @return string + */ + protected function parseSavepointRollBack($name) + { + return 'ROLLBACK TO SAVEPOINT ' . $name; + } + + /** + * 批处理执行SQL语句 + * 批处理的指令都认为是execute操作 + * @access public + * @param array $sqlArray SQL批处理指令 + * @return boolean + */ + public function batchQuery($sqlArray = [], $bind = [], Query $query = null) + { + if (!is_array($sqlArray)) { + return false; + } + // 自动启动事务支持 + $this->startTrans(); + try { + foreach ($sqlArray as $sql) { + $this->execute($sql, $bind, $query); + } + // 提交事务 + $this->commit(); + } catch (\Exception $e) { + $this->rollback(); + throw $e; + } + + return true; + } + + /** + * 获得查询次数 + * @access public + * @param boolean $execute 是否包含所有查询 + * @return integer + */ + public function getQueryTimes($execute = false) + { + return $execute ? Db::$queryTimes + Db::$executeTimes : Db::$queryTimes; + } + + /** + * 获得执行次数 + * @access public + * @return integer + */ + public function getExecuteTimes() + { + return Db::$executeTimes; + } + + /** + * 关闭数据库(或者重新连接) + * @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) + { + if (!$this->config['break_reconnect']) { + return false; + } + + $info = [ + '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', + ]; + + $error = $e->getMessage(); + + foreach ($info as $msg) { + if (false !== stripos($error, $msg)) { + return true; + } + } + return false; + } + + /** + * 获取最近一次查询的sql语句 + * @access public + * @return string + */ + public function getLastSql() + { + return $this->getRealSql($this->queryStr, $this->bind); + } + + /** + * 获取最近插入的ID + * @access public + * @param string $sequence 自增序列名 + * @return string + */ + public function getLastInsID($sequence = null) + { + return $this->linkID->lastInsertId($sequence); + } + + /** + * 获取返回或者影响的记录数 + * @access public + * @return integer + */ + public function getNumRows() + { + return $this->numRows; + } + + /** + * 获取最近的错误信息 + * @access public + * @return string + */ + public function getError() + { + if ($this->PDOStatement) { + $error = $this->PDOStatement->errorInfo(); + $error = $error[1] . ':' . $error[2]; + } else { + $error = ''; + } + if ('' != $this->queryStr) { + $error .= "\n [ SQL语句 ] : " . $this->getLastsql(); + } + return $error; + } + + /** + * SQL指令安全过滤 + * @access public + * @param string $str SQL字符串 + * @param bool $master 是否主库查询 + * @return string + */ + public function quote($str, $master = true) + { + $this->initConnect($master); + return $this->linkID ? $this->linkID->quote($str) : $str; + } + + /** + * 数据库调试 记录当前SQL及分析性能 + * @access protected + * @param boolean $start 调试开始标记 true 开始 false 结束 + * @param string $sql 执行的SQL语句 留空自动获取 + * @param boolean $master 主从标记 + * @return void + */ + protected function debug($start, $sql = '', $master = false) + { + if (!empty($this->config['debug'])) { + // 开启数据库调试模式 + if ($start) { + Debug::remark('queryStartTime', 'time'); + } else { + // 记录操作结束时间 + Debug::remark('queryEndTime', 'time'); + $runtime = Debug::getRangeTime('queryStartTime', 'queryEndTime'); + $sql = $sql ?: $this->getLastsql(); + $result = []; + // SQL性能分析 + if ($this->config['sql_explain'] && 0 === stripos(trim($sql), 'select')) { + $result = $this->getExplain($sql); + } + // SQL监听 + $this->trigger($sql, $runtime, $result, $master); + } + } + } + + /** + * 监听SQL执行 + * @access public + * @param callable $callback 回调方法 + * @return void + */ + public function listen($callback) + { + self::$event[] = $callback; + } + + /** + * 触发SQL事件 + * @access protected + * @param string $sql SQL语句 + * @param float $runtime SQL运行时间 + * @param mixed $explain SQL分析 + * @param bool $master 主从标记 + * @return void + */ + protected function trigger($sql, $runtime, $explain = [], $master = false) + { + if (!empty(self::$event)) { + foreach (self::$event as $callback) { + if (is_callable($callback)) { + call_user_func_array($callback, [$sql, $runtime, $explain, $master]); + } + } + } else { + // 未注册监听则记录到日志中 + if ($this->config['deploy']) { + // 分布式记录当前操作的主从 + $master = $master ? 'master|' : 'slave|'; + } else { + $master = ''; + } + + Log::record('[ SQL ] ' . $sql . ' [ ' . $master . 'RunTime:' . $runtime . 's ]', 'sql'); + if (!empty($explain)) { + Log::record('[ EXPLAIN : ' . var_export($explain, true) . ' ]', 'sql'); + } + } + } + + /** + * 初始化数据库连接 + * @access protected + * @param boolean $master 是否主服务器 + * @return void + */ + protected function initConnect($master = true) + { + 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($master = false) + { + $_config = []; + // 分布式数据库配置解析 + foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) { + $_config[$name] = explode(',', $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] = isset($_config[$name][$m]) ? $_config[$name][$m] : $_config[$name][0]; + } + } + $dbConfig = []; + foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) { + $dbConfig[$name] = isset($_config[$name][$r]) ? $_config[$name][$r] : $_config[$name][0]; + } + return $this->connect($dbConfig, $r, $r == $m ? false : $dbMaster); + } + + /** + * 析构方法 + * @access public + */ + public function __destruct() + { + // 释放查询 + if ($this->PDOStatement) { + $this->free(); + } + // 关闭连接 + $this->close(); + } +} diff --git a/thinkphp/library/think/db/Expression.php b/thinkphp/library/think/db/Expression.php new file mode 100644 index 0000000..f1b92ab --- /dev/null +++ b/thinkphp/library/think/db/Expression.php @@ -0,0 +1,48 @@ + +// +---------------------------------------------------------------------- + +namespace think\db; + +class Expression +{ + /** + * 查询表达式 + * + * @var string + */ + protected $value; + + /** + * 创建一个查询表达式 + * + * @param string $value + * @return void + */ + public function __construct($value) + { + $this->value = $value; + } + + /** + * 获取表达式 + * + * @return string + */ + public function getValue() + { + return $this->value; + } + + public function __toString() + { + return (string) $this->value; + } +} diff --git a/thinkphp/library/think/db/Query.php b/thinkphp/library/think/db/Query.php new file mode 100644 index 0000000..37310f5 --- /dev/null +++ b/thinkphp/library/think/db/Query.php @@ -0,0 +1,3045 @@ + +// +---------------------------------------------------------------------- + +namespace think\db; + +use PDO; +use think\App; +use think\Cache; +use think\Collection; +use think\Config; +use think\Db; +use think\db\exception\BindParamException; +use think\db\exception\DataNotFoundException; +use think\db\exception\ModelNotFoundException; +use think\Exception; +use think\exception\DbException; +use think\exception\PDOException; +use think\Loader; +use think\Model; +use think\model\Relation; +use think\model\relation\OneToOne; +use think\Paginator; + +class Query +{ + // 数据库Connection对象实例 + protected $connection; + // 数据库Builder对象实例 + protected $builder; + // 当前模型类名称 + protected $model; + // 当前数据表名称(含前缀) + protected $table = ''; + // 当前数据表名称(不含前缀) + protected $name = ''; + // 当前数据表主键 + protected $pk; + // 当前数据表前缀 + protected $prefix = ''; + // 查询参数 + protected $options = []; + // 参数绑定 + protected $bind = []; + // 数据表信息 + protected static $info = []; + // 回调事件 + private static $event = []; + // 读取主库 + protected static $readMaster = []; + + /** + * 构造函数 + * @access public + * @param Connection $connection 数据库对象实例 + * @param Model $model 模型对象 + */ + public function __construct(Connection $connection = null, $model = null) + { + $this->connection = $connection ?: Db::connect([], true); + $this->prefix = $this->connection->getConfig('prefix'); + $this->model = $model; + // 设置当前连接的Builder对象 + $this->setBuilder(); + } + + /** + * 利用__call方法实现一些特殊的Model方法 + * @access public + * @param string $method 方法名称 + * @param array $args 调用参数 + * @return mixed + * @throws DbException + * @throws Exception + */ + public function __call($method, $args) + { + if (strtolower(substr($method, 0, 5)) == 'getby') { + // 根据某个字段获取记录 + $field = Loader::parseName(substr($method, 5)); + $where[$field] = $args[0]; + return $this->where($where)->find(); + } elseif (strtolower(substr($method, 0, 10)) == 'getfieldby') { + // 根据某个字段获取记录的某个值 + $name = Loader::parseName(substr($method, 10)); + $where[$name] = $args[0]; + return $this->where($where)->value($args[1]); + } 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:' . __CLASS__ . '->' . $method); + } + } + + /** + * 获取当前的数据库Connection对象 + * @access public + * @return Connection + */ + public function getConnection() + { + return $this->connection; + } + + /** + * 切换当前的数据库连接 + * @access public + * @param mixed $config + * @return $this + */ + public function connect($config) + { + $this->connection = Db::connect($config); + $this->setBuilder(); + $this->prefix = $this->connection->getConfig('prefix'); + return $this; + } + + /** + * 设置当前的数据库Builder对象 + * @access protected + * @return void + */ + protected function setBuilder() + { + $class = $this->connection->getBuilder(); + $this->builder = new $class($this->connection, $this); + } + + /** + * 获取当前的模型对象实例 + * @access public + * @return Model|null + */ + public function getModel() + { + return $this->model; + } + + /** + * 设置后续从主库读取数据 + * @access public + * @param bool $allTable + * @return void + */ + public function readMaster($allTable = false) + { + if ($allTable) { + $table = '*'; + } else { + $table = isset($this->options['table']) ? $this->options['table'] : $this->getTable(); + } + + static::$readMaster[$table] = true; + + return $this; + } + + /** + * 获取当前的builder实例对象 + * @access public + * @return Builder + */ + public function getBuilder() + { + return $this->builder; + } + + /** + * 指定默认的数据表名(不含前缀) + * @access public + * @param string $name + * @return $this + */ + public function name($name) + { + $this->name = $name; + return $this; + } + + /** + * 指定默认数据表名(含前缀) + * @access public + * @param string $table 表名 + * @return $this + */ + public function setTable($table) + { + $this->table = $table; + return $this; + } + + /** + * 得到当前或者指定名称的数据表 + * @access public + * @param string $name + * @return string + */ + public function getTable($name = '') + { + if ($name || empty($this->table)) { + $name = $name ?: $this->name; + $tableName = $this->prefix; + if ($name) { + $tableName .= Loader::parseName($name); + } + } else { + $tableName = $this->table; + } + return $tableName; + } + + /** + * 将SQL语句中的__TABLE_NAME__字符串替换成带前缀的表名(小写) + * @access public + * @param string $sql sql语句 + * @return string + */ + public function parseSqlTable($sql) + { + if (false !== strpos($sql, '__')) { + $prefix = $this->prefix; + $sql = preg_replace_callback("/__([A-Z0-9_-]+)__/sU", function ($match) use ($prefix) { + return $prefix . strtolower($match[1]); + }, $sql); + } + return $sql; + } + + /** + * 执行查询 返回数据集 + * @access public + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @param boolean $master 是否在主服务器读操作 + * @param bool|string $class 指定返回的数据集对象 + * @return mixed + * @throws BindParamException + * @throws PDOException + */ + public function query($sql, $bind = [], $master = false, $class = false) + { + return $this->connection->query($sql, $bind, $master, $class); + } + + /** + * 执行语句 + * @access public + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @return int + * @throws BindParamException + * @throws PDOException + */ + public function execute($sql, $bind = []) + { + return $this->connection->execute($sql, $bind, $this); + } + + /** + * 获取最近插入的ID + * @access public + * @param string $sequence 自增序列名 + * @return string + */ + public function getLastInsID($sequence = null) + { + return $this->connection->getLastInsID($sequence); + } + + /** + * 获取最近一次查询的sql语句 + * @access public + * @return string + */ + public function getLastSql() + { + return $this->connection->getLastSql(); + } + + /** + * 执行数据库事务 + * @access public + * @param callable $callback 数据操作方法回调 + * @return mixed + */ + public function transaction($callback) + { + return $this->connection->transaction($callback); + } + + /** + * 启动事务 + * @access public + * @return void + */ + public function startTrans() + { + $this->connection->startTrans(); + } + + /** + * 用于非自动提交状态下面的查询提交 + * @access public + * @return void + * @throws PDOException + */ + public function commit() + { + $this->connection->commit(); + } + + /** + * 事务回滚 + * @access public + * @return void + * @throws PDOException + */ + public function rollback() + { + $this->connection->rollback(); + } + + /** + * 批处理执行SQL语句 + * 批处理的指令都认为是execute操作 + * @access public + * @param array $sql SQL批处理指令 + * @return boolean + */ + public function batchQuery($sql = [], $bind = []) + { + return $this->connection->batchQuery($sql, $bind); + } + + /** + * 获取数据库的配置参数 + * @access public + * @param string $name 参数名称 + * @return boolean + */ + public function getConfig($name = '') + { + return $this->connection->getConfig($name); + } + + /** + * 得到分表的的数据表名 + * @access public + * @param array $data 操作的数据 + * @param string $field 分表依据的字段 + * @param array $rule 分表规则 + * @return string + */ + public function getPartitionTableName($data, $field, $rule = []) + { + // 对数据表进行分区 + if ($field && isset($data[$field])) { + $value = $data[$field]; + $type = $rule['type']; + switch ($type) { + case 'id': + // 按照id范围分表 + $step = $rule['expr']; + $seq = floor($value / $step) + 1; + break; + case 'year': + // 按照年份分表 + if (!is_numeric($value)) { + $value = strtotime($value); + } + $seq = date('Y', $value) - $rule['expr'] + 1; + break; + case 'mod': + // 按照id的模数分表 + $seq = ($value % $rule['num']) + 1; + break; + case 'md5': + // 按照md5的序列分表 + $seq = (ord(substr(md5($value), 0, 1)) % $rule['num']) + 1; + break; + default: + if (function_exists($type)) { + // 支持指定函数哈希 + $seq = (ord(substr($type($value), 0, 1)) % $rule['num']) + 1; + } else { + // 按照字段的首字母的值分表 + $seq = (ord($value[0]) % $rule['num']) + 1; + } + } + return $this->getTable() . '_' . $seq; + } else { + // 当设置的分表字段不在查询条件或者数据中 + // 进行联合查询,必须设定 partition['num'] + $tableName = []; + for ($i = 0; $i < $rule['num']; $i++) { + $tableName[] = 'SELECT * FROM ' . $this->getTable() . '_' . ($i + 1); + } + + $tableName = '( ' . implode(" UNION ", $tableName) . ') AS ' . $this->name; + return $tableName; + } + } + + /** + * 得到某个字段的值 + * @access public + * @param string $field 字段名 + * @param mixed $default 默认值 + * @param bool $force 强制转为数字类型 + * @return mixed + */ + public function value($field, $default = null, $force = false) + { + $result = false; + if (empty($this->options['fetch_sql']) && !empty($this->options['cache'])) { + // 判断查询缓存 + $cache = $this->options['cache']; + if (empty($this->options['table'])) { + $this->options['table'] = $this->getTable(); + } + $key = is_string($cache['key']) ? $cache['key'] : md5($this->connection->getConfig('database') . '.' . $field . serialize($this->options) . serialize($this->bind)); + $result = Cache::get($key); + } + if (false === $result) { + if (isset($this->options['field'])) { + unset($this->options['field']); + } + $pdo = $this->field($field)->limit(1)->getPdo(); + if (is_string($pdo)) { + // 返回SQL语句 + return $pdo; + } + + $result = $pdo->fetchColumn(); + if ($force) { + $result = (float) $result; + } + + if (isset($cache) && false !== $result) { + // 缓存数据 + $this->cacheData($key, $result, $cache); + } + } else { + // 清空查询条件 + $this->options = []; + } + return false !== $result ? $result : $default; + } + + /** + * 得到某个列的数组 + * @access public + * @param string $field 字段名 多个字段用逗号分隔 + * @param string $key 索引 + * @return array + */ + public function column($field, $key = '') + { + $result = false; + if (empty($this->options['fetch_sql']) && !empty($this->options['cache'])) { + // 判断查询缓存 + $cache = $this->options['cache']; + if (empty($this->options['table'])) { + $this->options['table'] = $this->getTable(); + } + $guid = is_string($cache['key']) ? $cache['key'] : md5($this->connection->getConfig('database') . '.' . $field . serialize($this->options) . serialize($this->bind)); + $result = Cache::get($guid); + } + if (false === $result) { + if (isset($this->options['field'])) { + unset($this->options['field']); + } + if (is_null($field)) { + $field = '*'; + } elseif ($key && '*' != $field) { + $field = $key . ',' . $field; + } + $pdo = $this->field($field)->getPdo(); + if (is_string($pdo)) { + // 返回SQL语句 + return $pdo; + } + if (1 == $pdo->columnCount()) { + $result = $pdo->fetchAll(PDO::FETCH_COLUMN); + } else { + $resultSet = $pdo->fetchAll(PDO::FETCH_ASSOC); + if ($resultSet) { + $fields = array_keys($resultSet[0]); + $count = count($fields); + $key1 = array_shift($fields); + $key2 = $fields ? array_shift($fields) : ''; + $key = $key ?: $key1; + if (strpos($key, '.')) { + list($alias, $key) = explode('.', $key); + } + foreach ($resultSet as $val) { + if ($count > 2) { + $result[$val[$key]] = $val; + } elseif (2 == $count) { + $result[$val[$key]] = $val[$key2]; + } elseif (1 == $count) { + $result[$val[$key]] = $val[$key1]; + } + } + } else { + $result = []; + } + } + if (isset($cache) && isset($guid)) { + // 缓存数据 + $this->cacheData($guid, $result, $cache); + } + } else { + // 清空查询条件 + $this->options = []; + } + return $result; + } + + /** + * COUNT查询 + * @access public + * @param string $field 字段名 + * @return integer|string + */ + public function count($field = '*') + { + if (isset($this->options['group'])) { + if (!preg_match('/^[\w\.\*]+$/', $field)) { + throw new Exception('not support data:' . $field); + } + // 支持GROUP + $options = $this->getOptions(); + $subSql = $this->options($options)->field('count(' . $field . ')')->bind($this->bind)->buildSql(); + + $count = $this->table([$subSql => '_group_count_'])->value('COUNT(*) AS tp_count', 0, true); + } else { + $count = $this->aggregate('COUNT', $field, true); + } + + return is_string($count) ? $count : (int) $count; + + } + + /** + * 聚合查询 + * @access public + * @param string $aggregate 聚合方法 + * @param string $field 字段名 + * @param bool $force 强制转为数字类型 + * @return mixed + */ + public function aggregate($aggregate, $field, $force = false) + { + if (0 === stripos($field, 'DISTINCT ')) { + list($distinct, $field) = explode(' ', $field); + } + + if (!preg_match('/^[\w\.\+\-\*]+$/', $field)) { + throw new Exception('not support data:' . $field); + } + + $result = $this->value($aggregate . '(' . (!empty($distinct) ? 'DISTINCT ' : '') . $field . ') AS tp_' . strtolower($aggregate), 0, $force); + + return $result; + } + + /** + * SUM查询 + * @access public + * @param string $field 字段名 + * @return float|int + */ + public function sum($field) + { + return $this->aggregate('SUM', $field, true); + } + + /** + * MIN查询 + * @access public + * @param string $field 字段名 + * @param bool $force 强制转为数字类型 + * @return mixed + */ + public function min($field, $force = true) + { + return $this->aggregate('MIN', $field, $force); + } + + /** + * MAX查询 + * @access public + * @param string $field 字段名 + * @param bool $force 强制转为数字类型 + * @return mixed + */ + public function max($field, $force = true) + { + return $this->aggregate('MAX', $field, $force); + } + + /** + * AVG查询 + * @access public + * @param string $field 字段名 + * @return float|int + */ + public function avg($field) + { + return $this->aggregate('AVG', $field, true); + } + + /** + * 设置记录的某个字段值 + * 支持使用数据库字段和方法 + * @access public + * @param string|array $field 字段名 + * @param mixed $value 字段值 + * @return integer + */ + public function setField($field, $value = '') + { + if (is_array($field)) { + $data = $field; + } else { + $data[$field] = $value; + } + return $this->update($data); + } + + /** + * 字段值(延迟)增长 + * @access public + * @param string $field 字段名 + * @param integer $step 增长值 + * @param integer $lazyTime 延时时间(s) + * @return integer|true + * @throws Exception + */ + public function setInc($field, $step = 1, $lazyTime = 0) + { + $condition = !empty($this->options['where']) ? $this->options['where'] : []; + if (empty($condition)) { + // 没有条件不做任何更新 + throw new Exception('no data to update'); + } + if ($lazyTime > 0) { + // 延迟写入 + $guid = md5($this->getTable() . '_' . $field . '_' . serialize($condition) . serialize($this->bind)); + $step = $this->lazyWrite('inc', $guid, $step, $lazyTime); + if (false === $step) { + // 清空查询条件 + $this->options = []; + return true; + } + } + return $this->setField($field, ['inc', $step]); + } + + /** + * 字段值(延迟)减少 + * @access public + * @param string $field 字段名 + * @param integer $step 减少值 + * @param integer $lazyTime 延时时间(s) + * @return integer|true + * @throws Exception + */ + public function setDec($field, $step = 1, $lazyTime = 0) + { + $condition = !empty($this->options['where']) ? $this->options['where'] : []; + if (empty($condition)) { + // 没有条件不做任何更新 + throw new Exception('no data to update'); + } + if ($lazyTime > 0) { + // 延迟写入 + $guid = md5($this->getTable() . '_' . $field . '_' . serialize($condition) . serialize($this->bind)); + $step = $this->lazyWrite('dec', $guid, $step, $lazyTime); + if (false === $step) { + // 清空查询条件 + $this->options = []; + return true; + } + return $this->setField($field, ['inc', $step]); + } + return $this->setField($field, ['dec', $step]); + } + + /** + * 延时更新检查 返回false表示需要延时 + * 否则返回实际写入的数值 + * @access protected + * @param string $type 自增或者自减 + * @param string $guid 写入标识 + * @param integer $step 写入步进值 + * @param integer $lazyTime 延时时间(s) + * @return false|integer + */ + protected function lazyWrite($type, $guid, $step, $lazyTime) + { + if (!Cache::has($guid . '_time')) { + // 计时开始 + Cache::set($guid . '_time', $_SERVER['REQUEST_TIME'], 0); + Cache::$type($guid, $step); + } elseif ($_SERVER['REQUEST_TIME'] > Cache::get($guid . '_time') + $lazyTime) { + // 删除缓存 + $value = Cache::$type($guid, $step); + Cache::rm($guid); + Cache::rm($guid . '_time'); + return 0 === $value ? false : $value; + } else { + // 更新缓存 + Cache::$type($guid, $step); + } + return false; + } + + /** + * 查询SQL组装 join + * @access public + * @param mixed $join 关联的表名 + * @param mixed $condition 条件 + * @param string $type JOIN类型 + * @return $this + */ + public function join($join, $condition = null, $type = 'INNER') + { + if (empty($condition)) { + // 如果为组数,则循环调用join + foreach ($join as $key => $value) { + if (is_array($value) && 2 <= count($value)) { + $this->join($value[0], $value[1], isset($value[2]) ? $value[2] : $type); + } + } + } else { + $table = $this->getJoinTable($join); + + $this->options['join'][] = [$table, strtoupper($type), $condition]; + } + return $this; + } + + /** + * 获取Join表名及别名 支持 + * ['prefix_table或者子查询'=>'alias'] 'prefix_table alias' 'table alias' + * @access public + * @param array|string $join + * @return array|string + */ + protected function getJoinTable($join, &$alias = null) + { + // 传入的表名为数组 + if (is_array($join)) { + $table = $join; + $alias = array_shift($join); + } else { + $join = trim($join); + if (false !== strpos($join, '(')) { + // 使用子查询 + $table = $join; + } else { + $prefix = $this->prefix; + if (strpos($join, ' ')) { + // 使用别名 + list($table, $alias) = explode(' ', $join); + } else { + $table = $join; + if (false === strpos($join, '.') && 0 !== strpos($join, '__')) { + $alias = $join; + } + } + if ($prefix && false === strpos($table, '.') && 0 !== strpos($table, $prefix) && 0 !== strpos($table, '__')) { + $table = $this->getTable($table); + } + } + if (isset($alias) && $table != $alias) { + $table = [$table => $alias]; + } + } + return $table; + } + + /** + * 查询SQL组装 union + * @access public + * @param mixed $union + * @param boolean $all + * @return $this + */ + public function union($union, $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; + } + + /** + * 指定查询字段 支持字段排除和指定数据表 + * @access public + * @param mixed $field + * @param boolean $except 是否排除 + * @param string $tableName 数据表名 + * @param string $prefix 字段前缀 + * @param string $alias 别名前缀 + * @return $this + */ + public function field($field, $except = false, $tableName = '', $prefix = '', $alias = '') + { + if (empty($field)) { + return $this; + } elseif ($field instanceof Expression) { + $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->getTableInfo($tableName ?: (isset($this->options['table']) ? $this->options['table'] : ''), 'fields'); + $field = $fields ?: ['*']; + } elseif ($except) { + // 字段排除 + $fields = $this->getTableInfo($tableName ?: (isset($this->options['table']) ? $this->options['table'] : ''), 'fields'); + $field = $fields ? array_diff($fields, $field) : $field; + } + if ($tableName) { + // 添加统一的前缀 + $prefix = $prefix ?: $tableName; + foreach ($field as $key => $val) { + if (is_numeric($key)) { + $val = $prefix . '.' . $val . ($alias ? ' AS ' . $alias . $val : ''); + } + $field[$key] = $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 string $field 字段名 + * @param array $bind 参数绑定 + * @return $this + */ + public function fieldRaw($field, array $bind = []) + { + $this->options['field'][] = $this->raw($field); + + if ($bind) { + $this->bind($bind); + } + + return $this; + } + + /** + * 设置数据 + * @access public + * @param mixed $field 字段名或者数据 + * @param mixed $value 字段值 + * @return $this + */ + public function data($field, $value = null) + { + if (is_array($field)) { + $this->options['data'] = isset($this->options['data']) ? array_merge($this->options['data'], $field) : $field; + } else { + $this->options['data'][$field] = $value; + } + return $this; + } + + /** + * 字段值增长 + * @access public + * @param string|array $field 字段名 + * @param integer $step 增长值 + * @return $this + */ + public function inc($field, $step = 1) + { + $fields = is_string($field) ? explode(',', $field) : $field; + foreach ($fields as $field) { + $this->data($field, ['inc', $step]); + } + return $this; + } + + /** + * 字段值减少 + * @access public + * @param string|array $field 字段名 + * @param integer $step 增长值 + * @return $this + */ + public function dec($field, $step = 1) + { + $fields = is_string($field) ? explode(',', $field) : $field; + foreach ($fields as $field) { + $this->data($field, ['dec', $step]); + } + return $this; + } + + /** + * 使用表达式设置数据 + * @access public + * @param string $field 字段名 + * @param string $value 字段值 + * @return $this + */ + public function exp($field, $value) + { + $this->data($field, $this->raw($value)); + return $this; + } + + /** + * 使用表达式设置数据 + * @access public + * @param mixed $value 表达式 + * @return Expression + */ + public function raw($value) + { + return new Expression($value); + } + + /** + * 指定JOIN查询字段 + * @access public + * @param string|array $table 数据表 + * @param string|array $field 查询字段 + * @param mixed $on JOIN条件 + * @param string $type JOIN类型 + * @return $this + */ + public function view($join, $field = true, $on = null, $type = 'INNER') + { + $this->options['view'] = true; + if (is_array($join) && key($join) === 0) { + foreach ($join as $key => $val) { + $this->view($val[0], $val[1], isset($val[2]) ? $val[2] : null, isset($val[3]) ? $val[3] : 'INNER'); + } + } else { + $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] = $val; + $this->options['map'][$val] = $name; + } + } + } + $this->field($fields); + if ($on) { + $this->join($table, $on, $type); + } else { + $this->table($table); + } + } + return $this; + } + + /** + * 设置分表规则 + * @access public + * @param array $data 操作的数据 + * @param string $field 分表依据的字段 + * @param array $rule 分表规则 + * @return $this + */ + public function partition($data, $field, $rule = []) + { + $this->options['table'] = $this->getPartitionTableName($data, $field, $rule); + return $this; + } + + /** + * 指定AND查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 + * @return $this + */ + public function where($field, $op = null, $condition = null) + { + $param = func_get_args(); + array_shift($param); + $this->parseWhereExp('AND', $field, $op, $condition, $param); + return $this; + } + + /** + * 指定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); + $this->parseWhereExp('OR', $field, $op, $condition, $param); + return $this; + } + + /** + * 指定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); + $this->parseWhereExp('XOR', $field, $op, $condition, $param); + return $this; + } + + /** + * 指定表达式查询条件 + * @access public + * @param string $where 查询条件 + * @param array $bind 参数绑定 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereRaw($where, $bind = [], $logic = 'AND') + { + $this->options['where'][$logic][] = $this->raw($where); + + if ($bind) { + $this->bind($bind); + } + + return $this; + } + + /** + * 指定表达式查询条件 OR + * @access public + * @param string $where 查询条件 + * @param array $bind 参数绑定 + * @return $this + */ + public function whereOrRaw($where, $bind = []) + { + return $this->whereRaw($where, $bind, 'OR'); + } + + /** + * 指定Null查询条件 + * @access public + * @param mixed $field 查询字段 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNull($field, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'null', null, [], true); + return $this; + } + + /** + * 指定NotNull查询条件 + * @access public + * @param mixed $field 查询字段 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotNull($field, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'notnull', null, [], true); + return $this; + } + + /** + * 指定Exists查询条件 + * @access public + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereExists($condition, $logic = 'AND') + { + $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, $logic = 'AND') + { + $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($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'in', $condition, [], true); + return $this; + } + + /** + * 指定NotIn查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotIn($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'not in', $condition, [], true); + return $this; + } + + /** + * 指定Like查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereLike($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'like', $condition, [], true); + return $this; + } + + /** + * 指定NotLike查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotLike($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'not like', $condition, [], true); + return $this; + } + + /** + * 指定Between查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereBetween($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'between', $condition, [], true); + return $this; + } + + /** + * 指定NotBetween查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotBetween($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'not between', $condition, [], true); + return $this; + } + + /** + * 指定Exp查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereExp($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'exp', $this->raw($condition), [], true); + return $this; + } + + /** + * 设置软删除字段及条件 + * @access public + * @param false|string $field 查询字段 + * @param mixed $condition 查询条件 + * @return $this + */ + public function useSoftDelete($field, $condition = null) + { + if ($field) { + $this->options['soft_delete'] = [$field, $condition ?: ['null', '']]; + } + return $this; + } + + /** + * 分析查询表达式 + * @access public + * @param string $logic 查询逻辑 and or xor + * @param string|array|\Closure $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 + * @param array $param 查询参数 + * @param bool $strict 严格模式 + * @return void + */ + protected function parseWhereExp($logic, $field, $op, $condition, $param = [], $strict = false) + { + $logic = strtoupper($logic); + if ($field instanceof \Closure) { + $this->options['where'][$logic][] = is_string($op) ? [$op, $field] : $field; + return; + } + + if (is_string($field) && !empty($this->options['via']) && !strpos($field, '.')) { + $field = $this->options['via'] . '.' . $field; + } + + if ($field instanceof Expression) { + return $this->whereRaw($field, is_array($op) ? $op : []); + } elseif ($strict) { + // 使用严格模式查询 + $where[$field] = [$op, $condition]; + + // 记录一个字段多次查询条件 + $this->options['multi'][$logic][$field][] = $where[$field]; + } elseif (is_string($field) && preg_match('/[,=\>\<\'\"\(\s]/', $field)) { + $where[] = ['exp', $this->raw($field)]; + if (is_array($op)) { + // 参数绑定 + $this->bind($op); + } + } elseif (is_null($op) && is_null($condition)) { + if (is_array($field)) { + // 数组批量查询 + $where = $field; + foreach ($where as $k => $val) { + $this->options['multi'][$logic][$k][] = $val; + } + } elseif ($field && is_string($field)) { + // 字符串查询 + $where[$field] = ['null', '']; + $this->options['multi'][$logic][$field][] = $where[$field]; + } + } elseif (is_array($op)) { + $where[$field] = $param; + } elseif (in_array(strtolower($op), ['null', 'notnull', 'not null'])) { + // null查询 + $where[$field] = [$op, '']; + + $this->options['multi'][$logic][$field][] = $where[$field]; + } elseif (is_null($condition)) { + // 字段相等查询 + $where[$field] = ['eq', $op]; + + $this->options['multi'][$logic][$field][] = $where[$field]; + } else { + if ('exp' == strtolower($op)) { + $where[$field] = ['exp', $this->raw($condition)]; + // 参数绑定 + if (isset($param[2]) && is_array($param[2])) { + $this->bind($param[2]); + } + } else { + $where[$field] = [$op, $condition]; + } + // 记录一个字段多次查询条件 + $this->options['multi'][$logic][$field][] = $where[$field]; + } + + if (!empty($where)) { + if (!isset($this->options['where'][$logic])) { + $this->options['where'][$logic] = []; + } + if (is_string($field) && $this->checkMultiField($field, $logic)) { + $where[$field] = $this->options['multi'][$logic][$field]; + } elseif (is_array($field)) { + foreach ($field as $key => $val) { + if ($this->checkMultiField($key, $logic)) { + $where[$key] = $this->options['multi'][$logic][$key]; + } + } + } + $this->options['where'][$logic] = array_merge($this->options['where'][$logic], $where); + } + } + + /** + * 检查是否存在一个字段多次查询条件 + * @access public + * @param string $field 查询字段 + * @param string $logic 查询逻辑 and or xor + * @return bool + */ + private function checkMultiField($field, $logic) + { + return isset($this->options['multi'][$logic][$field]) && count($this->options['multi'][$logic][$field]) > 1; + } + + /** + * 去除某个查询条件 + * @access public + * @param string $field 查询字段 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function removeWhereField($field, $logic = 'AND') + { + $logic = strtoupper($logic); + if (isset($this->options['where'][$logic][$field])) { + unset($this->options['where'][$logic][$field]); + unset($this->options['multi'][$logic][$field]); + } + return $this; + } + + /** + * 去除查询参数 + * @access public + * @param string|bool $option 参数名 true 表示去除所有参数 + * @return $this + */ + public function removeOption($option = true) + { + if (true === $option) { + $this->options = []; + } elseif (is_string($option) && isset($this->options[$option])) { + unset($this->options[$option]); + } + return $this; + } + + /** + * 指定查询数量 + * @access public + * @param mixed $offset 起始位置 + * @param mixed $length 查询数量 + * @return $this + */ + public function limit($offset, $length = null) + { + if (is_null($length) && strpos($offset, ',')) { + list($offset, $length) = explode(',', $offset); + } + $this->options['limit'] = intval($offset) . ($length ? ',' . intval($length) : ''); + return $this; + } + + /** + * 指定分页 + * @access public + * @param mixed $page 页数 + * @param mixed $listRows 每页数量 + * @return $this + */ + public function page($page, $listRows = null) + { + if (is_null($listRows) && strpos($page, ',')) { + list($page, $listRows) = explode(',', $page); + } + $this->options['page'] = [intval($page), intval($listRows)]; + return $this; + } + + /** + * 分页查询 + * @param int|array $listRows 每页数量 数组表示配置参数 + * @param int|bool $simple 是否简洁模式或者总记录数 + * @param array $config 配置参数 + * page:当前页, + * path:url路径, + * query:url额外参数, + * fragment:url锚点, + * var_page:分页变量, + * list_rows:每页数量 + * type:分页类名 + * @return \think\Paginator + * @throws DbException + */ + public function paginate($listRows = null, $simple = false, $config = []) + { + if (is_int($simple)) { + $total = $simple; + $simple = false; + } + if (is_array($listRows)) { + $config = array_merge(Config::get('paginate'), $listRows); + $listRows = $config['list_rows']; + } else { + $config = array_merge(Config::get('paginate'), $config); + $listRows = $listRows ?: $config['list_rows']; + } + + /** @var Paginator $class */ + $class = false !== strpos($config['type'], '\\') ? $config['type'] : '\\think\\paginator\\driver\\' . ucwords($config['type']); + $page = isset($config['page']) ? (int) $config['page'] : call_user_func([ + $class, + 'getCurrentPage', + ], $config['var_page']); + + $page = $page < 1 ? 1 : $page; + + $config['path'] = isset($config['path']) ? $config['path'] : call_user_func([$class, '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(); + } + return $class::make($results, $listRows, $page, $total, $simple, $config); + } + + /** + * 指定当前操作的数据表 + * @access public + * @param mixed $table 表名 + * @return $this + */ + public function table($table) + { + if (is_string($table)) { + if (strpos($table, ')')) { + // 子查询 + } elseif (strpos($table, ',')) { + $tables = explode(',', $table); + $table = []; + foreach ($tables as $item) { + list($item, $alias) = explode(' ', trim($item)); + if ($alias) { + $this->alias([$item => $alias]); + $table[$item] = $alias; + } else { + $table[] = $item; + } + } + } elseif (strpos($table, ' ')) { + list($table, $alias) = explode(' ', $table); + + $table = [$table => $alias]; + $this->alias($table); + } + } else { + $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; + } + + /** + * USING支持 用于多表删除 + * @access public + * @param mixed $using + * @return $this + */ + public function using($using) + { + $this->options['using'] = $using; + return $this; + } + + /** + * 指定排序 order('id','desc') 或者 order(['id'=>'desc','create_time'=>'desc']) + * @access public + * @param string|array $field 排序字段 + * @param string $order 排序 + * @return $this + */ + public function order($field, $order = null) + { + if (empty($field)) { + return $this; + } elseif ($field instanceof Expression) { + $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; + } + + /** + * 表达式方式指定Field排序 + * @access public + * @param string $field 排序字段 + * @param array $bind 参数绑定 + * @return $this + */ + public function orderRaw($field, array $bind = []) + { + $this->options['order'][] = $this->raw($field); + + if ($bind) { + $this->bind($bind); + } + + return $this; + } + + /** + * 查询缓存 + * @access public + * @param mixed $key 缓存key + * @param integer|\DateTime $expire 缓存有效期 + * @param string $tag 缓存标签 + * @return $this + */ + public function cache($key = true, $expire = null, $tag = null) + { + // 增加快捷调用方式 cache(10) 等同于 cache(true, 10) + if ($key instanceof \DateTime || (is_numeric($key) && is_null($expire))) { + $expire = $key; + $key = true; + } + + if (false !== $key) { + $this->options['cache'] = ['key' => $key, 'expire' => $expire, 'tag' => $tag]; + } + return $this; + } + + /** + * 指定group查询 + * @access public + * @param string $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($having) + { + $this->options['having'] = $having; + return $this; + } + + /** + * 指定查询lock + * @access public + * @param bool|string $lock 是否lock + * @return $this + */ + public function lock($lock = false) + { + $this->options['lock'] = $lock; + $this->options['master'] = true; + return $this; + } + + /** + * 指定distinct查询 + * @access public + * @param string $distinct 是否唯一 + * @return $this + */ + public function distinct($distinct) + { + $this->options['distinct'] = $distinct; + return $this; + } + + /** + * 指定数据表别名 + * @access public + * @param mixed $alias 数据表别名 + * @return $this + */ + public function alias($alias) + { + if (is_array($alias)) { + foreach ($alias as $key => $val) { + if (false !== strpos($key, '__')) { + $table = $this->parseSqlTable($key); + } else { + $table = $key; + } + $this->options['alias'][$table] = $val; + } + } else { + if (isset($this->options['table'])) { + $table = is_array($this->options['table']) ? key($this->options['table']) : $this->options['table']; + if (false !== strpos($table, '__')) { + $table = $this->parseSqlTable($table); + } + } else { + $table = $this->getTable(); + } + + $this->options['alias'][$table] = $alias; + } + return $this; + } + + /** + * 指定强制索引 + * @access public + * @param string $force 索引名称 + * @return $this + */ + public function force($force) + { + $this->options['force'] = $force; + return $this; + } + + /** + * 查询注释 + * @access public + * @param string $comment 注释 + * @return $this + */ + public function comment($comment) + { + $this->options['comment'] = $comment; + return $this; + } + + /** + * 获取执行的SQL语句 + * @access public + * @param boolean $fetch 是否返回sql + * @return $this + */ + public function fetchSql($fetch = true) + { + $this->options['fetch_sql'] = $fetch; + return $this; + } + + /** + * 不主动获取数据集 + * @access public + * @param bool $pdo 是否返回 PDOStatement 对象 + * @return $this + */ + public function fetchPdo($pdo = true) + { + $this->options['fetch_pdo'] = $pdo; + return $this; + } + + /** + * 设置从主服务器读取数据 + * @access public + * @return $this + */ + public function master() + { + $this->options['master'] = true; + return $this; + } + + /** + * 设置是否严格检查字段名 + * @access public + * @param bool $strict 是否严格检查字段 + * @return $this + */ + public function strict($strict = true) + { + $this->options['strict'] = $strict; + return $this; + } + + /** + * 设置查询数据不存在是否抛出异常 + * @access public + * @param bool $fail 数据不存在是否抛出异常 + * @return $this + */ + public function failException($fail = true) + { + $this->options['fail'] = $fail; + return $this; + } + + /** + * 设置自增序列名 + * @access public + * @param string $sequence 自增序列名 + * @return $this + */ + public function sequence($sequence = null) + { + $this->options['sequence'] = $sequence; + return $this; + } + + /** + * 指定数据表主键 + * @access public + * @param string $pk 主键 + * @return $this + */ + public function pk($pk) + { + $this->pk = $pk; + return $this; + } + + /** + * 查询日期或者时间 + * @access public + * @param string $field 日期字段名 + * @param string|array $op 比较运算符或者表达式 + * @param string|array $range 比较范围 + * @return $this + */ + public function whereTime($field, $op, $range = null) + { + if (is_null($range)) { + if (is_array($op)) { + $range = $op; + } else { + // 使用日期表达式 + switch (strtolower($op)) { + case 'today': + case 'd': + $range = ['today', 'tomorrow']; + break; + case 'week': + case 'w': + $range = ['this week 00:00:00', 'next week 00:00:00']; + break; + case 'month': + case 'm': + $range = ['first Day of this month 00:00:00', 'first Day of next month 00:00:00']; + break; + case 'year': + case 'y': + $range = ['this year 1/1', 'next year 1/1']; + break; + case 'yesterday': + $range = ['yesterday', 'today']; + break; + case 'last week': + $range = ['last week 00:00:00', 'this week 00:00:00']; + break; + case 'last month': + $range = ['first Day of last month 00:00:00', 'first Day of this month 00:00:00']; + break; + case 'last year': + $range = ['last year 1/1', 'this year 1/1']; + break; + default: + $range = $op; + } + } + $op = is_array($range) ? 'between' : '>'; + } + $this->where($field, strtolower($op) . ' time', $range); + return $this; + } + + /** + * 获取数据表信息 + * @access public + * @param mixed $tableName 数据表名 留空自动获取 + * @param string $fetch 获取信息类型 包括 fields type bind pk + * @return mixed + */ + public function getTableInfo($tableName = '', $fetch = '') + { + if (!$tableName) { + $tableName = $this->getTable(); + } + if (is_array($tableName)) { + $tableName = key($tableName) ?: current($tableName); + } + + if (strpos($tableName, ',')) { + // 多表不获取字段信息 + return false; + } else { + $tableName = $this->parseSqlTable($tableName); + } + + // 修正子查询作为表名的问题 + if (strpos($tableName, ')')) { + return []; + } + + list($guid) = explode(' ', $tableName); + $db = $this->getConfig('database'); + if (!isset(self::$info[$db . '.' . $guid])) { + if (!strpos($guid, '.')) { + $schema = $db . '.' . $guid; + } else { + $schema = $guid; + } + // 读取缓存 + if (!App::$debug && is_file(RUNTIME_PATH . 'schema/' . $schema . '.php')) { + $info = include RUNTIME_PATH . 'schema/' . $schema . '.php'; + } else { + $info = $this->connection->getFields($guid); + } + $fields = array_keys($info); + $bind = $type = []; + foreach ($info as $key => $val) { + // 记录字段类型 + $type[$key] = $val['type']; + $bind[$key] = $this->getFieldBindType($val['type']); + if (!empty($val['primary'])) { + $pk[] = $key; + } + } + if (isset($pk)) { + // 设置主键 + $pk = count($pk) > 1 ? $pk : $pk[0]; + } else { + $pk = null; + } + self::$info[$db . '.' . $guid] = ['fields' => $fields, 'type' => $type, 'bind' => $bind, 'pk' => $pk]; + } + return $fetch ? self::$info[$db . '.' . $guid][$fetch] : self::$info[$db . '.' . $guid]; + } + + /** + * 获取当前数据表的主键 + * @access public + * @param string|array $options 数据表名或者查询参数 + * @return string|array + */ + public function getPk($options = '') + { + if (!empty($this->pk)) { + $pk = $this->pk; + } else { + $pk = $this->getTableInfo(is_array($options) && isset($options['table']) ? $options['table'] : $options, 'pk'); + } + return $pk; + } + + // 获取当前数据表字段信息 + public function getTableFields($table = '') + { + return $this->getTableInfo($table ?: $this->getOptions('table'), 'fields'); + } + + // 获取当前数据表字段类型 + public function getFieldsType($table = '') + { + return $this->getTableInfo($table ?: $this->getOptions('table'), 'type'); + } + + // 获取当前数据表绑定信息 + public function getFieldsBind($table = '') + { + $types = $this->getFieldsType($table); + $bind = []; + if ($types) { + foreach ($types as $key => $type) { + $bind[$key] = $this->getFieldBindType($type); + } + } + return $bind; + } + + /** + * 获取字段绑定类型 + * @access public + * @param string $type 字段类型 + * @return integer + */ + protected function getFieldBindType($type) + { + if (0 === strpos($type, 'set') || 0 === strpos($type, 'enum')) { + $bind = PDO::PARAM_STR; + } elseif (preg_match('/(int|double|float|decimal|real|numeric|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; + } + + /** + * 参数绑定 + * @access public + * @param mixed $key 参数名 + * @param mixed $value 绑定变量值 + * @param integer $type 绑定类型 + * @return $this + */ + public function bind($key, $value = false, $type = PDO::PARAM_STR) + { + if (is_array($key)) { + $this->bind = array_merge($this->bind, $key); + } else { + $this->bind[$key] = [$value, $type]; + } + return $this; + } + + /** + * 检测参数是否已经绑定 + * @access public + * @param string $key 参数名 + * @return bool + */ + public function isBind($key) + { + return isset($this->bind[$key]); + } + + /** + * 查询参数赋值 + * @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($name = '') + { + if ('' === $name) { + return $this->options; + } else { + return isset($this->options[$name]) ? $this->options[$name] : null; + } + } + + /** + * 设置关联查询JOIN预查询 + * @access public + * @param string|array $with 关联方法名称 + * @return $this + */ + public function with($with) + { + if (empty($with)) { + return $this; + } + + if (is_string($with)) { + $with = explode(',', $with); + } + + $first = true; + + /** @var Model $class */ + $class = $this->model; + foreach ($with as $key => $relation) { + $subRelation = ''; + $closure = false; + if ($relation instanceof \Closure) { + // 支持闭包查询过滤关联条件 + $closure = $relation; + $relation = $key; + $with[$key] = $key; + } elseif (is_array($relation)) { + $subRelation = $relation; + $relation = $key; + } elseif (is_string($relation) && strpos($relation, '.')) { + $with[$key] = $relation; + list($relation, $subRelation) = explode('.', $relation, 2); + } + + /** @var Relation $model */ + $relation = Loader::parseName($relation, 1, false); + $model = $class->$relation(); + if ($model instanceof OneToOne && 0 == $model->getEagerlyType()) { + $model->eagerly($this, $relation, $subRelation, $closure, $first); + $first = false; + } elseif ($closure) { + $with[$key] = $closure; + } + } + $this->via(); + if (isset($this->options['with'])) { + $this->options['with'] = array_merge($this->options['with'], $with); + } else { + $this->options['with'] = $with; + } + return $this; + } + + /** + * 关联统计 + * @access public + * @param string|array $relation 关联方法名 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + public function withCount($relation, $subQuery = true) + { + if (!$subQuery) { + $this->options['with_count'] = $relation; + } else { + $relations = is_string($relation) ? explode(',', $relation) : $relation; + if (!isset($this->options['field'])) { + $this->field('*'); + } + foreach ($relations as $key => $relation) { + $closure = $name = null; + if ($relation instanceof \Closure) { + $closure = $relation; + $relation = $key; + } elseif (!is_int($key)) { + $name = $relation; + $relation = $key; + } + $relation = Loader::parseName($relation, 1, false); + + $count = '(' . $this->model->$relation()->getRelationCountQuery($closure, $name) . ')'; + + if (empty($name)) { + $name = Loader::parseName($relation) . '_count'; + } + + $this->field([$count => $name]); + } + } + return $this; + } + + /** + * 关联预加载中 获取关联指定字段值 + * example: + * Model::with(['relation' => function($query){ + * $query->withField("id,name"); + * }]) + * + * @param string | array $field 指定获取的字段 + * @return $this + */ + public function withField($field) + { + $this->options['with_field'] = $field; + return $this; + } + + /** + * 设置当前字段添加的表别名 + * @access public + * @param string $via + * @return $this + */ + public function via($via = '') + { + $this->options['via'] = $via; + return $this; + } + + /** + * 设置关联查询 + * @access public + * @param string|array $relation 关联名称 + * @return $this + */ + public function relation($relation) + { + if (empty($relation)) { + return $this; + } + if (is_string($relation)) { + $relation = explode(',', $relation); + } + if (isset($this->options['relation'])) { + $this->options['relation'] = array_merge($this->options['relation'], $relation); + } else { + $this->options['relation'] = $relation; + } + return $this; + } + + /** + * 把主键值转换为查询条件 支持复合主键 + * @access public + * @param array|string $data 主键数据 + * @param mixed $options 表达式参数 + * @return void + * @throws Exception + */ + protected function parsePkWhere($data, &$options) + { + $pk = $this->getPk($options); + // 获取当前数据表 + $table = is_array($options['table']) ? key($options['table']) : $options['table']; + if (!empty($options['alias'][$table])) { + $alias = $options['alias'][$table]; + } + if (is_string($pk)) { + $key = isset($alias) ? $alias . '.' . $pk : $pk; + // 根据主键查询 + if (is_array($data)) { + $where[$key] = isset($data[$pk]) ? $data[$pk] : ['in', $data]; + } else { + $where[$key] = strpos($data, ',') ? ['IN', $data] : $data; + } + } elseif (is_array($pk) && is_array($data) && !empty($data)) { + // 根据复合主键查询 + foreach ($pk as $key) { + if (isset($data[$key])) { + $attr = isset($alias) ? $alias . '.' . $key : $key; + $where[$attr] = $data[$key]; + } else { + throw new Exception('miss complex primary data'); + } + } + } + + if (!empty($where)) { + if (isset($options['where']['AND'])) { + $options['where']['AND'] = array_merge($options['where']['AND'], $where); + } else { + $options['where']['AND'] = $where; + } + } + return; + } + + /** + * 插入记录 + * @access public + * @param mixed $data 数据 + * @param boolean $replace 是否replace + * @param boolean $getLastInsID 返回自增主键 + * @param string $sequence 自增序列名 + * @return integer|string + */ + public function insert(array $data = [], $replace = false, $getLastInsID = false, $sequence = null) + { + // 分析查询表达式 + $options = $this->parseExpress(); + $data = array_merge($options['data'], $data); + // 生成SQL语句 + $sql = $this->builder->insert($data, $options, $replace); + // 获取参数绑定 + $bind = $this->getBind(); + if ($options['fetch_sql']) { + // 获取实际执行的SQL语句 + return $this->connection->getRealSql($sql, $bind); + } + + // 执行操作 + $result = 0 === $sql ? 0 : $this->execute($sql, $bind, $this); + if ($result) { + $sequence = $sequence ?: (isset($options['sequence']) ? $options['sequence'] : null); + $lastInsId = $this->getLastInsID($sequence); + if ($lastInsId) { + $pk = $this->getPk($options); + if (is_string($pk)) { + $data[$pk] = $lastInsId; + } + } + $options['data'] = $data; + $this->trigger('after_insert', $options); + + if ($getLastInsID) { + return $lastInsId; + } + } + return $result; + } + + /** + * 插入记录并获取自增ID + * @access public + * @param mixed $data 数据 + * @param boolean $replace 是否replace + * @param string $sequence 自增序列名 + * @return integer|string + */ + public function insertGetId(array $data, $replace = false, $sequence = null) + { + return $this->insert($data, $replace, true, $sequence); + } + + /** + * 批量插入记录 + * @access public + * @param mixed $dataSet 数据集 + * @param boolean $replace 是否replace + * @param integer $limit 每次写入数据限制 + * @return integer|string + */ + public function insertAll(array $dataSet, $replace = false, $limit = null) + { + // 分析查询表达式 + $options = $this->parseExpress(); + if (!is_array(reset($dataSet))) { + return false; + } + + // 生成SQL语句 + if (is_null($limit)) { + $sql = $this->builder->insertAll($dataSet, $options, $replace); + } else { + $array = array_chunk($dataSet, $limit, true); + foreach ($array as $item) { + $sql[] = $this->builder->insertAll($item, $options, $replace); + } + } + + // 获取参数绑定 + $bind = $this->getBind(); + if ($options['fetch_sql']) { + // 获取实际执行的SQL语句 + return $this->connection->getRealSql($sql, $bind); + } elseif (is_array($sql)) { + // 执行操作 + return $this->batchQuery($sql, $bind, $this); + } else { + // 执行操作 + return $this->execute($sql, $bind, $this); + } + } + + /** + * 通过Select方式插入记录 + * @access public + * @param string $fields 要插入的数据表字段名 + * @param string $table 要插入的数据表名 + * @return integer|string + * @throws PDOException + */ + public function selectInsert($fields, $table) + { + // 分析查询表达式 + $options = $this->parseExpress(); + // 生成SQL语句 + $table = $this->parseSqlTable($table); + $sql = $this->builder->selectInsert($fields, $table, $options); + // 获取参数绑定 + $bind = $this->getBind(); + if ($options['fetch_sql']) { + // 获取实际执行的SQL语句 + return $this->connection->getRealSql($sql, $bind); + } else { + // 执行操作 + return $this->execute($sql, $bind, $this); + } + } + + /** + * 更新记录 + * @access public + * @param mixed $data 数据 + * @return integer|string + * @throws Exception + * @throws PDOException + */ + public function update(array $data = []) + { + $options = $this->parseExpress(); + $data = array_merge($options['data'], $data); + $pk = $this->getPk($options); + if (isset($options['cache']) && is_string($options['cache']['key'])) { + $key = $options['cache']['key']; + } + + if (empty($options['where'])) { + // 如果存在主键数据 则自动作为更新条件 + if (is_string($pk) && isset($data[$pk])) { + $where[$pk] = $data[$pk]; + if (!isset($key)) { + $key = 'think:' . $options['table'] . '|' . $data[$pk]; + } + unset($data[$pk]); + } elseif (is_array($pk)) { + // 增加复合主键支持 + foreach ($pk as $field) { + if (isset($data[$field])) { + $where[$field] = $data[$field]; + } else { + // 如果缺少复合主键数据则不执行 + throw new Exception('miss complex primary data'); + } + unset($data[$field]); + } + } + if (!isset($where)) { + // 如果没有任何更新条件则不执行 + throw new Exception('miss update condition'); + } else { + $options['where']['AND'] = $where; + } + } elseif (!isset($key) && is_string($pk) && isset($options['where']['AND'][$pk])) { + $key = $this->getCacheKey($options['where']['AND'][$pk], $options, $this->bind); + } + + // 生成UPDATE SQL语句 + $sql = $this->builder->update($data, $options); + // 获取参数绑定 + $bind = $this->getBind(); + if ($options['fetch_sql']) { + // 获取实际执行的SQL语句 + return $this->connection->getRealSql($sql, $bind); + } else { + // 检测缓存 + if (isset($key) && Cache::get($key)) { + // 删除缓存 + Cache::rm($key); + } elseif (!empty($options['cache']['tag'])) { + Cache::clear($options['cache']['tag']); + } + // 执行操作 + $result = '' == $sql ? 0 : $this->execute($sql, $bind, $this); + if ($result) { + if (is_string($pk) && isset($where[$pk])) { + $data[$pk] = $where[$pk]; + } elseif (is_string($pk) && isset($key) && strpos($key, '|')) { + list($a, $val) = explode('|', $key); + $data[$pk] = $val; + } + $options['data'] = $data; + $this->trigger('after_update', $options); + } + return $result; + } + } + + /** + * 执行查询但只返回PDOStatement对象 + * @access public + * @return \PDOStatement|string + */ + public function getPdo() + { + // 分析查询表达式 + $options = $this->parseExpress(); + // 生成查询SQL + $sql = $this->builder->select($options); + // 获取参数绑定 + $bind = $this->getBind(); + if ($options['fetch_sql']) { + // 获取实际执行的SQL语句 + return $this->connection->getRealSql($sql, $bind); + } + // 执行查询操作 + return $this->query($sql, $bind, $options['master'], true); + } + + /** + * 查找记录 + * @access public + * @param array|string|Query|\Closure $data + * @return Collection|false|\PDOStatement|string + * @throws DbException + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function select($data = null) + { + if ($data instanceof Query) { + return $data->select(); + } elseif ($data instanceof \Closure) { + call_user_func_array($data, [ & $this]); + $data = null; + } + // 分析查询表达式 + $options = $this->parseExpress(); + + if (false === $data) { + // 用于子查询 不查询只返回SQL + $options['fetch_sql'] = true; + } elseif (!is_null($data)) { + // 主键条件分析 + $this->parsePkWhere($data, $options); + } + + $resultSet = false; + if (empty($options['fetch_sql']) && !empty($options['cache'])) { + // 判断查询缓存 + $cache = $options['cache']; + unset($options['cache']); + $key = is_string($cache['key']) ? $cache['key'] : md5($this->connection->getConfig('database') . '.' . serialize($options) . serialize($this->bind)); + $resultSet = Cache::get($key); + } + if (false === $resultSet) { + // 生成查询SQL + $sql = $this->builder->select($options); + // 获取参数绑定 + $bind = $this->getBind(); + if ($options['fetch_sql']) { + // 获取实际执行的SQL语句 + return $this->connection->getRealSql($sql, $bind); + } + + $options['data'] = $data; + if ($resultSet = $this->trigger('before_select', $options)) { + } else { + // 执行查询操作 + $resultSet = $this->query($sql, $bind, $options['master'], $options['fetch_pdo']); + + if ($resultSet instanceof \PDOStatement) { + // 返回PDOStatement对象 + return $resultSet; + } + } + + if (isset($cache) && false !== $resultSet) { + // 缓存数据集 + $this->cacheData($key, $resultSet, $cache); + } + } + + // 数据列表读取后的处理 + if (!empty($this->model)) { + // 生成模型对象 + if (count($resultSet) > 0) { + foreach ($resultSet as $key => $result) { + /** @var Model $model */ + $model = $this->model->newInstance($result); + $model->isUpdate(true); + + // 关联查询 + if (!empty($options['relation'])) { + $model->relationQuery($options['relation']); + } + // 关联统计 + if (!empty($options['with_count'])) { + $model->relationCount($model, $options['with_count']); + } + $resultSet[$key] = $model; + } + if (!empty($options['with'])) { + // 预载入 + $model->eagerlyResultSet($resultSet, $options['with']); + } + // 模型数据集转换 + $resultSet = $model->toCollection($resultSet); + } else { + $resultSet = $this->model->toCollection($resultSet); + } + } elseif ('collection' == $this->connection->getConfig('resultset_type')) { + // 返回Collection对象 + $resultSet = new Collection($resultSet); + } + // 返回结果处理 + if (!empty($options['fail']) && count($resultSet) == 0) { + $this->throwNotFound($options); + } + return $resultSet; + } + + /** + * 缓存数据 + * @access public + * @param string $key 缓存标识 + * @param mixed $data 缓存数据 + * @param array $config 缓存参数 + */ + protected function cacheData($key, $data, $config = []) + { + if (isset($config['tag'])) { + Cache::tag($config['tag'])->set($key, $data, $config['expire']); + } else { + Cache::set($key, $data, $config['expire']); + } + } + + /** + * 生成缓存标识 + * @access public + * @param mixed $value 缓存数据 + * @param array $options 缓存参数 + * @param array $bind 绑定参数 + * @return string + */ + protected function getCacheKey($value, $options, $bind = []) + { + if (is_scalar($value)) { + $data = $value; + } elseif (is_array($value) && is_string($value[0]) && 'eq' == strtolower($value[0])) { + $data = $value[1]; + } + $prefix = $this->connection->getConfig('database') . '.'; + + if (isset($data)) { + return 'think:' . $prefix . (is_array($options['table']) ? key($options['table']) : $options['table']) . '|' . $data; + } + + try { + return md5($prefix . serialize($options) . serialize($bind)); + } catch (\Exception $e) { + throw new Exception('closure not support cache(true)'); + } + } + + /** + * 查找单条记录 + * @access public + * @param array|string|Query|\Closure $data + * @return array|false|\PDOStatement|string|Model + * @throws DbException + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function find($data = null) + { + if ($data instanceof Query) { + return $data->find(); + } elseif ($data instanceof \Closure) { + call_user_func_array($data, [ & $this]); + $data = null; + } + // 分析查询表达式 + $options = $this->parseExpress(); + $pk = $this->getPk($options); + if (!is_null($data)) { + // AR模式分析主键条件 + $this->parsePkWhere($data, $options); + } elseif (!empty($options['cache']) && true === $options['cache']['key'] && is_string($pk) && isset($options['where']['AND'][$pk])) { + $key = $this->getCacheKey($options['where']['AND'][$pk], $options, $this->bind); + } + + $options['limit'] = 1; + $result = false; + if (empty($options['fetch_sql']) && !empty($options['cache'])) { + // 判断查询缓存 + $cache = $options['cache']; + if (true === $cache['key'] && !is_null($data) && !is_array($data)) { + $key = 'think:' . $this->connection->getConfig('database') . '.' . (is_array($options['table']) ? key($options['table']) : $options['table']) . '|' . $data; + } elseif (is_string($cache['key'])) { + $key = $cache['key']; + } elseif (!isset($key)) { + $key = md5($this->connection->getConfig('database') . '.' . serialize($options) . serialize($this->bind)); + } + $result = Cache::get($key); + } + if (false === $result) { + // 生成查询SQL + $sql = $this->builder->select($options); + // 获取参数绑定 + $bind = $this->getBind(); + if ($options['fetch_sql']) { + // 获取实际执行的SQL语句 + return $this->connection->getRealSql($sql, $bind); + } + if (is_string($pk)) { + if (!is_array($data)) { + if (isset($key) && strpos($key, '|')) { + list($a, $val) = explode('|', $key); + $item[$pk] = $val; + } else { + $item[$pk] = $data; + } + $data = $item; + } + } + $options['data'] = $data; + // 事件回调 + if ($result = $this->trigger('before_find', $options)) { + } else { + // 执行查询 + $resultSet = $this->query($sql, $bind, $options['master'], $options['fetch_pdo']); + + if ($resultSet instanceof \PDOStatement) { + // 返回PDOStatement对象 + return $resultSet; + } + $result = isset($resultSet[0]) ? $resultSet[0] : null; + } + + if (isset($cache) && $result) { + // 缓存数据 + $this->cacheData($key, $result, $cache); + } + } + + // 数据处理 + if (!empty($result)) { + if (!empty($this->model)) { + // 返回模型对象 + $result = $this->model->newInstance($result); + $result->isUpdate(true, isset($options['where']['AND']) ? $options['where']['AND'] : null); + // 关联查询 + if (!empty($options['relation'])) { + $result->relationQuery($options['relation']); + } + // 预载入查询 + if (!empty($options['with'])) { + $result->eagerlyResult($result, $options['with']); + } + // 关联统计 + if (!empty($options['with_count'])) { + $result->relationCount($result, $options['with_count']); + } + } + } elseif (!empty($options['fail'])) { + $this->throwNotFound($options); + } + return $result; + } + + /** + * 查询失败 抛出异常 + * @access public + * @param array $options 查询参数 + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + protected function throwNotFound($options = []) + { + if (!empty($this->model)) { + $class = get_class($this->model); + throw new ModelNotFoundException('model data Not Found:' . $class, $class, $options); + } else { + $table = is_array($options['table']) ? key($options['table']) : $options['table']; + throw new DataNotFoundException('table data not Found:' . $table, $table, $options); + } + } + + /** + * 查找多条记录 如果不存在则抛出异常 + * @access public + * @param array|string|Query|\Closure $data + * @return array|\PDOStatement|string|Model + * @throws DbException + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function selectOrFail($data = null) + { + return $this->failException(true)->select($data); + } + + /** + * 查找单条记录 如果不存在则抛出异常 + * @access public + * @param array|string|Query|\Closure $data + * @return array|\PDOStatement|string|Model + * @throws DbException + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function findOrFail($data = null) + { + return $this->failException(true)->find($data); + } + + /** + * 分批数据返回处理 + * @access public + * @param integer $count 每次处理的数据数量 + * @param callable $callback 处理回调方法 + * @param string $column 分批处理的字段名 + * @param string $order 排序规则 + * @return boolean + * @throws \LogicException + */ + public function chunk($count, $callback, $column = null, $order = 'asc') + { + $options = $this->getOptions(); + if (empty($options['table'])) { + $options['table'] = $this->getTable(); + } + $column = $column ?: $this->getPk($options); + + if (isset($options['order'])) { + if (App::$debug) { + throw new \LogicException('chunk not support call order'); + } + unset($options['order']); + } + $bind = $this->bind; + if (is_array($column)) { + $times = 1; + $query = $this->options($options)->page($times, $count); + } else { + if (strpos($column, '.')) { + list($alias, $key) = explode('.', $column); + } else { + $key = $column; + } + $query = $this->options($options)->limit($count); + } + $resultSet = $query->order($column, $order)->select(); + + while (count($resultSet) > 0) { + if ($resultSet instanceof Collection) { + $resultSet = $resultSet->all(); + } + + if (false === call_user_func($callback, $resultSet)) { + return false; + } + + if (is_array($column)) { + $times++; + $query = $this->options($options)->page($times, $count); + } else { + $end = end($resultSet); + $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; + } + + /** + * 获取绑定的参数 并清空 + * @access public + * @return array + */ + public function getBind() + { + $bind = $this->bind; + $this->bind = []; + return $bind; + } + + /** + * 创建子查询SQL + * @access public + * @param bool $sub + * @return string + * @throws DbException + */ + public function buildSql($sub = true) + { + return $sub ? '( ' . $this->select(false) . ' )' : $this->select(false); + } + + /** + * 删除记录 + * @access public + * @param mixed $data 表达式 true 表示强制删除 + * @return int + * @throws Exception + * @throws PDOException + */ + public function delete($data = null) + { + // 分析查询表达式 + $options = $this->parseExpress(); + $pk = $this->getPk($options); + if (isset($options['cache']) && is_string($options['cache']['key'])) { + $key = $options['cache']['key']; + } + + if (!is_null($data) && true !== $data) { + if (!isset($key) && !is_array($data)) { + // 缓存标识 + $key = 'think:' . $options['table'] . '|' . $data; + } + // AR模式分析主键条件 + $this->parsePkWhere($data, $options); + } elseif (!isset($key) && is_string($pk) && isset($options['where']['AND'][$pk])) { + $key = $this->getCacheKey($options['where']['AND'][$pk], $options, $this->bind); + } + + if (true !== $data && empty($options['where'])) { + // 如果条件为空 不进行删除操作 除非设置 1=1 + throw new Exception('delete without condition'); + } + // 生成删除SQL语句 + $sql = $this->builder->delete($options); + // 获取参数绑定 + $bind = $this->getBind(); + if ($options['fetch_sql']) { + // 获取实际执行的SQL语句 + return $this->connection->getRealSql($sql, $bind); + } + + // 检测缓存 + if (isset($key) && Cache::get($key)) { + // 删除缓存 + Cache::rm($key); + } elseif (!empty($options['cache']['tag'])) { + Cache::clear($options['cache']['tag']); + } + // 执行操作 + $result = $this->execute($sql, $bind, $this); + if ($result) { + if (!is_array($data) && is_string($pk) && isset($key) && strpos($key, '|')) { + list($a, $val) = explode('|', $key); + $item[$pk] = $val; + $data = $item; + } + $options['data'] = $data; + $this->trigger('after_delete', $options); + } + return $result; + } + + /** + * 分析表达式(可用于查询或者写入操作) + * @access protected + * @return array + */ + protected function parseExpress() + { + $options = $this->options; + + // 获取数据表 + if (empty($options['table'])) { + $options['table'] = $this->getTable(); + } + + if (!isset($options['where'])) { + $options['where'] = []; + } elseif (isset($options['view'])) { + // 视图查询条件处理 + foreach (['AND', 'OR'] as $logic) { + if (isset($options['where'][$logic])) { + foreach ($options['where'][$logic] as $key => $val) { + if (array_key_exists($key, $options['map'])) { + $options['where'][$logic][$options['map'][$key]] = $val; + unset($options['where'][$logic][$key]); + } + } + } + } + + if (isset($options['order'])) { + // 视图查询排序处理 + if (is_string($options['order'])) { + $options['order'] = explode(',', $options['order']); + } + foreach ($options['order'] as $key => $val) { + if (is_numeric($key)) { + if (strpos($val, ' ')) { + list($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]); + } + } + } + } + + if (!isset($options['field'])) { + $options['field'] = '*'; + } + + if (!isset($options['data'])) { + $options['data'] = []; + } + + if (!isset($options['strict'])) { + $options['strict'] = $this->getConfig('fields_strict'); + } + + foreach (['master', 'lock', 'fetch_pdo', 'fetch_sql', 'distinct'] as $name) { + if (!isset($options[$name])) { + $options[$name] = false; + } + } + + if (isset(static::$readMaster['*']) || (is_string($options['table']) && isset(static::$readMaster[$options['table']]))) { + $options['master'] = true; + } + + foreach (['join', 'union', 'group', 'having', 'limit', 'order', 'force', 'comment'] as $name) { + if (!isset($options[$name])) { + $options[$name] = ''; + } + } + + if (isset($options['page'])) { + // 根据页数计算limit + list($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['limit'] = $offset . ',' . $listRows; + } + + $this->options = []; + return $options; + } + + /** + * 注册回调方法 + * @access public + * @param string $event 事件名 + * @param callable $callback 回调方法 + * @return void + */ + public static function event($event, $callback) + { + self::$event[$event] = $callback; + } + + /** + * 触发事件 + * @access protected + * @param string $event 事件名 + * @param mixed $params 额外参数 + * @return bool + */ + protected function trigger($event, $params = []) + { + $result = false; + if (isset(self::$event[$event])) { + $callback = self::$event[$event]; + $result = call_user_func_array($callback, [$params, $this]); + } + return $result; + } +} diff --git a/thinkphp/library/think/db/builder/Mysql.php b/thinkphp/library/think/db/builder/Mysql.php new file mode 100644 index 0000000..be2af71 --- /dev/null +++ b/thinkphp/library/think/db/builder/Mysql.php @@ -0,0 +1,137 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\builder; + +use think\db\Builder; +use think\Exception; + +/** + * mysql数据库驱动 + */ +class Mysql extends Builder +{ + + protected $insertAllSql = '%INSERT% INTO %TABLE% (%FIELD%) VALUES %DATA% %COMMENT%'; + protected $updateSql = 'UPDATE %TABLE% %JOIN% SET %SET% %WHERE% %ORDER%%LIMIT% %LOCK%%COMMENT%'; + + /** + * 生成insertall SQL + * @access public + * @param array $dataSet 数据集 + * @param array $options 表达式 + * @param bool $replace 是否replace + * @return string + * @throws Exception + */ + public function insertAll($dataSet, $options = [], $replace = false) + { + // 获取合法的字段 + if ('*' == $options['field']) { + $fields = array_keys($this->query->getFieldsType($options['table'])); + } else { + $fields = $options['field']; + } + + foreach ($dataSet as $data) { + foreach ($data as $key => $val) { + if (!in_array($key, $fields, true)) { + if ($options['strict']) { + throw new Exception('fields not exists:[' . $key . ']'); + } + unset($data[$key]); + } elseif (is_null($val)) { + $data[$key] = 'NULL'; + } elseif (is_scalar($val)) { + $data[$key] = $this->parseValue($val, $key); + } elseif (is_object($val) && method_exists($val, '__toString')) { + // 对象数据写入 + $data[$key] = $val->__toString(); + } else { + // 过滤掉非标量数据 + unset($data[$key]); + } + } + $value = array_values($data); + $values[] = '( ' . implode(',', $value) . ' )'; + + if (!isset($insertFields)) { + $insertFields = array_map([$this, 'parseKey'], array_keys($data)); + } + } + + return str_replace( + ['%INSERT%', '%TABLE%', '%FIELD%', '%DATA%', '%COMMENT%'], + [ + $replace ? 'REPLACE' : 'INSERT', + $this->parseTable($options['table'], $options), + implode(' , ', $insertFields), + implode(' , ', $values), + $this->parseComment($options['comment']), + ], $this->insertAllSql); + } + + /** + * 字段和表名处理 + * @access protected + * @param mixed $key + * @param array $options + * @return string + */ + protected function parseKey($key, $options = [], $strict = false) + { + if (is_numeric($key)) { + return $key; + } elseif ($key instanceof Expression) { + return $key->getValue(); + } + + $key = trim($key); + if (strpos($key, '$.') && false === strpos($key, '(')) { + // JSON字段支持 + list($field, $name) = explode('$.', $key); + return 'json_extract(' . $field . ', \'$.' . $name . '\')'; + } elseif (strpos($key, '.') && !preg_match('/[,\'\"\(\)`\s]/', $key)) { + list($table, $key) = explode('.', $key, 2); + if ('__TABLE__' == $table) { + $table = $this->query->getTable(); + } + if (isset($options['alias'][$table])) { + $table = $options['alias'][$table]; + } + } + + if ($strict && !preg_match('/^[\w\.\*]+$/', $key)) { + throw new Exception('not support data:' . $key); + } + if ('*' != $key && ($strict || !preg_match('/[,\'\"\*\(\)`.\s]/', $key))) { + $key = '`' . $key . '`'; + } + if (isset($table)) { + if (strpos($table, '.')) { + $table = str_replace('.', '`.`', $table); + } + $key = '`' . $table . '`.' . $key; + } + return $key; + } + + /** + * 随机排序 + * @access protected + * @return string + */ + protected function parseRand() + { + return 'rand()'; + } + +} diff --git a/thinkphp/library/think/db/builder/Pgsql.php b/thinkphp/library/think/db/builder/Pgsql.php new file mode 100644 index 0000000..acc2289 --- /dev/null +++ b/thinkphp/library/think/db/builder/Pgsql.php @@ -0,0 +1,89 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\builder; + +use think\db\Builder; + +/** + * Pgsql数据库驱动 + */ +class Pgsql extends Builder +{ + protected $insertSql = 'INSERT INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%'; + protected $insertAllSql = 'INSERT INTO %TABLE% (%FIELD%) %DATA% %COMMENT%'; + + /** + * limit分析 + * @access protected + * @param mixed $limit + * @return string + */ + public function parseLimit($limit) + { + $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 mixed $key + * @param array $options + * @return string + */ + protected function parseKey($key, $options = [], $strict = false) + { + if (is_numeric($key)) { + return $key; + } elseif ($key instanceof Expression) { + return $key->getValue(); + } + + $key = trim($key); + if (strpos($key, '$.') && false === strpos($key, '(')) { + // JSON字段支持 + list($field, $name) = explode('$.', $key); + $key = $field . '->>\'' . $name . '\''; + } elseif (strpos($key, '.')) { + list($table, $key) = explode('.', $key, 2); + if ('__TABLE__' == $table) { + $table = $this->query->getTable(); + } + if (isset($options['alias'][$table])) { + $table = $options['alias'][$table]; + } + } + if (isset($table)) { + $key = $table . '.' . $key; + } + return $key; + } + + /** + * 随机排序 + * @access protected + * @return string + */ + protected function parseRand() + { + return 'RANDOM()'; + } + +} diff --git a/thinkphp/library/think/db/builder/Sqlite.php b/thinkphp/library/think/db/builder/Sqlite.php new file mode 100644 index 0000000..c727f04 --- /dev/null +++ b/thinkphp/library/think/db/builder/Sqlite.php @@ -0,0 +1,82 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\builder; + +use think\db\Builder; + +/** + * Sqlite数据库驱动 + */ +class Sqlite extends Builder +{ + + /** + * limit + * @access public + * @param string $limit + * @return string + */ + public function parseLimit($limit) + { + $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 + * @return string + */ + protected function parseRand() + { + return 'RANDOM()'; + } + + /** + * 字段和表名处理 + * @access protected + * @param mixed $key + * @param array $options + * @return string + */ + protected function parseKey($key, $options = [], $strict = false) + { + if (is_numeric($key)) { + return $key; + } elseif ($key instanceof Expression) { + return $key->getValue(); + } + + $key = trim($key); + if (strpos($key, '.')) { + list($table, $key) = explode('.', $key, 2); + if ('__TABLE__' == $table) { + $table = $this->query->getTable(); + } + if (isset($options['alias'][$table])) { + $table = $options['alias'][$table]; + } + } + if (isset($table)) { + $key = $table . '.' . $key; + } + return $key; + } +} diff --git a/thinkphp/library/think/db/builder/Sqlsrv.php b/thinkphp/library/think/db/builder/Sqlsrv.php new file mode 100644 index 0000000..dc425d9 --- /dev/null +++ b/thinkphp/library/think/db/builder/Sqlsrv.php @@ -0,0 +1,137 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\builder; + +use think\db\Builder; +use think\db\Expression; + +/** + * Sqlsrv数据库驱动 + */ +class Sqlsrv extends Builder +{ + 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%'; + protected $selectInsertSql = 'SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%'; + protected $updateSql = 'UPDATE %TABLE% SET %SET% FROM %TABLE% %JOIN% %WHERE% %LIMIT% %LOCK%%COMMENT%'; + protected $deleteSql = 'DELETE FROM %TABLE% %USING% FROM %TABLE% %JOIN% %WHERE% %LIMIT% %LOCK%%COMMENT%'; + protected $insertSql = 'INSERT INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%'; + protected $insertAllSql = 'INSERT INTO %TABLE% (%FIELD%) %DATA% %COMMENT%'; + + /** + * order分析 + * @access protected + * @param mixed $order + * @param array $options + * @return string + */ + protected function parseOrder($order, $options = []) + { + if (empty($order)) { + return ' ORDER BY rand()'; + } + + $array = []; + foreach ($order as $key => $val) { + if ($val instanceof Expression) { + $array[] = $val->getValue(); + } elseif (is_numeric($key)) { + if (false === strpos($val, '(')) { + $array[] = $this->parseKey($val, $options); + } elseif ('[rand]' == $val) { + $array[] = $this->parseRand(); + } else { + $array[] = $val; + } + } else { + $sort = in_array(strtolower(trim($val)), ['asc', 'desc'], true) ? ' ' . $val : ''; + $array[] = $this->parseKey($key, $options, true) . ' ' . $sort; + } + } + + return ' ORDER BY ' . implode(',', $array); + } + + /** + * 随机排序 + * @access protected + * @return string + */ + protected function parseRand() + { + return 'rand()'; + } + + /** + * 字段和表名处理 + * @access protected + * @param mixed $key + * @param array $options + * @return string + */ + protected function parseKey($key, $options = [], $strict = false) + { + if (is_numeric($key)) { + return $key; + } elseif ($key instanceof Expression) { + return $key->getValue(); + } + $key = trim($key); + if (strpos($key, '.') && !preg_match('/[,\'\"\(\)\[\s]/', $key)) { + list($table, $key) = explode('.', $key, 2); + if ('__TABLE__' == $table) { + $table = $this->query->getTable(); + } + if (isset($options['alias'][$table])) { + $table = $options['alias'][$table]; + } + } + + if ($strict && !preg_match('/^[\w\.\*]+$/', $key)) { + throw new Exception('not support data:' . $key); + } + if ('*' != $key && ($strict || !preg_match('/[,\'\"\*\(\)\[.\s]/', $key))) { + $key = '[' . $key . ']'; + } + if (isset($table)) { + $key = '[' . $table . '].' . $key; + } + return $key; + } + + /** + * limit + * @access protected + * @param mixed $limit + * @return string + */ + protected function parseLimit($limit) + { + 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($fields, $table, $options) + { + $this->selectSql = $this->selectInsertSql; + return parent::selectInsert($fields, $table, $options); + } + +} diff --git a/thinkphp/library/think/db/connector/Mysql.php b/thinkphp/library/think/db/connector/Mysql.php new file mode 100644 index 0000000..be1a85c --- /dev/null +++ b/thinkphp/library/think/db/connector/Mysql.php @@ -0,0 +1,126 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\connector; + +use PDO; +use think\db\Connection; +use think\Log; + +/** + * mysql数据库驱动 + */ +class Mysql extends Connection +{ + + protected $builder = '\\think\\db\\builder\\Mysql'; + + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn($config) + { + 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($tableName) + { + list($tableName) = explode(' ', $tableName); + if (false === strpos($tableName, '`')) { + if (strpos($tableName, '.')) { + $tableName = str_replace('.', '`.`', $tableName); + } + $tableName = '`' . $tableName . '`'; + } + $sql = 'SHOW COLUMNS FROM ' . $tableName; + $pdo = $this->query($sql, [], false, true); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + if ($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']), // not null is empty, null is yes + 'default' => $val['default'], + 'primary' => (strtolower($val['key']) == 'pri'), + 'autoinc' => (strtolower($val['extra']) == 'auto_increment'), + ]; + } + } + return $this->fieldCase($info); + } + + /** + * 取得数据库的表信息 + * @access public + * @param string $dbName + * @return array + */ + public function getTables($dbName = '') + { + $sql = !empty($dbName) ? 'SHOW TABLES FROM ' . $dbName : 'SHOW TABLES '; + $pdo = $this->query($sql, [], false, true); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + return $info; + } + + /** + * SQL性能分析 + * @access protected + * @param string $sql + * @return array + */ + protected function getExplain($sql) + { + $pdo = $this->linkID->query("EXPLAIN " . $sql); + $result = $pdo->fetch(PDO::FETCH_ASSOC); + $result = array_change_key_case($result); + if (isset($result['extra'])) { + if (strpos($result['extra'], 'filesort') || strpos($result['extra'], 'temporary')) { + Log::record('SQL:' . $this->queryStr . '[' . $result['extra'] . ']', 'warn'); + } + } + return $result; + } + + protected function supportSavepoint() + { + return true; + } + +} diff --git a/thinkphp/library/think/db/connector/Pgsql.php b/thinkphp/library/think/db/connector/Pgsql.php new file mode 100644 index 0000000..bbcf576 --- /dev/null +++ b/thinkphp/library/think/db/connector/Pgsql.php @@ -0,0 +1,103 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\connector; + +use PDO; +use think\db\Connection; + +/** + * Pgsql数据库驱动 + */ +class Pgsql extends Connection +{ + protected $builder = '\\think\\db\\builder\\Pgsql'; + + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn($config) + { + $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($tableName) + { + + list($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->query($sql, [], false, true); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + if ($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($dbName = '') + { + $sql = "select tablename as Tables_in_test from pg_tables where schemaname ='public'"; + $pdo = $this->query($sql, [], false, true); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + return $info; + } + + /** + * SQL性能分析 + * @access protected + * @param string $sql + * @return array + */ + protected function getExplain($sql) + { + return []; + } + + protected function supportSavepoint() + { + return true; + } +} diff --git a/thinkphp/library/think/db/connector/Sqlite.php b/thinkphp/library/think/db/connector/Sqlite.php new file mode 100644 index 0000000..c4e3a72 --- /dev/null +++ b/thinkphp/library/think/db/connector/Sqlite.php @@ -0,0 +1,104 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\connector; + +use PDO; +use think\db\Connection; + +/** + * Sqlite数据库驱动 + */ +class Sqlite extends Connection +{ + + protected $builder = '\\think\\db\\builder\\Sqlite'; + + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn($config) + { + $dsn = 'sqlite:' . $config['database']; + return $dsn; + } + + /** + * 取得数据表的字段信息 + * @access public + * @param string $tableName + * @return array + */ + public function getFields($tableName) + { + list($tableName) = explode(' ', $tableName); + $sql = 'PRAGMA table_info( ' . $tableName . ' )'; + + $pdo = $this->query($sql, [], false, true); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + if ($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($dbName = '') + { + + $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->query($sql, [], false, true); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + return $info; + } + + /** + * SQL性能分析 + * @access protected + * @param string $sql + * @return array + */ + protected function getExplain($sql) + { + return []; + } + + protected function supportSavepoint() + { + return true; + } +} diff --git a/thinkphp/library/think/db/connector/Sqlsrv.php b/thinkphp/library/think/db/connector/Sqlsrv.php new file mode 100644 index 0000000..35c6600 --- /dev/null +++ b/thinkphp/library/think/db/connector/Sqlsrv.php @@ -0,0 +1,125 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\connector; + +use PDO; +use think\db\Connection; + +/** + * Sqlsrv数据库驱动 + */ +class Sqlsrv extends Connection +{ + // PDO连接参数 + protected $params = [ + PDO::ATTR_CASE => PDO::CASE_NATURAL, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_STRINGIFY_FETCHES => false, + ]; + protected $builder = '\\think\\db\\builder\\Sqlsrv'; + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn($config) + { + $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($tableName) + { + list($tableName) = explode(' ', $tableName); + $tableNames = explode('.', $tableName); + $tableName = isset($tableNames[1]) ? $tableNames[1] : $tableNames[0]; + + $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->query($sql, [], false, true); + $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' => (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'"; + // 调试开始 + $this->debug(true); + $pdo = $this->linkID->query($sql); + // 调试结束 + $this->debug(false, $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($dbName = '') + { + $sql = "SELECT TABLE_NAME + FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_TYPE = 'BASE TABLE' + "; + + $pdo = $this->query($sql, [], false, true); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + return $info; + } + + /** + * SQL性能分析 + * @access protected + * @param string $sql + * @return array + */ + protected function getExplain($sql) + { + return []; + } +} diff --git a/thinkphp/library/think/db/connector/pgsql.sql b/thinkphp/library/think/db/connector/pgsql.sql new file mode 100644 index 0000000..e1a09a3 --- /dev/null +++ b/thinkphp/library/think/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/thinkphp/library/think/db/exception/BindParamException.php b/thinkphp/library/think/db/exception/BindParamException.php new file mode 100644 index 0000000..4ed1954 --- /dev/null +++ b/thinkphp/library/think/db/exception/BindParamException.php @@ -0,0 +1,35 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\exception; + +use think\exception\DbException; + +/** + * PDO参数绑定异常 + */ +class BindParamException extends DbException +{ + + /** + * BindParamException constructor. + * @param string $message + * @param array $config + * @param string $sql + * @param array $bind + * @param int $code + */ + public function __construct($message, $config, $sql, $bind, $code = 10502) + { + $this->setData('Bind Param', $bind); + parent::__construct($message, $config, $sql, $code); + } +} diff --git a/thinkphp/library/think/db/exception/DataNotFoundException.php b/thinkphp/library/think/db/exception/DataNotFoundException.php new file mode 100644 index 0000000..f2542ac --- /dev/null +++ b/thinkphp/library/think/db/exception/DataNotFoundException.php @@ -0,0 +1,43 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\exception; + +use think\exception\DbException; + +class DataNotFoundException extends DbException +{ + protected $table; + + /** + * DbException constructor. + * @param string $message + * @param string $table + * @param array $config + */ + public function __construct($message, $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/thinkphp/library/think/db/exception/ModelNotFoundException.php b/thinkphp/library/think/db/exception/ModelNotFoundException.php new file mode 100644 index 0000000..6e5f930 --- /dev/null +++ b/thinkphp/library/think/db/exception/ModelNotFoundException.php @@ -0,0 +1,43 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\exception; + +use think\exception\DbException; + +class ModelNotFoundException extends DbException +{ + protected $model; + + /** + * 构造方法 + * @param string $message + * @param string $model + */ + public function __construct($message, $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/thinkphp/library/think/debug/Console.php b/thinkphp/library/think/debug/Console.php new file mode 100644 index 0000000..c17911b --- /dev/null +++ b/thinkphp/library/think/debug/Console.php @@ -0,0 +1,160 @@ + +// +---------------------------------------------------------------------- + +namespace think\debug; + +use think\Cache; +use think\Config; +use think\Db; +use think\Debug; +use think\Request; +use think\Response; + +/** + * 浏览器调试输出 + */ +class Console +{ + protected $config = [ + 'trace_tabs' => ['base' => '基本', 'file' => '文件', 'info' => '流程', 'notice|error' => '错误', 'sql' => 'SQL', 'debug|log' => '调试'], + ]; + + // 实例化并传入参数 + public function __construct($config = []) + { + if (is_array($config)) { + $this->config = array_merge($this->config, $config); + } + } + + /** + * 调试输出接口 + * @access public + * @param Response $response Response对象 + * @param array $log 日志信息 + * @return bool + */ + public function output(Response $response, array $log = []) + { + $request = Request::instance(); + $contentType = $response->getHeader('Content-Type'); + $accept = $request->header('accept'); + if (strpos($accept, 'application/json') === 0 || $request->isAjax()) { + return false; + } elseif (!empty($contentType) && strpos($contentType, 'html') === false) { + return false; + } + // 获取基本信息 + $runtime = number_format(microtime(true) - THINK_START_TIME, 10); + $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; + $mem = number_format((memory_get_usage() - THINK_START_MEM) / 1024, 2); + + if (isset($_SERVER['HTTP_HOST'])) { + $uri = $_SERVER['SERVER_PROTOCOL'] . ' ' . $_SERVER['REQUEST_METHOD'] . ' : ' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; + } else { + $uri = 'cmd:' . implode(' ', $_SERVER['argv']); + } + + // 页面Trace信息 + $base = [ + '请求信息' => date('Y-m-d H:i:s', $_SERVER['REQUEST_TIME']) . ' ' . $uri, + '运行时间' => number_format($runtime, 6) . 's [ 吞吐率:' . $reqs . 'req/s ] 内存消耗:' . $mem . 'kb 文件加载:' . count(get_included_files()), + '查询信息' => Db::$queryTimes . ' queries ' . Db::$executeTimes . ' writes ', + '缓存信息' => Cache::$readTimes . ' reads,' . Cache::$writeTimes . ' writes', + '配置加载' => count(Config::get()), + ]; + + if (session_id()) { + $base['会话信息'] = 'SESSION_ID=' . session_id(); + } + + $info = Debug::getFile(true); + + // 页面Trace信息 + $trace = []; + foreach ($this->config['trace_tabs'] as $name => $title) { + $name = strtolower($name); + switch ($name) { + case 'base': // 基本信息 + $trace[$title] = $base; + break; + case 'file': // 文件信息 + $trace[$title] = $info; + break; + default: // 调试信息 + if (strpos($name, '|')) { + // 多组信息 + $names = explode('|', $name); + $result = []; + foreach ($names as $name) { + $result = array_merge($result, isset($log[$name]) ? $log[$name] : []); + } + $trace[$title] = $result; + } else { + $trace[$title] = isset($log[$name]) ? $log[$name] : ''; + } + } + } + + //输出到控制台 + $lines = ''; + foreach ($trace as $type => $msg) { + $lines .= $this->console($type, $msg); + } + $js = << +{$lines} + +JS; + return $js; + } + + protected function console($type, $msg) + { + $type = strtolower($type); + $trace_tabs = array_values($this->config['trace_tabs']); + $line[] = ($type == $trace_tabs[0] || '调试' == $type || '错误' == $type) + ? "console.group('{$type}');" + : "console.groupCollapsed('{$type}');"; + + foreach ((array) $msg as $key => $m) { + switch ($type) { + case '调试': + $var_type = gettype($m); + if (in_array($var_type, ['array', 'string'])) { + $line[] = "console.log(" . json_encode($m) . ");"; + } else { + $line[] = "console.log(" . json_encode(var_export($m, 1)) . ");"; + } + break; + case '错误': + $msg = str_replace("\n", '\n', json_encode($m)); + $style = 'color:#F4006B;font-size:14px;'; + $line[] = "console.error(\"%c{$msg}\", \"{$style}\");"; + break; + case 'sql': + $msg = str_replace("\n", '\n', $m); + $style = "color:#009bb4;"; + $line[] = "console.log(\"%c{$msg}\", \"{$style}\");"; + break; + default: + $m = is_string($key) ? $key . ' ' . $m : $key + 1 . ' ' . $m; + $msg = json_encode($m); + $line[] = "console.log({$msg});"; + break; + } + } + $line[] = "console.groupEnd();"; + return implode(PHP_EOL, $line); + } + +} diff --git a/thinkphp/library/think/debug/Html.php b/thinkphp/library/think/debug/Html.php new file mode 100644 index 0000000..b6be7ad --- /dev/null +++ b/thinkphp/library/think/debug/Html.php @@ -0,0 +1,111 @@ + +// +---------------------------------------------------------------------- + +namespace think\debug; + +use think\Cache; +use think\Config; +use think\Db; +use think\Debug; +use think\Request; +use think\Response; + +/** + * 页面Trace调试 + */ +class Html +{ + protected $config = [ + 'trace_file' => '', + 'trace_tabs' => ['base' => '基本', 'file' => '文件', 'info' => '流程', 'notice|error' => '错误', 'sql' => 'SQL', 'debug|log' => '调试'], + ]; + + // 实例化并传入参数 + public function __construct(array $config = []) + { + $this->config['trace_file'] = THINK_PATH . 'tpl/page_trace.tpl'; + $this->config = array_merge($this->config, $config); + } + + /** + * 调试输出接口 + * @access public + * @param Response $response Response对象 + * @param array $log 日志信息 + * @return bool + */ + public function output(Response $response, array $log = []) + { + $request = Request::instance(); + $contentType = $response->getHeader('Content-Type'); + $accept = $request->header('accept'); + if (strpos($accept, 'application/json') === 0 || $request->isAjax()) { + return false; + } elseif (!empty($contentType) && strpos($contentType, 'html') === false) { + return false; + } + // 获取基本信息 + $runtime = number_format(microtime(true) - THINK_START_TIME, 10, '.', ''); + $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; + $mem = number_format((memory_get_usage() - THINK_START_MEM) / 1024, 2); + + // 页面Trace信息 + if (isset($_SERVER['HTTP_HOST'])) { + $uri = $_SERVER['SERVER_PROTOCOL'] . ' ' . $_SERVER['REQUEST_METHOD'] . ' : ' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; + } else { + $uri = 'cmd:' . implode(' ', $_SERVER['argv']); + } + $base = [ + '请求信息' => date('Y-m-d H:i:s', $_SERVER['REQUEST_TIME']) . ' ' . $uri, + '运行时间' => number_format($runtime, 6) . 's [ 吞吐率:' . $reqs . 'req/s ] 内存消耗:' . $mem . 'kb 文件加载:' . count(get_included_files()), + '查询信息' => Db::$queryTimes . ' queries ' . Db::$executeTimes . ' writes ', + '缓存信息' => Cache::$readTimes . ' reads,' . Cache::$writeTimes . ' writes', + '配置加载' => count(Config::get()), + ]; + + if (session_id()) { + $base['会话信息'] = 'SESSION_ID=' . session_id(); + } + + $info = Debug::getFile(true); + + // 页面Trace信息 + $trace = []; + foreach ($this->config['trace_tabs'] as $name => $title) { + $name = strtolower($name); + switch ($name) { + case 'base': // 基本信息 + $trace[$title] = $base; + break; + case 'file': // 文件信息 + $trace[$title] = $info; + break; + default: // 调试信息 + if (strpos($name, '|')) { + // 多组信息 + $names = explode('|', $name); + $result = []; + foreach ($names as $name) { + $result = array_merge($result, isset($log[$name]) ? $log[$name] : []); + } + $trace[$title] = $result; + } else { + $trace[$title] = isset($log[$name]) ? $log[$name] : ''; + } + } + } + // 调用Trace页面模板 + ob_start(); + include $this->config['trace_file']; + return ob_get_clean(); + } + +} diff --git a/thinkphp/library/think/exception/ClassNotFoundException.php b/thinkphp/library/think/exception/ClassNotFoundException.php new file mode 100644 index 0000000..eb22e73 --- /dev/null +++ b/thinkphp/library/think/exception/ClassNotFoundException.php @@ -0,0 +1,32 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +class ClassNotFoundException extends \RuntimeException +{ + protected $class; + public function __construct($message, $class = '') + { + $this->message = $message; + $this->class = $class; + } + + /** + * 获取类名 + * @access public + * @return string + */ + public function getClass() + { + return $this->class; + } +} diff --git a/thinkphp/library/think/exception/DbException.php b/thinkphp/library/think/exception/DbException.php new file mode 100644 index 0000000..0ae80ad --- /dev/null +++ b/thinkphp/library/think/exception/DbException.php @@ -0,0 +1,43 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +use think\Exception; + +/** + * Database相关异常处理类 + */ +class DbException extends Exception +{ + /** + * DbException constructor. + * @param string $message + * @param array $config + * @param string $sql + * @param int $code + */ + public function __construct($message, array $config, $sql, $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); + } + +} diff --git a/thinkphp/library/think/exception/ErrorException.php b/thinkphp/library/think/exception/ErrorException.php new file mode 100644 index 0000000..b3a9a30 --- /dev/null +++ b/thinkphp/library/think/exception/ErrorException.php @@ -0,0 +1,57 @@ + +// +---------------------------------------------------------------------- + +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; + + /** + * 错误异常构造函数 + * @param integer $severity 错误级别 + * @param string $message 错误详细信息 + * @param string $file 出错文件路径 + * @param integer $line 出错行号 + * @param array $context 错误上下文,会包含错误触发处作用域内所有变量的数组 + */ + public function __construct($severity, $message, $file, $line, array $context = []) + { + $this->severity = $severity; + $this->message = $message; + $this->file = $file; + $this->line = $line; + $this->code = 0; + + empty($context) || $this->setData('Error Context', $context); + } + + /** + * 获取错误级别 + * @return integer 错误级别 + */ + final public function getSeverity() + { + return $this->severity; + } +} diff --git a/thinkphp/library/think/exception/Handle.php b/thinkphp/library/think/exception/Handle.php new file mode 100644 index 0000000..f523db0 --- /dev/null +++ b/thinkphp/library/think/exception/Handle.php @@ -0,0 +1,282 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +use Exception; +use think\App; +use think\Config; +use think\console\Output; +use think\Lang; +use think\Log; +use think\Response; + +class Handle +{ + protected $render; + protected $ignoreReport = [ + '\\think\\exception\\HttpException', + ]; + + public function setRender($render) + { + $this->render = $render; + } + + /** + * Report or log an exception. + * + * @param \Exception $exception + * @return void + */ + public function report(Exception $exception) + { + if (!$this->isIgnoreReport($exception)) { + // 收集异常数据 + if (App::$debug) { + $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 (Config::get('record_trace')) { + $log .= "\r\n" . $exception->getTraceAsString(); + } + + Log::record($log, 'error'); + } + } + + protected function isIgnoreReport(Exception $exception) + { + foreach ($this->ignoreReport as $class) { + if ($exception instanceof $class) { + return true; + } + } + return false; + } + + /** + * Render an exception into an HTTP response. + * + * @param \Exception $e + * @return Response + */ + public function render(Exception $e) + { + if ($this->render && $this->render instanceof \Closure) { + $result = call_user_func_array($this->render, [$e]); + if ($result) { + return $result; + } + } + + if ($e instanceof HttpException) { + return $this->renderHttpException($e); + } else { + return $this->convertExceptionToResponse($e); + } + } + + /** + * @param Output $output + * @param Exception $e + */ + public function renderForConsole(Output $output, Exception $e) + { + if (App::$debug) { + $output->setVerbosity(Output::VERBOSITY_DEBUG); + } + $output->renderException($e); + } + + /** + * @param HttpException $e + * @return Response + */ + protected function renderHttpException(HttpException $e) + { + $status = $e->getStatusCode(); + $template = Config::get('http_exception_template'); + if (!App::$debug && !empty($template[$status])) { + return Response::create($template[$status], 'view', $status)->assign(['e' => $e]); + } else { + return $this->convertExceptionToResponse($e); + } + } + + /** + * @param Exception $exception + * @return Response + */ + protected function convertExceptionToResponse(Exception $exception) + { + // 收集异常数据 + if (App::$debug) { + // 调试模式,获取详细的错误信息 + $data = [ + 'name' => get_class($exception), + 'file' => $exception->getFile(), + 'line' => $exception->getLine(), + 'message' => $this->getMessage($exception), + 'trace' => $exception->getTrace(), + 'code' => $this->getCode($exception), + 'source' => $this->getSourceCode($exception), + 'datas' => $this->getExtendData($exception), + 'tables' => [ + 'GET Data' => $_GET, + 'POST Data' => $_POST, + 'Files' => $_FILES, + 'Cookies' => $_COOKIE, + 'Session' => isset($_SESSION) ? $_SESSION : [], + 'Server/Request Data' => $_SERVER, + 'Environment Variables' => $_ENV, + 'ThinkPHP Constants' => $this->getConst(), + ], + ]; + } else { + // 部署模式仅显示 Code 和 Message + $data = [ + 'code' => $this->getCode($exception), + 'message' => $this->getMessage($exception), + ]; + + if (!Config::get('show_error_msg')) { + // 不显示详细错误信息 + $data['message'] = Config::get('error_message'); + } + } + + //保留一层 + while (ob_get_level() > 1) { + ob_end_clean(); + } + + $data['echo'] = ob_get_clean(); + + ob_start(); + extract($data); + include Config::get('exception_tmpl'); + // 获取并清空缓存 + $content = ob_get_clean(); + $response = new Response($content, 'html'); + + if ($exception instanceof HttpException) { + $statusCode = $exception->getStatusCode(); + $response->header($exception->getHeaders()); + } + + if (!isset($statusCode)) { + $statusCode = 500; + } + $response->code($statusCode); + return $response; + } + + /** + * 获取错误编码 + * ErrorException则使用错误级别作为错误编码 + * @param \Exception $exception + * @return integer 错误编码 + */ + protected function getCode(Exception $exception) + { + $code = $exception->getCode(); + if (!$code && $exception instanceof ErrorException) { + $code = $exception->getSeverity(); + } + return $code; + } + + /** + * 获取错误信息 + * ErrorException则使用错误级别作为错误编码 + * @param \Exception $exception + * @return string 错误信息 + */ + protected function getMessage(Exception $exception) + { + $message = $exception->getMessage(); + if (IS_CLI) { + return $message; + } + + 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行 + * @param \Exception $exception + * @return array 错误文件内容 + */ + protected function getSourceCode(Exception $exception) + { + // 读取前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返回类型显示 + * @param \Exception $exception + * @return array 异常类定义的扩展数据 + */ + protected function getExtendData(Exception $exception) + { + $data = []; + if ($exception instanceof \think\Exception) { + $data = $exception->getData(); + } + return $data; + } + + /** + * 获取常量列表 + * @return array 常量列表 + */ + private static function getConst() + { + return get_defined_constants(true)['user']; + } +} diff --git a/thinkphp/library/think/exception/HttpException.php b/thinkphp/library/think/exception/HttpException.php new file mode 100644 index 0000000..01a27fc --- /dev/null +++ b/thinkphp/library/think/exception/HttpException.php @@ -0,0 +1,36 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +class HttpException extends \RuntimeException +{ + private $statusCode; + private $headers; + + public function __construct($statusCode, $message = null, \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/thinkphp/library/think/exception/HttpResponseException.php b/thinkphp/library/think/exception/HttpResponseException.php new file mode 100644 index 0000000..5297286 --- /dev/null +++ b/thinkphp/library/think/exception/HttpResponseException.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +use think\Response; + +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/thinkphp/library/think/exception/PDOException.php b/thinkphp/library/think/exception/PDOException.php new file mode 100644 index 0000000..044f82a --- /dev/null +++ b/thinkphp/library/think/exception/PDOException.php @@ -0,0 +1,39 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +/** + * PDO异常处理类 + * 重新封装了系统的\PDOException类 + */ +class PDOException extends DbException +{ + /** + * PDOException constructor. + * @param \PDOException $exception + * @param array $config + * @param string $sql + * @param int $code + */ + public function __construct(\PDOException $exception, array $config, $sql, $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/thinkphp/library/think/exception/RouteNotFoundException.php b/thinkphp/library/think/exception/RouteNotFoundException.php new file mode 100644 index 0000000..d22e3a6 --- /dev/null +++ b/thinkphp/library/think/exception/RouteNotFoundException.php @@ -0,0 +1,22 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +class RouteNotFoundException extends HttpException +{ + + public function __construct() + { + parent::__construct(404, 'Route Not Found'); + } + +} diff --git a/thinkphp/library/think/exception/TemplateNotFoundException.php b/thinkphp/library/think/exception/TemplateNotFoundException.php new file mode 100644 index 0000000..4202069 --- /dev/null +++ b/thinkphp/library/think/exception/TemplateNotFoundException.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +class TemplateNotFoundException extends \RuntimeException +{ + protected $template; + + public function __construct($message, $template = '') + { + $this->message = $message; + $this->template = $template; + } + + /** + * 获取模板文件 + * @access public + * @return string + */ + public function getTemplate() + { + return $this->template; + } +} diff --git a/thinkphp/library/think/exception/ThrowableError.php b/thinkphp/library/think/exception/ThrowableError.php new file mode 100644 index 0000000..87b6b9d --- /dev/null +++ b/thinkphp/library/think/exception/ThrowableError.php @@ -0,0 +1,47 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +class ThrowableError extends \ErrorException +{ + public function __construct(\Throwable $e) + { + + if ($e instanceof \ParseError) { + $message = 'Parse error: ' . $e->getMessage(); + $severity = E_PARSE; + } elseif ($e instanceof \TypeError) { + $message = 'Type error: ' . $e->getMessage(); + $severity = E_RECOVERABLE_ERROR; + } else { + $message = 'Fatal error: ' . $e->getMessage(); + $severity = E_ERROR; + } + + parent::__construct( + $message, + $e->getCode(), + $severity, + $e->getFile(), + $e->getLine() + ); + + $this->setTrace($e->getTrace()); + } + + protected function setTrace($trace) + { + $traceReflector = new \ReflectionProperty('Exception', 'trace'); + $traceReflector->setAccessible(true); + $traceReflector->setValue($this, $trace); + } +} diff --git a/thinkphp/library/think/exception/ValidateException.php b/thinkphp/library/think/exception/ValidateException.php new file mode 100644 index 0000000..b368416 --- /dev/null +++ b/thinkphp/library/think/exception/ValidateException.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +class ValidateException extends \RuntimeException +{ + protected $error; + + public function __construct($error) + { + $this->error = $error; + $this->message = is_array($error) ? implode("\n\r", $error) : $error; + } + + /** + * 获取验证错误信息 + * @access public + * @return array|string + */ + public function getError() + { + return $this->error; + } +} diff --git a/thinkphp/library/think/log/driver/File.php b/thinkphp/library/think/log/driver/File.php new file mode 100644 index 0000000..bace4c2 --- /dev/null +++ b/thinkphp/library/think/log/driver/File.php @@ -0,0 +1,272 @@ + +// +---------------------------------------------------------------------- + +namespace think\log\driver; + +use think\App; +use think\Request; + +/** + * 本地化调试输出到文件 + */ +class File +{ + protected $config = [ + 'time_format' => ' c ', + 'single' => false, + 'file_size' => 2097152, + 'path' => LOG_PATH, + 'apart_level' => [], + 'max_files' => 0, + 'json' => false, + ]; + + // 实例化并传入参数 + public function __construct($config = []) + { + if (is_array($config)) { + $this->config = array_merge($this->config, $config); + } + } + + /** + * 日志写入接口 + * @access public + * @param array $log 日志信息 + * @param bool $append 是否追加请求信息 + * @return bool + */ + public function save(array $log = [], $append = false) + { + $destination = $this->getMasterLogFile(); + + $path = dirname($destination); + !is_dir($path) && mkdir($path, 0755, true); + + $info = []; + foreach ($log as $type => $val) { + + foreach ($val as $msg) { + if (!is_string($msg)) { + $msg = var_export($msg, true); + } + + $info[$type][] = $this->config['json'] ? $msg : '[ ' . $type . ' ] ' . $msg; + } + + if (!$this->config['json'] && (true === $this->config['apart_level'] || in_array($type, $this->config['apart_level']))) { + // 独立记录的日志级别 + $filename = $this->getApartLevelFile($path, $type); + + $this->write($info[$type], $filename, true, $append); + unset($info[$type]); + } + } + + if ($info) { + return $this->write($info, $destination, false, $append); + } + + return true; + } + + /** + * 获取主日志文件名 + * @access public + * @return string + */ + protected function getMasterLogFile() + { + $cli = PHP_SAPI == 'cli' ? '_cli' : ''; + if ($this->config['single']) { + $name = is_string($this->config['single']) ? $this->config['single'] : 'single'; + $destination = $this->config['path'] . $name . $cli . '.log'; + } else { + if ($this->config['max_files']) { + $filename = date('Ymd') . $cli . '.log'; + $files = glob($this->config['path'] . '*.log'); + + try { + if (count($files) > $this->config['max_files']) { + unlink($files[0]); + } + } catch (\Exception $e) { + } + } else { + $filename = date('Ym') . DIRECTORY_SEPARATOR . date('d') . $cli . '.log'; + } + + $destination = $this->config['path'] . $filename; + } + + return $destination; + } + + /** + * 获取独立日志文件名 + * @access public + * @param string $path 日志目录 + * @param string $type 日志类型 + * @return string + */ + protected function getApartLevelFile($path, $type) + { + $cli = PHP_SAPI == 'cli' ? '_cli' : ''; + + if ($this->config['single']) { + $name = is_string($this->config['single']) ? $this->config['single'] : 'single'; + } elseif ($this->config['max_files']) { + $name = date('Ymd'); + } else { + $name = date('d'); + } + + return $path . DIRECTORY_SEPARATOR . $name . '_' . $type . $cli . '.log'; + } + + /** + * 日志写入 + * @access protected + * @param array $message 日志信息 + * @param string $destination 日志文件 + * @param bool $apart 是否独立文件写入 + * @param bool $append 是否追加请求信息 + * @return bool + */ + protected function write($message, $destination, $apart = false, $append = false) + { + // 检测日志文件大小,超过配置大小则备份日志文件重新生成 + $this->checkLogSize($destination); + + // 日志信息封装 + $info['timestamp'] = date($this->config['time_format']); + + foreach ($message as $type => $msg) { + $msg = is_array($msg) ? implode("\r\n", $msg) : $msg; + if (PHP_SAPI == 'cli') { + $info['msg'] = $msg; + $info['type'] = $type; + } else { + $info[$type] = $msg; + } + } + + if (PHP_SAPI == 'cli') { + $message = $this->parseCliLog($info); + } else { + // 添加调试日志 + $this->getDebugLog($info, $append, $apart); + + $message = $this->parseLog($info); + } + + return error_log($message, 3, $destination); + } + + /** + * 检查日志文件大小并自动生成备份文件 + * @access protected + * @param string $destination 日志文件 + * @return void + */ + protected function checkLogSize($destination) + { + if (is_file($destination) && floor($this->config['file_size']) <= filesize($destination)) { + try { + rename($destination, dirname($destination) . DIRECTORY_SEPARATOR . time() . '-' . basename($destination)); + } catch (\Exception $e) { + } + } + } + + /** + * CLI日志解析 + * @access protected + * @param array $info 日志信息 + * @return string + */ + protected function parseCliLog($info) + { + if ($this->config['json']) { + $message = json_encode($info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\r\n"; + } else { + $now = $info['timestamp']; + unset($info['timestamp']); + + $message = implode("\r\n", $info); + + $message = "[{$now}]" . $message . "\r\n"; + } + + return $message; + } + + /** + * 解析日志 + * @access protected + * @param array $info 日志信息 + * @return string + */ + protected function parseLog($info) + { + $request = Request::instance(); + $requestInfo = [ + 'ip' => $request->ip(), + 'method' => $request->method(), + 'host' => $request->host(), + 'uri' => $request->url(), + ]; + + if ($this->config['json']) { + $info = $requestInfo + $info; + return json_encode($info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\r\n"; + } + + array_unshift($info, "---------------------------------------------------------------\r\n[{$info['timestamp']}] {$requestInfo['ip']} {$requestInfo['method']} {$requestInfo['host']}{$requestInfo['uri']}"); + unset($info['timestamp']); + + return implode("\r\n", $info) . "\r\n"; + } + + protected function getDebugLog(&$info, $append, $apart) + { + if (App::$debug && $append) { + + if ($this->config['json']) { + // 获取基本信息 + $runtime = round(microtime(true) - THINK_START_TIME, 10); + $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; + + $memory_use = number_format((memory_get_usage() - THINK_START_MEM) / 1024, 2); + + $info = [ + 'runtime' => number_format($runtime, 6) . 's', + 'reqs' => $reqs . 'req/s', + 'memory' => $memory_use . 'kb', + 'file' => count(get_included_files()), + ] + $info; + + } elseif (!$apart) { + // 增加额外的调试信息 + $runtime = round(microtime(true) - THINK_START_TIME, 10); + $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; + + $memory_use = number_format((memory_get_usage() - THINK_START_MEM) / 1024, 2); + + $time_str = '[运行时间:' . number_format($runtime, 6) . 's] [吞吐率:' . $reqs . 'req/s]'; + $memory_str = ' [内存消耗:' . $memory_use . 'kb]'; + $file_load = ' [文件加载:' . count(get_included_files()) . ']'; + + array_unshift($info, $time_str . $memory_str . $file_load); + } + } + } +} diff --git a/thinkphp/library/think/log/driver/Socket.php b/thinkphp/library/think/log/driver/Socket.php new file mode 100644 index 0000000..4f62915 --- /dev/null +++ b/thinkphp/library/think/log/driver/Socket.php @@ -0,0 +1,250 @@ + +// +---------------------------------------------------------------------- + +namespace think\log\driver; + +use think\App; + +/** + * github: https://github.com/luofei614/SocketLog + * @author luofei614 + */ +class Socket +{ + public $port = 1116; //SocketLog 服务的http的端口号 + + protected $config = [ + // socket服务器地址 + 'host' => 'localhost', + // 是否显示加载的文件列表 + 'show_included_files' => false, + // 日志强制记录到配置的client_id + 'force_client_ids' => [], + // 限制允许读取日志的client_id + 'allow_client_ids' => [], + ]; + + 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 + + /** + * 构造函数 + * @param array $config 缓存参数 + * @access public + */ + public function __construct(array $config = []) + { + if (!empty($config)) { + $this->config = array_merge($this->config, $config); + } + } + + /** + * 调试输出接口 + * @access public + * @param array $log 日志信息 + * @return bool + */ + public function save(array $log = [], $append = false) + { + if (!$this->check()) { + return false; + } + $trace = []; + if (App::$debug) { + $runtime = round(microtime(true) - THINK_START_TIME, 10); + $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; + $time_str = ' [运行时间:' . number_format($runtime, 6) . 's][吞吐率:' . $reqs . 'req/s]'; + $memory_use = number_format((memory_get_usage() - THINK_START_MEM) / 1024, 2); + $memory_str = ' [内存消耗:' . $memory_use . 'kb]'; + $file_load = ' [文件加载:' . count(get_included_files()) . ']'; + + if (isset($_SERVER['HTTP_HOST'])) { + $current_uri = $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; + } else { + $current_uri = 'cmd:' . implode(' ', $_SERVER['argv']); + } + // 基本信息 + $trace[] = [ + 'type' => 'group', + 'msg' => $current_uri . $time_str . $memory_str . $file_load, + 'css' => $this->css['page'], + ]; + } + + foreach ($log as $type => $val) { + $trace[] = [ + 'type' => 'groupCollapsed', + 'msg' => '[ ' . $type . ' ]', + 'css' => isset($this->css[$type]) ? $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 (!$client_id = $this->getClientArg('client_id')) { + $client_id = ''; + } + + if (!empty($this->allowForceClientIds)) { + //强制推送到多个client_id + foreach ($this->allowForceClientIds as $force_client_id) { + $client_id = $force_client_id; + $this->sendToClient($tabid, $client_id, $trace, $force_client_id); + } + } else { + $this->sendToClient($tabid, $client_id, $trace, ''); + } + return true; + } + + /** + * 发送给指定客户端 + * @author Zjmainstay + * @param $tabid + * @param $client_id + * @param $logs + * @param $force_client_id + */ + protected function sendToClient($tabid, $client_id, $logs, $force_client_id) + { + $logs = [ + 'tabid' => $tabid, + 'client_id' => $client_id, + 'logs' => $logs, + 'force_client_id' => $force_client_id, + ]; + $msg = @json_encode($logs); + $address = '/' . $client_id; //将client_id作为地址, server端通过地址判断将日志发布给谁 + $this->send($this->config['host'], $msg, $address); + } + + protected function check() + { + $tabid = $this->getClientArg('tabid'); + //是否记录日志的检查 + if (!$tabid && !$this->config['force_client_ids']) { + return false; + } + //用户认证 + $allow_client_ids = $this->config['allow_client_ids']; + if (!empty($allow_client_ids)) { + //通过数组交集得出授权强制推送的client_id + $this->allowForceClientIds = array_intersect($allow_client_ids, $this->config['force_client_ids']); + if (!$tabid && count($this->allowForceClientIds)) { + return true; + } + + $client_id = $this->getClientArg('client_id'); + if (!in_array($client_id, $allow_client_ids)) { + return false; + } + } else { + $this->allowForceClientIds = $this->config['force_client_ids']; + } + return true; + } + + protected function getClientArg($name) + { + static $args = []; + + $key = 'HTTP_USER_AGENT'; + + if (isset($_SERVER['HTTP_SOCKETLOG'])) { + $key = 'HTTP_SOCKETLOG'; + } + + if (!isset($_SERVER[$key])) { + return; + } + if (empty($args)) { + if (!preg_match('/SocketLog\((.*?)\)/', $_SERVER[$key], $match)) { + $args = ['tabid' => null]; + return; + } + parse_str($match[1], $args); + } + if (isset($args[$name])) { + return $args[$name]; + } + return; + } + + /** + * @param string $host - $host of socket server + * @param string $message - 发送的消息 + * @param string $address - 地址 + * @return bool + */ + protected function send($host, $message = '', $address = '/') + { + $url = 'http://' . $host . ':' . $this->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, 1); + curl_setopt($ch, 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/thinkphp/library/think/log/driver/Test.php b/thinkphp/library/think/log/driver/Test.php new file mode 100644 index 0000000..7f66338 --- /dev/null +++ b/thinkphp/library/think/log/driver/Test.php @@ -0,0 +1,30 @@ + +// +---------------------------------------------------------------------- + +namespace think\log\driver; + +/** + * 模拟测试输出 + */ +class Test +{ + /** + * 日志写入接口 + * @access public + * @param array $log 日志信息 + * @return bool + */ + public function save(array $log = []) + { + return true; + } + +} diff --git a/thinkphp/library/think/model/Collection.php b/thinkphp/library/think/model/Collection.php new file mode 100644 index 0000000..0406533 --- /dev/null +++ b/thinkphp/library/think/model/Collection.php @@ -0,0 +1,79 @@ + +// +---------------------------------------------------------------------- + +namespace think\model; + +use think\Collection as BaseCollection; +use think\Model; + +class Collection extends BaseCollection +{ + /** + * 延迟预载入关联查询 + * @access public + * @param mixed $relation 关联 + * @return $this + */ + public function load($relation) + { + $item = current($this->items); + $item->eagerlyResultSet($this->items, $relation); + return $this; + } + + /** + * 设置需要隐藏的输出属性 + * @access public + * @param array $hidden 属性列表 + * @param bool $override 是否覆盖 + * @return $this + */ + public function hidden($hidden = [], $override = false) + { + $this->each(function ($model) use ($hidden, $override) { + /** @var Model $model */ + $model->hidden($hidden, $override); + }); + return $this; + } + + /** + * 设置需要输出的属性 + * @param array $visible + * @param bool $override 是否覆盖 + * @return $this + */ + public function visible($visible = [], $override = false) + { + $this->each(function ($model) use ($visible, $override) { + /** @var Model $model */ + $model->visible($visible, $override); + }); + return $this; + } + + /** + * 设置需要追加的输出属性 + * @access public + * @param array $append 属性列表 + * @param bool $override 是否覆盖 + * @return $this + */ + public function append($append = [], $override = false) + { + $this->each(function ($model) use ($append, $override) { + /** @var Model $model */ + $model && $model->append($append, $override); + }); + return $this; + } + +} diff --git a/thinkphp/library/think/model/Merge.php b/thinkphp/library/think/model/Merge.php new file mode 100644 index 0000000..4a9da81 --- /dev/null +++ b/thinkphp/library/think/model/Merge.php @@ -0,0 +1,322 @@ + +// +---------------------------------------------------------------------- + +namespace think\model; + +use think\Db; +use think\db\Query; +use think\Model; + +class Merge extends Model +{ + + protected $relationModel = []; // HAS ONE 关联的模型列表 + protected $fk = ''; // 外键名 默认为主表名_id + protected $mapFields = []; // 需要处理的模型映射字段,避免混淆 array( id => 'user.id' ) + + /** + * 构造函数 + * @access public + * @param array|object $data 数据 + */ + public function __construct($data = []) + { + parent::__construct($data); + + // 设置默认外键名 仅支持单一外键 + if (empty($this->fk)) { + $this->fk = strtolower($this->name) . '_id'; + } + } + + /** + * 查找单条记录 + * @access public + * @param mixed $data 主键值或者查询条件(闭包) + * @param string|array $with 关联预查询 + * @param bool $cache 是否缓存 + * @return \think\Model + */ + public static function get($data = null, $with = [], $cache = false) + { + $query = self::parseQuery($data, $with, $cache); + $query = self::attachQuery($query); + return $query->find($data); + } + + /** + * 附加查询表达式 + * @access protected + * @param \think\db\Query $query 查询对象 + * @return \think\db\Query + */ + protected static function attachQuery($query) + { + $class = new static(); + $master = $class->name; + $fields = self::getModelField($query, $master, '', $class->mapFields, $class->field); + $query->alias($master)->field($fields); + + foreach ($class->relationModel as $key => $model) { + $name = is_int($key) ? $model : $key; + $table = is_int($key) ? $query->getTable($name) : $model; + $query->join($table . ' ' . $name, $name . '.' . $class->fk . '=' . $master . '.' . $class->getPk()); + $fields = self::getModelField($query, $name, $table, $class->mapFields, $class->field); + $query->field($fields); + } + return $query; + } + + /** + * 获取关联模型的字段 并解决混淆 + * @access protected + * @param \think\db\Query $query 查询对象 + * @param string $name 模型名称 + * @param string $table 关联表名称 + * @param array $map 字段映射 + * @param array $fields 查询字段 + * @return array + */ + protected static function getModelField($query, $name, $table = '', $map = [], $fields = []) + { + // 获取模型的字段信息 + $fields = $fields ?: $query->getTableInfo($table, 'fields'); + $array = []; + foreach ($fields as $field) { + if ($key = array_search($name . '.' . $field, $map)) { + // 需要处理映射字段 + $array[] = $name . '.' . $field . ' AS ' . $key; + } else { + $array[] = $field; + } + } + return $array; + } + + /** + * 查找所有记录 + * @access public + * @param mixed $data 主键列表或者查询条件(闭包) + * @param array|string $with 关联预查询 + * @param bool $cache + * @return array|false|string + */ + public static function all($data = null, $with = [], $cache = false) + { + $query = self::parseQuery($data, $with, $cache); + $query = self::attachQuery($query); + return $query->select($data); + } + + /** + * 处理写入的模型数据 + * @access public + * @param string $model 模型名称 + * @param array $data 数据 + * @return array + */ + protected function parseData($model, $data) + { + $item = []; + foreach ($data as $key => $val) { + if ($this->fk != $key && array_key_exists($key, $this->mapFields)) { + list($name, $key) = explode('.', $this->mapFields[$key]); + if ($model == $name) { + $item[$key] = $val; + } + } else { + $item[$key] = $val; + } + } + return $item; + } + + /** + * 保存模型数据 以及关联数据 + * @access public + * @param mixed $data 数据 + * @param array $where 更新条件 + * @param string $sequence 自增序列名 + * @return false|int + * @throws \Exception + */ + public function save($data = [], $where = [], $sequence = null) + { + if (!empty($data)) { + // 数据自动验证 + if (!$this->validateData($data)) { + return false; + } + // 数据对象赋值 + foreach ($data as $key => $value) { + $this->setAttr($key, $value, $data); + } + if (!empty($where)) { + $this->isUpdate = true; + } + } + + // 数据自动完成 + $this->autoCompleteData($this->auto); + + // 自动写入更新时间 + if ($this->autoWriteTimestamp && $this->updateTime && !isset($this->data[$this->updateTime])) { + $this->setAttr($this->updateTime, null); + } + + // 事件回调 + if (false === $this->trigger('before_write', $this)) { + return false; + } + + $db = $this->db(); + $db->startTrans(); + $pk = $this->getPk(); + try { + if ($this->isUpdate) { + // 自动写入 + $this->autoCompleteData($this->update); + + if (false === $this->trigger('before_update', $this)) { + return false; + } + + if (empty($where) && !empty($this->updateWhere)) { + $where = $this->updateWhere; + } + + // 获取有更新的数据 + $data = $this->getChangedData(); + // 保留主键数据 + foreach ($this->data as $key => $val) { + if ($this->isPk($key)) { + $data[$key] = $val; + } + } + // 处理模型数据 + $data = $this->parseData($this->name, $data); + if (is_string($pk) && isset($data[$pk])) { + if (!isset($where[$pk])) { + unset($where); + $where[$pk] = $data[$pk]; + } + unset($data[$pk]); + } + // 写入主表数据 + $result = $db->strict(false)->where($where)->update($data); + + // 写入附表数据 + foreach ($this->relationModel as $key => $model) { + $name = is_int($key) ? $model : $key; + $table = is_int($key) ? $db->getTable($model) : $model; + // 处理关联模型数据 + $data = $this->parseData($name, $data); + if (Db::table($table)->strict(false)->where($this->fk, $this->data[$this->getPk()])->update($data)) { + $result = 1; + } + } + + // 新增回调 + $this->trigger('after_update', $this); + } else { + // 自动写入 + $this->autoCompleteData($this->insert); + + // 自动写入创建时间 + if ($this->autoWriteTimestamp && $this->createTime && !isset($this->data[$this->createTime])) { + $this->setAttr($this->createTime, null); + } + + if (false === $this->trigger('before_insert', $this)) { + return false; + } + + // 处理模型数据 + $data = $this->parseData($this->name, $this->data); + // 写入主表数据 + $result = $db->name($this->name)->strict(false)->insert($data); + if ($result) { + $insertId = $db->getLastInsID($sequence); + // 写入外键数据 + if ($insertId) { + if (is_string($pk)) { + $this->data[$pk] = $insertId; + } + $this->data[$this->fk] = $insertId; + } + + // 写入附表数据 + $source = $this->data; + if ($insertId && is_string($pk) && isset($source[$pk]) && $this->fk != $pk) { + unset($source[$pk]); + } + foreach ($this->relationModel as $key => $model) { + $name = is_int($key) ? $model : $key; + $table = is_int($key) ? $db->getTable($model) : $model; + // 处理关联模型数据 + $data = $this->parseData($name, $source); + Db::table($table)->strict(false)->insert($data); + } + } + // 标记为更新 + $this->isUpdate = true; + // 新增回调 + $this->trigger('after_insert', $this); + } + $db->commit(); + // 写入回调 + $this->trigger('after_write', $this); + + $this->origin = $this->data; + return $result; + } catch (\Exception $e) { + $db->rollback(); + throw $e; + } + } + + /** + * 删除当前的记录 并删除关联数据 + * @access public + * @return int + * @throws \Exception + */ + public function delete() + { + if (false === $this->trigger('before_delete', $this)) { + return false; + } + + $db = $this->db(); + $db->startTrans(); + try { + $result = $db->delete($this->data); + if ($result) { + // 获取主键数据 + $pk = $this->data[$this->getPk()]; + + // 删除关联数据 + foreach ($this->relationModel as $key => $model) { + $table = is_int($key) ? $db->getTable($model) : $model; + $query = new Query; + $query->table($table)->where($this->fk, $pk)->delete(); + } + } + $this->trigger('after_delete', $this); + $db->commit(); + return $result; + } catch (\Exception $e) { + $db->rollback(); + throw $e; + } + } + +} diff --git a/thinkphp/library/think/model/Pivot.php b/thinkphp/library/think/model/Pivot.php new file mode 100644 index 0000000..13525cd --- /dev/null +++ b/thinkphp/library/think/model/Pivot.php @@ -0,0 +1,42 @@ + +// +---------------------------------------------------------------------- + +namespace think\model; + +use think\Model; + +class Pivot extends Model +{ + + /** @var Model */ + public $parent; + + protected $autoWriteTimestamp = false; + + /** + * 架构函数 + * @access public + * @param array|object $data 数据 + * @param Model $parent 上级模型 + * @param string $table 中间数据表名 + */ + public function __construct($data = [], Model $parent = null, $table = '') + { + $this->parent = $parent; + + if (is_null($this->name)) { + $this->name = $table; + } + + parent::__construct($data); + } + +} diff --git a/thinkphp/library/think/model/Relation.php b/thinkphp/library/think/model/Relation.php new file mode 100644 index 0000000..25fe88d --- /dev/null +++ b/thinkphp/library/think/model/Relation.php @@ -0,0 +1,155 @@ + +// +---------------------------------------------------------------------- + +namespace think\model; + +use think\db\Query; +use think\Exception; +use think\Model; + +/** + * Class Relation + * @package think\model + * + * @mixin Query + */ +abstract class Relation +{ + // 父模型对象 + protected $parent; + /** @var Model 当前关联的模型类 */ + protected $model; + /** @var Query 关联模型查询对象 */ + protected $query; + // 关联表外键 + protected $foreignKey; + // 关联表主键 + protected $localKey; + // 基础查询 + protected $baseQuery; + // 是否为自关联 + protected $selfRelation; + + /** + * 获取关联的所属模型 + * @access public + * @return Model + */ + public function getParent() + { + return $this->parent; + } + + /** + * 获取当前的关联模型对象实例 + * @access public + * @return Model + */ + public function getModel() + { + return $this->query->getModel(); + } + + /** + * 获取关联的查询对象 + * @access public + * @return Query + */ + public function getQuery() + { + return $this->query; + } + + /** + * 设置当前关联为自关联 + * @access public + * @param bool $self 是否自关联 + * @return $this + */ + public function selfRelation($self = true) + { + $this->selfRelation = $self; + return $this; + } + + /** + * 当前关联是否为自关联 + * @access public + * @return bool + */ + public function isSelfRelation() + { + return $this->selfRelation; + } + + /** + * 封装关联数据集 + * @access public + * @param array $resultSet 数据集 + * @return mixed + */ + protected function resultSetBuild($resultSet) + { + return (new $this->model)->toCollection($resultSet); + } + + protected function getQueryFields($model) + { + $fields = $this->query->getOptions('field'); + return $this->getRelationQueryFields($fields, $model); + } + + protected function getRelationQueryFields($fields, $model) + { + if ($fields) { + + if (is_string($fields)) { + $fields = explode(',', $fields); + } + + foreach ($fields as &$field) { + if (false === strpos($field, '.')) { + $field = $model . '.' . $field; + } + } + } else { + $fields = $model . '.*'; + } + + return $fields; + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + {} + + public function __call($method, $args) + { + if ($this->query) { + // 执行基础查询 + $this->baseQuery(); + + $result = call_user_func_array([$this->query, $method], $args); + if ($result instanceof Query) { + return $this; + } else { + $this->baseQuery = false; + return $result; + } + } else { + throw new Exception('method not exists:' . __CLASS__ . '->' . $method); + } + } +} diff --git a/thinkphp/library/think/model/relation/BelongsTo.php b/thinkphp/library/think/model/relation/BelongsTo.php new file mode 100644 index 0000000..c1cbab9 --- /dev/null +++ b/thinkphp/library/think/model/relation/BelongsTo.php @@ -0,0 +1,243 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\db\Query; +use think\Loader; +use think\Model; + +class BelongsTo extends OneToOne +{ + /** + * 构造函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 关联主键 + * @param string $joinType JOIN类型 + * @param string $relation 关联名 + */ + public function __construct(Model $parent, $model, $foreignKey, $localKey, $joinType = 'INNER', $relation = null) + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + $this->joinType = $joinType; + $this->query = (new $model)->db(); + $this->relation = $relation; + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @access public + * @return array|false|\PDOStatement|string|Model + */ + public function getRelation($subRelation = '', $closure = null) + { + $foreignKey = $this->foreignKey; + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + $relationModel = $this->query + ->removeWhereField($this->localKey) + ->where($this->localKey, $this->parent->$foreignKey) + ->relation($subRelation) + ->find(); + + if ($relationModel) { + $relationModel->setParent(clone $this->parent); + } + + return $relationModel; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*') + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $relation = basename(str_replace('\\', '/', $this->model)); + + if (is_array($where)) { + foreach ($where as $key => $val) { + if (false === strpos($key, '.')) { + $where[$relation . '.' . $key] = $val; + unset($where[$key]); + } + } + } + $fields = $this->getRelationQueryFields($fields, $model); + + return $this->parent->db()->alias($model) + ->field($fields) + ->join([$table => $relation], $model . '.' . $this->foreignKey . '=' . $relation . '.' . $this->localKey, $this->joinType) + ->where($where); + } + + /** + * 预载入关联查询(数据集) + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure) + { + $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($this->query, [ + $localKey => [ + 'in', + $range, + ], + ], $localKey, $relation, $subRelation, $closure); + // 关联属性名 + $attr = Loader::parseName($relation); + // 关联数据封装 + foreach ($resultSet as $result) { + // 关联模型 + if (!isset($data[$result->$foreignKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$foreignKey]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($relationModel, $result, $this->bindAttr); + } else { + // 设置关联属性 + $result->setRelation($attr, $relationModel); + } + } + } + } + + /** + * 预载入关联查询(数据) + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + protected function eagerlyOne(&$result, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + $this->query->removeWhereField($localKey); + $data = $this->eagerlyWhere($this->query, [$localKey => $result->$foreignKey], $localKey, $relation, $subRelation, $closure); + // 关联模型 + if (!isset($data[$result->$foreignKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$foreignKey]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($relationModel, $result, $this->bindAttr); + } else { + // 设置关联属性 + $result->setRelation(Loader::parseName($relation), $relationModel); + } + } + + /** + * 添加关联数据 + * @access public + * @param Model $model 关联模型对象 + * @return Model + */ + public function associate($model) + { + $foreignKey = $this->foreignKey; + $pk = $model->getPk(); + + $this->parent->setAttr($foreignKey, $model->$pk); + $this->parent->save(); + + return $this->parent->setRelation($this->relation, $model); + } + + /** + * 注销关联数据 + * @access public + * @return Model + */ + public function dissociate() + { + $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() + { + 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/thinkphp/library/think/model/relation/BelongsToMany.php b/thinkphp/library/think/model/relation/BelongsToMany.php new file mode 100644 index 0000000..a41c45c --- /dev/null +++ b/thinkphp/library/think/model/relation/BelongsToMany.php @@ -0,0 +1,644 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\Collection; +use think\Db; +use think\db\Query; +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Pivot; +use think\model\Relation; +use think\Paginator; + +class BelongsToMany extends Relation +{ + // 中间表表名 + protected $middle; + // 中间表模型名称 + protected $pivotName; + // 中间表模型对象 + protected $pivot; + // 中间表数据名称 + protected $pivotDataName = 'pivot'; + + /** + * 构造函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $table 中间表名 + * @param string $foreignKey 关联模型外键 + * @param string $localKey 当前模型关联键 + */ + public function __construct(Model $parent, $model, $table, $foreignKey, $localKey) + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + if (false !== strpos($table, '\\')) { + $this->pivotName = $table; + $this->middle = basename(str_replace('\\', '/', $table)); + } else { + $this->middle = $table; + } + $this->query = (new $model)->db(); + $this->pivot = $this->newPivot(); + + if ('think\model\Pivot' == get_class($this->pivot)) { + $this->pivot->name($this->middle); + } + } + + /** + * 设置中间表模型 + * @param $pivot + * @return $this + */ + public function pivot($pivot) + { + $this->pivotName = $pivot; + return $this; + } + + /** + * 设置中间表数据名称 + * @access public + * @param string $name + * @return $this + */ + public function pivotDataName($name) + { + $this->pivotDataName = $name; + return $this; + } + + /** + * 获取中间表更新条件 + * @param $data + * @return array + */ + protected function getUpdateWhere($data) + { + return [ + $this->localKey => $data[$this->localKey], + $this->foreignKey => $data[$this->foreignKey], + ]; + } + + /** + * 实例化中间表模型 + * @param array $data + * @param bool $isUpdate + * @return Pivot + * @throws Exception + */ + protected function newPivot($data = [], $isUpdate = false) + { + $class = $this->pivotName ?: '\\think\\model\\Pivot'; + $pivot = new $class($data, $this->parent, $this->middle); + if ($pivot instanceof Pivot) { + return $isUpdate ? $pivot->isUpdate(true, $this->getUpdateWhere($data)) : $pivot; + } else { + throw new Exception('pivot model must extends: \think\model\Pivot'); + } + } + + /** + * 合成中间表模型 + * @param array|Collection|Paginator $models + */ + protected function hydratePivot($models) + { + foreach ($models as $model) { + $pivot = []; + foreach ($model->getData() as $key => $val) { + if (strpos($key, '__')) { + list($name, $attr) = explode('__', $key, 2); + if ('pivot' == $name) { + $pivot[$attr] = $val; + unset($model->$key); + } + } + } + $model->setRelation($this->pivotDataName, $this->newPivot($pivot, true)); + } + } + + /** + * 创建关联查询Query对象 + * @return Query + */ + protected function buildQuery() + { + $foreignKey = $this->foreignKey; + $localKey = $this->localKey; + $pk = $this->parent->getPk(); + // 关联查询 + $condition['pivot.' . $localKey] = $this->parent->$pk; + return $this->belongsToManyQuery($foreignKey, $localKey, $condition); + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return false|\PDOStatement|string|\think\Collection + */ + public function getRelation($subRelation = '', $closure = null) + { + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + $result = $this->buildQuery()->relation($subRelation)->select(); + $this->hydratePivot($result); + return $result; + } + + /** + * 重载select方法 + * @param null $data + * @return false|\PDOStatement|string|Collection + */ + public function select($data = null) + { + $result = $this->buildQuery()->select($data); + $this->hydratePivot($result); + return $result; + } + + /** + * 重载paginate方法 + * @param null $listRows + * @param bool $simple + * @param array $config + * @return Paginator + */ + public function paginate($listRows = null, $simple = false, $config = []) + { + $result = $this->buildQuery()->paginate($listRows, $simple, $config); + $this->hydratePivot($result); + return $result; + } + + /** + * 重载find方法 + * @param null $data + * @return array|false|\PDOStatement|string|Model + */ + public function find($data = null) + { + $result = $this->buildQuery()->find($data); + if ($result) { + $this->hydratePivot([$result]); + } + return $result; + } + + /** + * 查找多条记录 如果不存在则抛出异常 + * @access public + * @param array|string|Query|\Closure $data + * @return array|\PDOStatement|string|Model + */ + public function selectOrFail($data = null) + { + return $this->failException(true)->select($data); + } + + /** + * 查找单条记录 如果不存在则抛出异常 + * @access public + * @param array|string|Query|\Closure $data + * @return array|\PDOStatement|string|Model + */ + public function findOrFail($data = null) + { + return $this->failException(true)->find($data); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + * @throws Exception + */ + public function hasWhere($where = [], $fields = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 设置中间表的查询条件 + * @param $field + * @param null $op + * @param null $condition + * @return $this + */ + public function wherePivot($field, $op = null, $condition = null) + { + $field = 'pivot.' . $field; + $this->query->where($field, $op, $condition); + return $this; + } + + /** + * 预载入关联查询(数据集) + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $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, + ], + ], $relation, $subRelation); + // 关联属性名 + $attr = Loader::parseName($relation); + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$pk])) { + $data[$result->$pk] = []; + } + + $result->setRelation($attr, $this->resultSetBuild($data[$result->$pk])); + } + } + } + + /** + * 预载入关联查询(单个数据) + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + $pk = $result->getPk(); + if (isset($result->$pk)) { + $pk = $result->$pk; + // 查询管理数据 + $data = $this->eagerlyManyToMany(['pivot.' . $this->localKey => $pk], $relation, $subRelation); + + // 关联数据封装 + if (!isset($data[$pk])) { + $data[$pk] = []; + } + $result->setRelation(Loader::parseName($relation), $this->resultSetBuild($data[$pk])); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @return integer + */ + public function relationCount($result, $closure) + { + $pk = $result->getPk(); + $count = 0; + if (isset($result->$pk)) { + $pk = $result->$pk; + $count = $this->belongsToManyQuery($this->foreignKey, $this->localKey, ['pivot.' . $this->localKey => $pk])->count(); + } + return $count; + } + + /** + * 获取关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @param string $name 统计数据别名 + * @return string + */ + public function getRelationCountQuery($closure, &$name = null) + { + if ($closure) { + $return = call_user_func_array($closure, [ & $this->query]); + if ($return && is_string($return)) { + $name = $return; + } + } + + return $this->belongsToManyQuery($this->foreignKey, $this->localKey, [ + 'pivot.' . $this->localKey => [ + 'exp', + Db::raw('=' . $this->parent->getTable() . '.' . $this->parent->getPk()), + ], + ])->fetchSql()->count(); + } + + /** + * 多对多 关联模型预查询 + * @access public + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @return array + */ + protected function eagerlyManyToMany($where, $relation, $subRelation = '') + { + // 预载入关联查询 支持嵌套预载入 + $list = $this->belongsToManyQuery($this->foreignKey, $this->localKey, $where)->with($subRelation)->select(); + + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $pivot = []; + foreach ($set->getData() as $key => $val) { + if (strpos($key, '__')) { + list($name, $attr) = explode('__', $key, 2); + if ('pivot' == $name) { + $pivot[$attr] = $val; + unset($set->$key); + } + } + } + $set->setRelation($this->pivotDataName, $this->newPivot($pivot, true)); + $data[$pivot[$this->localKey]][] = $set; + } + return $data; + } + + /** + * BELONGS TO MANY 关联查询 + * @access public + * @param string $foreignKey 关联模型关联键 + * @param string $localKey 当前模型关联键 + * @param array $condition 关联查询条件 + * @return Query + */ + protected function belongsToManyQuery($foreignKey, $localKey, $condition = []) + { + // 关联查询封装 + $tableName = $this->query->getTable(); + $table = $this->pivot->getTable(); + $fields = $this->getQueryFields($tableName); + + $query = $this->query->field($fields) + ->field(true, false, $table, 'pivot', 'pivot__'); + + if (empty($this->baseQuery)) { + $relationFk = $this->query->getPk(); + $query->join([$table => 'pivot'], 'pivot.' . $foreignKey . '=' . $tableName . '.' . $relationFk) + ->where($condition); + } + return $query; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @param array $pivot 中间表额外数据 + * @return integer + */ + public function save($data, array $pivot = []) + { + // 保存关联表/中间表数据 + return $this->attach($data, $pivot); + } + + /** + * 批量保存当前关联数据对象 + * @access public + * @param array $dataSet 数据集 + * @param array $pivot 中间表额外数据 + * @param bool $samePivot 额外数据是否相同 + * @return integer + */ + public function saveAll(array $dataSet, array $pivot = [], $samePivot = false) + { + $result = false; + foreach ($dataSet as $key => $data) { + if (!$samePivot) { + $pivotData = isset($pivot[$key]) ? $pivot[$key] : []; + } else { + $pivotData = $pivot; + } + $result = $this->attach($data, $pivotData); + } + return $result; + } + + /** + * 附加关联的一个中间表数据 + * @access public + * @param mixed $data 数据 可以使用数组、关联模型对象 或者 关联对象的主键 + * @param array $pivot 中间表额外数据 + * @return array|Pivot + * @throws Exception + */ + public function attach($data, $pivot = []) + { + if (is_array($data)) { + if (key($data) === 0) { + $id = $data; + } else { + // 保存关联表数据 + $model = new $this->model; + $model->save($data); + $id = $model->getLastInsID(); + } + } elseif (is_numeric($data) || is_string($data)) { + // 根据关联表主键直接写入中间表 + $id = $data; + } elseif ($data instanceof Model) { + // 根据关联表主键直接写入中间表 + $relationFk = $data->getPk(); + $id = $data->$relationFk; + } + + if ($id) { + // 保存中间表数据 + $pk = $this->parent->getPk(); + $pivot[$this->localKey] = $this->parent->$pk; + $ids = (array) $id; + foreach ($ids as $id) { + $pivot[$this->foreignKey] = $id; + $this->pivot->insert($pivot, true); + $result[] = $this->newPivot($pivot, true); + } + if (count($result) == 1) { + // 返回中间表模型对象 + $result = $result[0]; + } + return $result; + } else { + throw new Exception('miss relation data'); + } + } + + /** + * 判断是否存在关联数据 + * @access public + * @param mixed $data 数据 可以使用关联模型对象 或者 关联对象的主键 + * @return Pivot + * @throws Exception + */ + public function attached($data) + { + if ($data instanceof Model) { + $relationFk = $data->getPk(); + $id = $data->$relationFk; + } else { + $id = $data; + } + + $pk = $this->parent->getPk(); + + $pivot = $this->pivot->where($this->localKey, $this->parent->$pk)->where($this->foreignKey, $id)->find(); + + return $pivot ?: false; + } + + /** + * 解除关联的一个中间表数据 + * @access public + * @param integer|array $data 数据 可以使用关联对象的主键 + * @param bool $relationDel 是否同时删除关联表数据 + * @return integer + */ + public function detach($data = null, $relationDel = false) + { + if (is_array($data)) { + $id = $data; + } elseif (is_numeric($data) || is_string($data)) { + // 根据关联表主键直接写入中间表 + $id = $data; + } elseif ($data instanceof Model) { + // 根据关联表主键直接写入中间表 + $relationFk = $data->getPk(); + $id = $data->$relationFk; + } + // 删除中间表数据 + $pk = $this->parent->getPk(); + $pivot[$this->localKey] = $this->parent->$pk; + if (isset($id)) { + $pivot[$this->foreignKey] = is_array($id) ? ['in', $id] : $id; + } + $this->pivot->where($pivot)->delete(); + // 删除关联表数据 + if (isset($id) && $relationDel) { + $model = $this->model; + $model::destroy($id); + } + } + + /** + * 数据同步 + * @param array $ids + * @param bool $detaching + * @return array + */ + public function sync($ids, $detaching = true) + { + $changes = [ + 'attached' => [], + 'detached' => [], + 'updated' => [], + ]; + $pk = $this->parent->getPk(); + $current = $this->pivot->where($this->localKey, $this->parent->$pk) + ->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() + { + if (empty($this->baseQuery) && $this->parent->getData()) { + $pk = $this->parent->getPk(); + $table = $this->pivot->getTable(); + $this->query->join([$table => 'pivot'], 'pivot.' . $this->foreignKey . '=' . $this->query->getTable() . '.' . $this->query->getPk())->where('pivot.' . $this->localKey, $this->parent->$pk); + $this->baseQuery = true; + } + } + +} diff --git a/thinkphp/library/think/model/relation/HasMany.php b/thinkphp/library/think/model/relation/HasMany.php new file mode 100644 index 0000000..ebab051 --- /dev/null +++ b/thinkphp/library/think/model/relation/HasMany.php @@ -0,0 +1,318 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\db\Query; +use think\Loader; +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, $model, $foreignKey, $localKey) + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return false|\PDOStatement|string|\think\Collection + */ + public function getRelation($subRelation = '', $closure = null) + { + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + $list = $this->relation($subRelation)->select(); + $parent = clone $this->parent; + + foreach ($list as &$model) { + $model->setParent($parent); + } + + return $list; + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$localKey)) { + $range[] = $result->$localKey; + } + } + + if (!empty($range)) { + $data = $this->eagerlyOneToMany($this->query, [ + $this->foreignKey => [ + 'in', + $range, + ], + ], $relation, $subRelation, $closure); + // 关联属性名 + $attr = Loader::parseName($relation); + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$localKey])) { + $data[$result->$localKey] = []; + } + + foreach ($data[$result->$localKey] as &$relationModel) { + $relationModel->setParent(clone $result); + } + + $result->setRelation($attr, $this->resultSetBuild($data[$result->$localKey])); + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + + if (isset($result->$localKey)) { + $data = $this->eagerlyOneToMany($this->query, [$this->foreignKey => $result->$localKey], $relation, $subRelation, $closure); + // 关联数据封装 + if (!isset($data[$result->$localKey])) { + $data[$result->$localKey] = []; + } + + foreach ($data[$result->$localKey] as &$relationModel) { + $relationModel->setParent(clone $result); + } + + $result->setRelation(Loader::parseName($relation), $this->resultSetBuild($data[$result->$localKey])); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @return integer + */ + public function relationCount($result, $closure) + { + $localKey = $this->localKey; + $count = 0; + if (isset($result->$localKey)) { + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + $count = $this->query->where($this->foreignKey, $result->$localKey)->count(); + } + return $count; + } + + /** + * 创建关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @param string $name 统计数据别名 + * @return string + */ + public function getRelationCountQuery($closure, &$name = null) + { + if ($closure) { + $return = call_user_func_array($closure, [ & $this->query]); + if ($return && is_string($return)) { + $name = $return; + } + } + $localKey = $this->localKey ?: $this->parent->getPk(); + return $this->query->whereExp($this->foreignKey, '=' . $this->parent->getTable() . '.' . $localKey)->fetchSql()->count(); + } + + /** + * 一对多 关联模型预查询 + * @access public + * @param object $model 关联模型对象 + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param bool $closure + * @return array + */ + protected function eagerlyOneToMany($model, $where, $relation, $subRelation = '', $closure = false) + { + $foreignKey = $this->foreignKey; + // 预载入关联查询 支持嵌套预载入 + if ($closure) { + call_user_func_array($closure, [ & $model]); + } + $list = $model->removeWhereField($foreignKey)->where($where)->with($subRelation)->select(); + + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $data[$set->$foreignKey][] = $set; + } + return $data; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @return Model|false + */ + public function save($data) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + + // 保存关联表数据 + $data[$this->foreignKey] = $this->parent->{$this->localKey}; + + $model = new $this->model(); + return $model->save($data) ? $model : false; + } + + /** + * 创建关联对象实例 + * @param array $data + * @return Model + */ + public function make($data = []) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + + // 保存关联表数据 + $data[$this->foreignKey] = $this->parent->{$this->localKey}; + + return new $this->model($data); + } + + /** + * 批量保存当前关联数据对象 + * @access public + * @param array $dataSet 数据集 + * @return integer + */ + public function saveAll(array $dataSet) + { + $result = false; + foreach ($dataSet as $key => $data) { + $result = $this->save($data); + } + return $result; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $relation = basename(str_replace('\\', '/', $this->model)); + + return $this->parent->db() + ->alias($model) + ->field($model . '.*') + ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $joinType) + ->group($relation . '.' . $this->foreignKey) + ->having('count(' . $id . ')' . $operator . $count); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $relation = basename(str_replace('\\', '/', $this->model)); + + if (is_array($where)) { + foreach ($where as $key => $val) { + if (false === strpos($key, '.')) { + $where[$relation . '.' . $key] = $val; + unset($where[$key]); + } + } + } + + $fields = $this->getRelationQueryFields($fields, $model); + + return $this->parent->db()->alias($model) + ->field($fields) + ->group($model . '.' . $this->localKey) + ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey) + ->where($where); + } + + /** + * 执行基础查询(进执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + 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/thinkphp/library/think/model/relation/HasManyThrough.php b/thinkphp/library/think/model/relation/HasManyThrough.php new file mode 100644 index 0000000..3a9a548 --- /dev/null +++ b/thinkphp/library/think/model/relation/HasManyThrough.php @@ -0,0 +1,157 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\db\Query; +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Relation; + +class HasManyThrough extends Relation +{ + // 中间关联表外键 + protected $throughKey; + // 中间表模型 + protected $through; + + /** + * 构造函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $through 中间模型名 + * @param string $foreignKey 关联外键 + * @param string $throughKey 关联外键 + * @param string $localKey 关联主键 + */ + public function __construct(Model $parent, $model, $through, $foreignKey, $throughKey, $localKey) + { + $this->parent = $parent; + $this->model = $model; + $this->through = $through; + $this->foreignKey = $foreignKey; + $this->throughKey = $throughKey; + $this->localKey = $localKey; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return false|\PDOStatement|string|\think\Collection + */ + public function getRelation($subRelation = '', $closure = null) + { + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + + return $this->relation($subRelation)->select(); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + {} + + /** + * 预载入关联查询 返回模型对象 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + {} + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @return integer + */ + public function relationCount($result, $closure) + {} + + /** + * 创建关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @param string $name 统计数据别名 + * @return string + */ + public function getRelationCountQuery($closure, &$name = null) + { + throw new Exception('relation not support: withCount'); + } + + /** + * 执行基础查询(进执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery) && $this->parent->getData()) { + $through = $this->through; + $alias = Loader::parseName(basename(str_replace('\\', '/', $this->model))); + $throughTable = $through::getTable(); + $pk = (new $through)->getPk(); + $throughKey = $this->throughKey; + $modelTable = $this->parent->getTable(); + $this->query->field($alias . '.*')->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/thinkphp/library/think/model/relation/HasOne.php b/thinkphp/library/think/model/relation/HasOne.php new file mode 100644 index 0000000..db74e4a --- /dev/null +++ b/thinkphp/library/think/model/relation/HasOne.php @@ -0,0 +1,215 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\db\Query; +use think\Loader; +use think\Model; + +class HasOne extends OneToOne +{ + /** + * 构造函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前模型主键 + * @param string $joinType JOIN类型 + */ + public function __construct(Model $parent, $model, $foreignKey, $localKey, $joinType = 'INNER') + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + $this->joinType = $joinType; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return array|false|\PDOStatement|string|Model + */ + public function getRelation($subRelation = '', $closure = null) + { + // 执行关联定义方法 + $localKey = $this->localKey; + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + // 判断关联类型执行查询 + $relationModel = $this->query + ->removeWhereField($this->foreignKey) + ->where($this->foreignKey, $this->parent->$localKey) + ->relation($subRelation) + ->find(); + + if ($relationModel) { + $relationModel->setParent(clone $this->parent); + } + + return $relationModel; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @return Query + */ + public function has() + { + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $relation = basename(str_replace('\\', '/', $this->model)); + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + return $this->parent->db() + ->alias($model) + ->whereExists(function ($query) use ($table, $model, $relation, $localKey, $foreignKey) { + $query->table([$table => $relation])->field($relation . '.' . $foreignKey)->whereExp($model . '.' . $localKey, '=' . $relation . '.' . $foreignKey); + }); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $relation = basename(str_replace('\\', '/', $this->model)); + + if (is_array($where)) { + foreach ($where as $key => $val) { + if (false === strpos($key, '.')) { + $where[$relation . '.' . $key] = $val; + unset($where[$key]); + } + } + } + $fields = $this->getRelationQueryFields($fields, $model); + + return $this->parent->db()->alias($model) + ->field($fields) + ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $this->joinType) + ->where($where); + } + + /** + * 预载入关联查询(数据集) + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure) + { + $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->query, [ + $foreignKey => [ + 'in', + $range, + ], + ], $foreignKey, $relation, $subRelation, $closure); + // 关联属性名 + $attr = Loader::parseName($relation); + // 关联数据封装 + foreach ($resultSet as $result) { + // 关联模型 + if (!isset($data[$result->$localKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$localKey]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($relationModel, $result, $this->bindAttr); + } else { + // 设置关联属性 + $result->setRelation($attr, $relationModel); + } + } + } + } + + /** + * 预载入关联查询(数据) + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + protected function eagerlyOne(&$result, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + $this->query->removeWhereField($foreignKey); + $data = $this->eagerlyWhere($this->query, [$foreignKey => $result->$localKey], $foreignKey, $relation, $subRelation, $closure); + + // 关联模型 + if (!isset($data[$result->$localKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$localKey]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($relationModel, $result, $this->bindAttr); + } else { + $result->setRelation(Loader::parseName($relation), $relationModel); + } + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + 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/thinkphp/library/think/model/relation/MorphMany.php b/thinkphp/library/think/model/relation/MorphMany.php new file mode 100644 index 0000000..2755d57 --- /dev/null +++ b/thinkphp/library/think/model/relation/MorphMany.php @@ -0,0 +1,314 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\Db; +use think\db\Query; +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Relation; + +class MorphMany extends Relation +{ + // 多态字段 + protected $morphKey; + protected $morphType; + // 多态类型 + protected $type; + + /** + * 构造函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $morphKey 关联外键 + * @param string $morphType 多态字段名 + * @param string $type 多态类型 + */ + public function __construct(Model $parent, $model, $morphKey, $morphType, $type) + { + $this->parent = $parent; + $this->model = $model; + $this->type = $type; + $this->morphKey = $morphKey; + $this->morphType = $morphType; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return false|\PDOStatement|string|\think\Collection + */ + public function getRelation($subRelation = '', $closure = null) + { + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + $list = $this->relation($subRelation)->select(); + $parent = clone $this->parent; + + foreach ($list as &$model) { + $model->setParent($parent); + } + + return $list; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + throw new Exception('relation not support: has'); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + $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->eagerlyMorphToMany([ + $morphKey => ['in', $range], + $morphType => $type, + ], $relation, $subRelation, $closure); + // 关联属性名 + $attr = Loader::parseName($relation); + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$pk])) { + $data[$result->$pk] = []; + } + foreach ($data[$result->$pk] as &$relationModel) { + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + $result->setRelation($attr, $this->resultSetBuild($data[$result->$pk])); + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + $pk = $result->getPk(); + if (isset($result->$pk)) { + $data = $this->eagerlyMorphToMany([ + $this->morphKey => $result->$pk, + $this->morphType => $this->type, + ], $relation, $subRelation, $closure); + + if (!isset($data[$result->$pk])) { + $data[$result->$pk] = []; + } + + foreach ($data[$result->$pk] as &$relationModel) { + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + + $result->setRelation(Loader::parseName($relation), $this->resultSetBuild($data[$result->$pk])); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @return integer + */ + public function relationCount($result, $closure) + { + $pk = $result->getPk(); + $count = 0; + if (isset($result->$pk)) { + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + $count = $this->query->where([$this->morphKey => $result->$pk, $this->morphType => $this->type])->count(); + } + return $count; + } + + /** + * 创建关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @param string $name 统计数据别名 + * @return string + */ + public function getRelationCountQuery($closure, &$name = null) + { + if ($closure) { + $return = call_user_func_array($closure, [ & $this->query]); + if ($return && is_string($return)) { + $name = $return; + } + } + + return $this->query->where([ + $this->morphKey => [ + 'exp', + Db::raw('=' . $this->parent->getTable() . '.' . $this->parent->getPk()), + ], + $this->morphType => $this->type, + ])->fetchSql()->count(); + } + + /** + * 多态一对多 关联模型预查询 + * @access public + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param bool|\Closure $closure 闭包 + * @return array + */ + protected function eagerlyMorphToMany($where, $relation, $subRelation = '', $closure = false) + { + // 预载入关联查询 支持嵌套预载入 + if ($closure) { + call_user_func_array($closure, [ & $this]); + } + $list = $this->query->where($where)->with($subRelation)->select(); + $morphKey = $this->morphKey; + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $data[$set->$morphKey][] = $set; + } + return $data; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @return Model|false + */ + public function save($data) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + + // 保存关联表数据 + $pk = $this->parent->getPk(); + + $data[$this->morphKey] = $this->parent->$pk; + $data[$this->morphType] = $this->type; + + $model = new $this->model(); + + return $model->save() ? $model : false; + } + + /** + * 创建关联对象实例 + * @param array $data + * @return Model + */ + public function make($data = []) + { + 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 array $dataSet 数据集 + * @return integer + */ + public function saveAll(array $dataSet) + { + $result = false; + foreach ($dataSet as $key => $data) { + $result = $this->save($data); + } + return $result; + } + + /** + * 执行基础查询(进执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery) && $this->parent->getData()) { + $pk = $this->parent->getPk(); + $map[$this->morphKey] = $this->parent->$pk; + $map[$this->morphType] = $this->type; + $this->query->where($map); + $this->baseQuery = true; + } + } + +} diff --git a/thinkphp/library/think/model/relation/MorphOne.php b/thinkphp/library/think/model/relation/MorphOne.php new file mode 100644 index 0000000..5ec7172 --- /dev/null +++ b/thinkphp/library/think/model/relation/MorphOne.php @@ -0,0 +1,263 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\db\Query; +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Relation; + +class MorphOne extends Relation +{ + // 多态字段 + protected $morphKey; + protected $morphType; + // 多态类型 + protected $type; + + /** + * 构造函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $morphKey 关联外键 + * @param string $morphType 多态字段名 + * @param string $type 多态类型 + */ + public function __construct(Model $parent, $model, $morphKey, $morphType, $type) + { + $this->parent = $parent; + $this->model = $model; + $this->type = $type; + $this->morphKey = $morphKey; + $this->morphType = $morphType; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return false|\PDOStatement|string|\think\Collection + */ + public function getRelation($subRelation = '', $closure = null) + { + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + $relationModel = $this->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类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + $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, + ], $relation, $subRelation, $closure); + // 关联属性名 + $attr = Loader::parseName($relation); + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$pk])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$pk]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + + $result->setRelation($attr, $relationModel); + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + $pk = $result->getPk(); + if (isset($result->$pk)) { + $pk = $result->$pk; + $data = $this->eagerlyMorphToOne([ + $this->morphKey => $pk, + $this->morphType => $this->type, + ], $relation, $subRelation, $closure); + + if (isset($data[$pk])) { + $relationModel = $data[$pk]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } else { + $relationModel = null; + } + + $result->setRelation(Loader::parseName($relation), $relationModel); + } + } + + /** + * 多态一对一 关联模型预查询 + * @access public + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param bool|\Closure $closure 闭包 + * @return array + */ + protected function eagerlyMorphToOne($where, $relation, $subRelation = '', $closure = false) + { + // 预载入关联查询 支持嵌套预载入 + if ($closure) { + call_user_func_array($closure, [ & $this]); + } + $list = $this->query->where($where)->with($subRelation)->find(); + $morphKey = $this->morphKey; + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $data[$set->$morphKey][] = $set; + } + return $data; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @return Model|false + */ + public function save($data) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + + // 保存关联表数据 + $pk = $this->parent->getPk(); + + $data[$this->morphKey] = $this->parent->$pk; + $data[$this->morphType] = $this->type; + + $model = new $this->model(); + + return $model->save() ? $model : false; + } + + /** + * 创建关联对象实例 + * @param array $data + * @return Model + */ + public function make($data = []) + { + 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() + { + if (empty($this->baseQuery) && $this->parent->getData()) { + $pk = $this->parent->getPk(); + $map[$this->morphKey] = $this->parent->$pk; + $map[$this->morphType] = $this->type; + $this->query->where($map); + $this->baseQuery = true; + } + } + + /** + * 创建关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @param string $name 统计数据别名 + * @return string + */ + public function getRelationCountQuery($closure, &$name = null) + { + throw new Exception('relation not support: withCount'); + } +} diff --git a/thinkphp/library/think/model/relation/MorphTo.php b/thinkphp/library/think/model/relation/MorphTo.php new file mode 100644 index 0000000..7d45265 --- /dev/null +++ b/thinkphp/library/think/model/relation/MorphTo.php @@ -0,0 +1,299 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Relation; + +class MorphTo extends Relation +{ + // 多态字段 + protected $morphKey; + protected $morphType; + // 多态别名 + protected $alias; + protected $relation; + + /** + * 构造函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $morphType 多态字段名 + * @param string $morphKey 外键名 + * @param array $alias 多态别名定义 + * @param string $relation 关联名 + */ + public function __construct(Model $parent, $morphType, $morphKey, $alias = [], $relation = null) + { + $this->parent = $parent; + $this->morphType = $morphType; + $this->morphKey = $morphKey; + $this->alias = $alias; + $this->relation = $relation; + } + + /** + * 获取当前的关联模型类的实例 + * @access public + * @return Model + */ + public function getModel() + { + $morphType = $this->morphType; + $model = $this->parseModel($this->parent->$morphType); + return (new $model); + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return mixed + */ + public function getRelation($subRelation = '', $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类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 解析模型的完整命名空间 + * @access protected + * @param string $model 模型名(或者完整类名) + * @return string + */ + protected function parseModel($model) + { + 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, Loader::parseName($model, 1)); + $model = implode('\\', $path); + } + return $model; + } + + /** + * 设置多态别名 + * @access public + * @param array $alias 别名定义 + * @return $this + */ + public function setAlias($alias) + { + $this->alias = $alias; + return $this; + } + + /** + * 移除关联查询参数 + * @access public + * @return $this + */ + public function removeOption() + { + return $this; + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + * @throws Exception + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (!empty($result->$morphKey)) { + $range[$result->$morphType][] = $result->$morphKey; + } + } + + if (!empty($range)) { + // 关联属性名 + $attr = Loader::parseName($relation); + foreach ($range as $key => $val) { + // 多态类型映射 + $model = $this->parseModel($key); + $obj = new $model; + $pk = $obj->getPk(); + $list = $obj->all($val, $subRelation); + $data = []; + foreach ($list as $k => $vo) { + $data[$vo->$pk] = $vo; + } + foreach ($resultSet as $result) { + if ($key == $result->$morphType) { + // 关联模型 + if (!isset($data[$result->$morphKey])) { + throw new Exception('relation data not exists :' . $this->model); + } else { + $relationModel = $data[$result->$morphKey]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + + $result->setRelation($attr, $relationModel); + } + } + } + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + // 多态类型映射 + $model = $this->parseModel($result->{$this->morphType}); + $this->eagerlyMorphToOne($model, $relation, $result, $subRelation); + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @return integer + */ + public function relationCount($result, $closure) + { + } + + /** + * 多态MorphTo 关联模型预查询 + * @access public + * @param object $model 关联模型对象 + * @param string $relation 关联名 + * @param $result + * @param string $subRelation 子关联 + * @return void + */ + protected function eagerlyMorphToOne($model, $relation, &$result, $subRelation = '') + { + // 预载入关联查询 支持嵌套预载入 + $pk = $this->parent->{$this->morphKey}; + $data = (new $model)->with($subRelation)->find($pk); + if ($data) { + $data->setParent(clone $result); + $data->isUpdate(true); + } + $result->setRelation(Loader::parseName($relation), $data ?: null); + } + + /** + * 添加关联数据 + * @access public + * @param Model $model 关联模型对象 + * @param string $type 多态类型 + * @return Model + */ + public function associate($model, $type = '') + { + $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() + { + $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); + } + + /** + * 创建关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @param string $name 统计数据别名 + * @return string + */ + public function getRelationCountQuery($closure, &$name = null) + { + throw new Exception('relation not support: withCount'); + } +} diff --git a/thinkphp/library/think/model/relation/OneToOne.php b/thinkphp/library/think/model/relation/OneToOne.php new file mode 100644 index 0000000..353ce21 --- /dev/null +++ b/thinkphp/library/think/model/relation/OneToOne.php @@ -0,0 +1,337 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\db\Query; +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Relation; + +/** + * Class OneToOne + * @package think\model\relation + * + */ +abstract class OneToOne extends Relation +{ + // 预载入方式 0 -JOIN 1 -IN + protected $eagerlyType = 1; + // 当前关联的JOIN类型 + protected $joinType; + // 要绑定的属性 + protected $bindAttr = []; + // 关联方法名 + protected $relation; + + /** + * 设置join类型 + * @access public + * @param string $type JOIN类型 + * @return $this + */ + public function joinType($type) + { + $this->joinType = $type; + return $this; + } + + /** + * 预载入关联查询(JOIN方式) + * @access public + * @param Query $query 查询对象 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param \Closure $closure 闭包条件 + * @param bool $first + * @return void + */ + public function eagerly(Query $query, $relation, $subRelation, $closure, $first) + { + $name = Loader::parseName(basename(str_replace('\\', '/', get_class($query->getModel())))); + + if ($first) { + $table = $query->getTable(); + $query->table([$table => $name]); + if ($query->getOptions('field')) { + $field = $query->getOptions('field'); + $query->removeOption('field'); + } else { + $field = true; + } + $query->field($field, false, $table, $name); + $field = null; + } + + // 预载入封装 + $joinTable = $this->query->getTable(); + $joinAlias = $relation; + $query->via($joinAlias); + + if ($this instanceof BelongsTo) { + $query->join([$joinTable => $joinAlias], $name . '.' . $this->foreignKey . '=' . $joinAlias . '.' . $this->localKey, $this->joinType); + } else { + $query->join([$joinTable => $joinAlias], $name . '.' . $this->localKey . '=' . $joinAlias . '.' . $this->foreignKey, $this->joinType); + } + + if ($closure) { + // 执行闭包查询 + call_user_func_array($closure, [ & $query]); + // 使用withField指定获取关联的字段,如 + // $query->where(['id'=>1])->withField('id,name'); + if ($query->getOptions('with_field')) { + $field = $query->getOptions('with_field'); + $query->removeOption('with_field'); + } + } elseif (isset($this->option['field'])) { + $field = $this->option['field']; + } + $query->field(isset($field) ? $field : true, false, $joinTable, $joinAlias, $relation . '__'); + } + + /** + * 预载入关联查询(数据集) + * @param array $resultSet + * @param string $relation + * @param string $subRelation + * @param \Closure $closure + * @return mixed + */ + abstract protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure); + + /** + * 预载入关联查询(数据) + * @param Model $result + * @param string $relation + * @param string $subRelation + * @param \Closure $closure + * @return mixed + */ + abstract protected function eagerlyOne(&$result, $relation, $subRelation, $closure); + + /** + * 预载入关联查询(数据集) + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + if (1 == $this->eagerlyType) { + // IN查询 + $this->eagerlySet($resultSet, $relation, $subRelation, $closure); + } else { + // 模型关联组装 + foreach ($resultSet as $result) { + $this->match($this->model, $relation, $result); + } + } + } + + /** + * 预载入关联查询(数据) + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + if (1 == $this->eagerlyType) { + // IN查询 + $this->eagerlyOne($result, $relation, $subRelation, $closure); + } else { + // 模型关联组装 + $this->match($this->model, $relation, $result); + } + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @return Model|false + */ + public function save($data) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + $model = new $this->model; + // 保存关联表数据 + $data[$this->foreignKey] = $this->parent->{$this->localKey}; + return $model->save($data) ? $model : false; + } + + /** + * 设置预载入方式 + * @access public + * @param integer $type 预载入方式 0 JOIN查询 1 IN查询 + * @return $this + */ + public function setEagerlyType($type) + { + $this->eagerlyType = $type; + return $this; + } + + /** + * 获取预载入方式 + * @access public + * @return integer + */ + public function getEagerlyType() + { + return $this->eagerlyType; + } + + /** + * 绑定关联表的属性到父模型属性 + * @access public + * @param mixed $attr 要绑定的属性列表 + * @return $this + */ + public function bind($attr) + { + if (is_string($attr)) { + $attr = explode(',', $attr); + } + $this->bindAttr = $attr; + return $this; + } + + /** + * 获取绑定属性 + * @access public + * @return array + */ + public function getBindAttr() + { + return $this->bindAttr; + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @return integer + */ + public function relationCount($result, $closure) + { + } + + /** + * 一对一 关联模型预查询拼装 + * @access public + * @param string $model 模型名称 + * @param string $relation 关联名 + * @param Model $result 模型对象实例 + * @return void + */ + protected function match($model, $relation, &$result) + { + // 重新组装模型数据 + foreach ($result->getData() as $key => $val) { + if (strpos($key, '__')) { + list($name, $attr) = explode('__', $key, 2); + if ($name == $relation) { + $list[$name][$attr] = $val; + unset($result->$key); + } + } + } + + if (isset($list[$relation])) { + $relationModel = new $model($list[$relation]); + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + + if (!empty($this->bindAttr)) { + $this->bindAttr($relationModel, $result, $this->bindAttr); + } + } else { + $relationModel = null; + } + $result->setRelation(Loader::parseName($relation), $relationModel); + } + + /** + * 绑定关联属性到父模型 + * @access protected + * @param Model $model 关联模型对象 + * @param Model $result 父模型对象 + * @param array $bindAttr 绑定属性 + * @return void + * @throws Exception + */ + protected function bindAttr($model, &$result, $bindAttr) + { + foreach ($bindAttr as $key => $attr) { + $key = is_numeric($key) ? $attr : $key; + if (isset($result->$key)) { + throw new Exception('bind attr has exists:' . $key); + } else { + $result->setAttr($key, $model ? $model->$attr : null); + } + } + } + + /** + * 一对一 关联模型预查询(IN方式) + * @access public + * @param object $model 关联模型对象 + * @param array $where 关联预查询条件 + * @param string $key 关联键名 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param bool|\Closure $closure + * @return array + */ + protected function eagerlyWhere($model, $where, $key, $relation, $subRelation = '', $closure = false) + { + $this->baseQuery = true; + + // 预载入关联查询 支持嵌套预载入 + if ($closure) { + call_user_func_array($closure, [ & $model]); + if ($field = $model->getOptions('with_field')) { + $model->field($field)->removeOption('with_field'); + } + } + $list = $model->where($where)->with($subRelation)->select(); + + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $data[$set->$key] = $set; + } + return $data; + } + + /** + * 创建关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @param string $name 统计数据别名 + * @return string + */ + public function getRelationCountQuery($closure, &$name = null) + { + throw new Exception('relation not support: withCount'); + } +} diff --git a/thinkphp/library/think/paginator/driver/Bootstrap.php b/thinkphp/library/think/paginator/driver/Bootstrap.php new file mode 100644 index 0000000..c5ac60d --- /dev/null +++ b/thinkphp/library/think/paginator/driver/Bootstrap.php @@ -0,0 +1,205 @@ + +// +---------------------------------------------------------------------- + +namespace think\paginator\driver; + +use think\Paginator; + +class Bootstrap extends Paginator +{ + + /** + * 上一页按钮 + * @param string $text + * @return string + */ + protected function getPreviousButton($text = "«") + { + + 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($text = '»') + { + if (!$this->hasMore) { + return $this->getDisabledTextWrapper($text); + } + + $url = $this->url($this->currentPage() + 1); + + return $this->getPageLinkWrapper($url, $text); + } + + /** + * 页码按钮 + * @return string + */ + protected function getLinks() + { + 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( + '
    %s %s
', + $this->getPreviousButton(), + $this->getNextButton() + ); + } else { + return sprintf( + '
    %s %s %s
', + $this->getPreviousButton(), + $this->getLinks(), + $this->getNextButton() + ); + } + } + } + + /** + * 生成一个可点击的按钮 + * + * @param string $url + * @param int $page + * @return string + */ + protected function getAvailablePageWrapper($url, $page) + { + return '
  • ' . $page . '
  • '; + } + + /** + * 生成一个禁用的按钮 + * + * @param string $text + * @return string + */ + protected function getDisabledTextWrapper($text) + { + return '
  • ' . $text . '
  • '; + } + + /** + * 生成一个激活的按钮 + * + * @param string $text + * @return string + */ + protected function getActivePageWrapper($text) + { + return '
  • ' . $text . '
  • '; + } + + /** + * 生成省略号按钮 + * + * @return string + */ + protected function getDots() + { + return $this->getDisabledTextWrapper('...'); + } + + /** + * 批量生成页码按钮. + * + * @param array $urls + * @return string + */ + protected function getUrlLinks(array $urls) + { + $html = ''; + + foreach ($urls as $page => $url) { + $html .= $this->getPageLinkWrapper($url, $page); + } + + return $html; + } + + /** + * 生成普通页码按钮 + * + * @param string $url + * @param int $page + * @return string + */ + protected function getPageLinkWrapper($url, $page) + { + if ($page == $this->currentPage()) { + return $this->getActivePageWrapper($page); + } + + return $this->getAvailablePageWrapper($url, $page); + } +} diff --git a/thinkphp/library/think/process/Builder.php b/thinkphp/library/think/process/Builder.php new file mode 100644 index 0000000..da56163 --- /dev/null +++ b/thinkphp/library/think/process/Builder.php @@ -0,0 +1,233 @@ + +// +---------------------------------------------------------------------- + +namespace think\process; + +use think\Process; + +class Builder +{ + private $arguments; + private $cwd; + private $env = null; + private $input; + private $timeout = 60; + private $options = []; + private $inheritEnv = true; + private $prefix = []; + private $outputDisabled = false; + + /** + * 构造方法 + * @param string[] $arguments 参数 + */ + public function __construct(array $arguments = []) + { + $this->arguments = $arguments; + } + + /** + * 创建一个实例 + * @param string[] $arguments 参数 + * @return self + */ + public static function create(array $arguments = []) + { + return new static($arguments); + } + + /** + * 添加一个参数 + * @param string $argument 参数 + * @return self + */ + public function add($argument) + { + $this->arguments[] = $argument; + + return $this; + } + + /** + * 添加一个前缀 + * @param string|array $prefix + * @return self + */ + public function setPrefix($prefix) + { + $this->prefix = is_array($prefix) ? $prefix : [$prefix]; + + return $this; + } + + /** + * 设置参数 + * @param string[] $arguments + * @return self + */ + public function setArguments(array $arguments) + { + $this->arguments = $arguments; + + return $this; + } + + /** + * 设置工作目录 + * @param null|string $cwd + * @return self + */ + public function setWorkingDirectory($cwd) + { + $this->cwd = $cwd; + + return $this; + } + + /** + * 是否初始化环境变量 + * @param bool $inheritEnv + * @return self + */ + public function inheritEnvironmentVariables($inheritEnv = true) + { + $this->inheritEnv = $inheritEnv; + + return $this; + } + + /** + * 设置环境变量 + * @param string $name + * @param null|string $value + * @return self + */ + public function setEnv($name, $value) + { + $this->env[$name] = $value; + + return $this; + } + + /** + * 添加环境变量 + * @param array $variables + * @return self + */ + public function addEnvironmentVariables(array $variables) + { + $this->env = array_replace($this->env, $variables); + + return $this; + } + + /** + * 设置输入 + * @param mixed $input + * @return self + */ + public function setInput($input) + { + $this->input = Utils::validateInput(sprintf('%s::%s', __CLASS__, __FUNCTION__), $input); + + return $this; + } + + /** + * 设置超时时间 + * @param float|null $timeout + * @return self + */ + public function setTimeout($timeout) + { + if (null === $timeout) { + $this->timeout = null; + + return $this; + } + + $timeout = (float) $timeout; + + if ($timeout < 0) { + throw new \InvalidArgumentException('The timeout value must be a valid positive integer or float number.'); + } + + $this->timeout = $timeout; + + return $this; + } + + /** + * 设置proc_open选项 + * @param string $name + * @param string $value + * @return self + */ + public function setOption($name, $value) + { + $this->options[$name] = $value; + + return $this; + } + + /** + * 禁止输出 + * @return self + */ + public function disableOutput() + { + $this->outputDisabled = true; + + return $this; + } + + /** + * 开启输出 + * @return self + */ + public function enableOutput() + { + $this->outputDisabled = false; + + return $this; + } + + /** + * 创建一个Process实例 + * @return Process + */ + public function getProcess() + { + if (0 === count($this->prefix) && 0 === count($this->arguments)) { + throw new \LogicException('You must add() command arguments before calling getProcess().'); + } + + $options = $this->options; + + $arguments = array_merge($this->prefix, $this->arguments); + $script = implode(' ', array_map([__NAMESPACE__ . '\\Utils', 'escapeArgument'], $arguments)); + + if ($this->inheritEnv) { + // include $_ENV for BC purposes + $env = array_replace($_ENV, $_SERVER, $this->env); + } else { + $env = $this->env; + } + + $process = new Process($script, $this->cwd, $env, $this->input, $this->timeout, $options); + + if ($this->outputDisabled) { + $process->disableOutput(); + } + + return $process; + } +} diff --git a/thinkphp/library/think/process/Utils.php b/thinkphp/library/think/process/Utils.php new file mode 100644 index 0000000..f94c648 --- /dev/null +++ b/thinkphp/library/think/process/Utils.php @@ -0,0 +1,75 @@ + +// +---------------------------------------------------------------------- + +namespace think\process; + +class Utils +{ + + /** + * 转义字符串 + * @param string $argument + * @return string + */ + public static function escapeArgument($argument) + { + + if ('' === $argument) { + return escapeshellarg($argument); + } + $escapedArgument = ''; + $quote = false; + foreach (preg_split('/(")/i', $argument, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE) as $part) { + if ('"' === $part) { + $escapedArgument .= '\\"'; + } elseif (self::isSurroundedBy($part, '%')) { + // Avoid environment variable expansion + $escapedArgument .= '^%"' . substr($part, 1, -1) . '"^%'; + } else { + // escape trailing backslash + if ('\\' === substr($part, -1)) { + $part .= '\\'; + } + $quote = true; + $escapedArgument .= $part; + } + } + if ($quote) { + $escapedArgument = '"' . $escapedArgument . '"'; + } + return $escapedArgument; + } + + /** + * 验证并进行规范化Process输入。 + * @param string $caller + * @param mixed $input + * @return string + * @throws \InvalidArgumentException + */ + public static function validateInput($caller, $input) + { + if (null !== $input) { + if (is_resource($input)) { + return $input; + } + if (is_scalar($input)) { + return (string) $input; + } + throw new \InvalidArgumentException(sprintf('%s only accepts strings or stream resources.', $caller)); + } + return $input; + } + + private static function isSurroundedBy($arg, $char) + { + return 2 < strlen($arg) && $char === $arg[0] && $char === $arg[strlen($arg) - 1]; + } + +} diff --git a/thinkphp/library/think/process/exception/Failed.php b/thinkphp/library/think/process/exception/Failed.php new file mode 100644 index 0000000..5295082 --- /dev/null +++ b/thinkphp/library/think/process/exception/Failed.php @@ -0,0 +1,42 @@ + +// +---------------------------------------------------------------------- + +namespace think\process\exception; + +use think\Process; + +class Failed 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.' . "\nExit Code: %s(%s)", $process->getCommandLine(), $process->getExitCode(), $process->getExitCodeText()); + + 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/thinkphp/library/think/process/exception/Timeout.php b/thinkphp/library/think/process/exception/Timeout.php new file mode 100644 index 0000000..d5f1162 --- /dev/null +++ b/thinkphp/library/think/process/exception/Timeout.php @@ -0,0 +1,61 @@ + +// +---------------------------------------------------------------------- + +namespace think\process\exception; + +use think\Process; + +class Timeout extends \RuntimeException +{ + + const TYPE_GENERAL = 1; + const TYPE_IDLE = 2; + + private $process; + private $timeoutType; + + public function __construct(Process $process, $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 $this->timeoutType === self::TYPE_GENERAL; + } + + public function isIdleTimeout() + { + return $this->timeoutType === self::TYPE_IDLE; + } + + 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/thinkphp/library/think/process/pipes/Pipes.php b/thinkphp/library/think/process/pipes/Pipes.php new file mode 100644 index 0000000..82396b8 --- /dev/null +++ b/thinkphp/library/think/process/pipes/Pipes.php @@ -0,0 +1,93 @@ + +// +---------------------------------------------------------------------- + +namespace think\process\pipes; + +abstract class Pipes +{ + + /** @var array */ + public $pipes = []; + + /** @var string */ + protected $inputBuffer = ''; + /** @var resource|null */ + protected $input; + + /** @var bool */ + private $blocked = true; + + const CHUNK_SIZE = 16384; + + /** + * 返回用于 proc_open 描述符的数组 + * @return array + */ + abstract public function getDescriptors(); + + /** + * 返回一个数组的索引由其相关的流,以防这些管道使用的临时文件的文件名。 + * @return string[] + */ + abstract public function getFiles(); + + /** + * 文件句柄和管道中读取数据。 + * @param bool $blocking 是否使用阻塞调用 + * @param bool $close 是否要关闭管道,如果他们已经到达 EOF。 + * @return string[] + */ + abstract public function readAndWrite($blocking, $close = false); + + /** + * 返回当前状态如果有打开的文件句柄或管道。 + * @return bool + */ + abstract public function areOpen(); + + /** + * {@inheritdoc} + */ + public function close() + { + foreach ($this->pipes as $pipe) { + fclose($pipe); + } + $this->pipes = []; + } + + /** + * 检查系统调用已被中断 + * @return bool + */ + protected function hasSystemCallBeenInterrupted() + { + $lastError = error_get_last(); + + return isset($lastError['message']) && false !== stripos($lastError['message'], 'interrupted system call'); + } + + protected function unblock() + { + if (!$this->blocked) { + return; + } + + foreach ($this->pipes as $pipe) { + stream_set_blocking($pipe, 0); + } + if (null !== $this->input) { + stream_set_blocking($this->input, 0); + } + + $this->blocked = false; + } +} diff --git a/thinkphp/library/think/process/pipes/Unix.php b/thinkphp/library/think/process/pipes/Unix.php new file mode 100644 index 0000000..fd99a5d --- /dev/null +++ b/thinkphp/library/think/process/pipes/Unix.php @@ -0,0 +1,196 @@ + +// +---------------------------------------------------------------------- + +namespace think\process\pipes; + +use think\Process; + +class Unix extends Pipes +{ + + /** @var bool */ + private $ttyMode; + /** @var bool */ + private $ptyMode; + /** @var bool */ + private $disableOutput; + + public function __construct($ttyMode, $ptyMode, $input, $disableOutput) + { + $this->ttyMode = (bool) $ttyMode; + $this->ptyMode = (bool) $ptyMode; + $this->disableOutput = (bool) $disableOutput; + + if (is_resource($input)) { + $this->input = $input; + } else { + $this->inputBuffer = (string) $input; + } + } + + public function __destruct() + { + $this->close(); + } + + /** + * {@inheritdoc} + */ + public function getDescriptors() + { + if ($this->disableOutput) { + $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) + { + + if (1 === count($this->pipes) && [0] === array_keys($this->pipes)) { + fclose($this->pipes[0]); + unset($this->pipes[0]); + } + + if (empty($this->pipes)) { + return []; + } + + $this->unblock(); + + $read = []; + + if (null !== $this->input) { + $r = array_merge($this->pipes, ['input' => $this->input]); + } else { + $r = $this->pipes; + } + + unset($r[0]); + + $w = isset($this->pipes[0]) ? [$this->pipes[0]] : null; + $e = null; + + if (false === $n = @stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) { + + if (!$this->hasSystemCallBeenInterrupted()) { + $this->pipes = []; + } + + return $read; + } + + if (0 === $n) { + return $read; + } + + foreach ($r as $pipe) { + + $type = (false !== $found = array_search($pipe, $this->pipes)) ? $found : 'input'; + $data = ''; + while ('' !== $dataread = (string) fread($pipe, self::CHUNK_SIZE)) { + $data .= $dataread; + } + + if ('' !== $data) { + if ('input' === $type) { + $this->inputBuffer .= $data; + } else { + $read[$type] = $data; + } + } + + if (false === $data || (true === $close && feof($pipe) && '' === $data)) { + if ('input' === $type) { + $this->input = null; + } else { + fclose($this->pipes[$type]); + unset($this->pipes[$type]); + } + } + } + + if (null !== $w && 0 < count($w)) { + while (strlen($this->inputBuffer)) { + $written = fwrite($w[0], $this->inputBuffer, 2 << 18); // write 512k + if ($written > 0) { + $this->inputBuffer = (string) substr($this->inputBuffer, $written); + } else { + break; + } + } + } + + if ('' === $this->inputBuffer && null === $this->input && isset($this->pipes[0])) { + fclose($this->pipes[0]); + unset($this->pipes[0]); + } + + return $read; + } + + /** + * {@inheritdoc} + */ + public function areOpen() + { + return (bool) $this->pipes; + } + + /** + * 创建一个新的 UnixPipes 实例 + * @param Process $process + * @param string|resource $input + * @return self + */ + public static function create(Process $process, $input) + { + return new static($process->isTty(), $process->isPty(), $input, $process->isOutputDisabled()); + } +} diff --git a/thinkphp/library/think/process/pipes/Windows.php b/thinkphp/library/think/process/pipes/Windows.php new file mode 100644 index 0000000..1b8b0d4 --- /dev/null +++ b/thinkphp/library/think/process/pipes/Windows.php @@ -0,0 +1,228 @@ + +// +---------------------------------------------------------------------- + +namespace think\process\pipes; + +use think\Process; + +class Windows extends Pipes +{ + + /** @var array */ + private $files = []; + /** @var array */ + private $fileHandles = []; + /** @var array */ + private $readBytes = [ + Process::STDOUT => 0, + Process::STDERR => 0, + ]; + /** @var bool */ + private $disableOutput; + + public function __construct($disableOutput, $input) + { + $this->disableOutput = (bool) $disableOutput; + + if (!$this->disableOutput) { + + $this->files = [ + Process::STDOUT => tempnam(sys_get_temp_dir(), 'sf_proc_stdout'), + Process::STDERR => tempnam(sys_get_temp_dir(), 'sf_proc_stderr'), + ]; + foreach ($this->files as $offset => $file) { + $this->fileHandles[$offset] = fopen($this->files[$offset], 'rb'); + if (false === $this->fileHandles[$offset]) { + throw new \RuntimeException('A temporary file could not be opened to write the process output to, verify that your TEMP environment variable is writable'); + } + } + } + + if (is_resource($input)) { + $this->input = $input; + } else { + $this->inputBuffer = $input; + } + } + + public function __destruct() + { + $this->close(); + $this->removeFiles(); + } + + /** + * {@inheritdoc} + */ + public function getDescriptors() + { + if ($this->disableOutput) { + $nullstream = fopen('NUL', 'c'); + + return [ + ['pipe', 'r'], + $nullstream, + $nullstream, + ]; + } + + return [ + ['pipe', 'r'], + ['file', 'NUL', 'w'], + ['file', 'NUL', 'w'], + ]; + } + + /** + * {@inheritdoc} + */ + public function getFiles() + { + return $this->files; + } + + /** + * {@inheritdoc} + */ + public function readAndWrite($blocking, $close = false) + { + $this->write($blocking, $close); + + $read = []; + $fh = $this->fileHandles; + foreach ($fh as $type => $fileHandle) { + if (0 !== fseek($fileHandle, $this->readBytes[$type])) { + continue; + } + $data = ''; + $dataread = null; + while (!feof($fileHandle)) { + if (false !== $dataread = fread($fileHandle, self::CHUNK_SIZE)) { + $data .= $dataread; + } + } + if (0 < $length = strlen($data)) { + $this->readBytes[$type] += $length; + $read[$type] = $data; + } + + if (false === $dataread || (true === $close && feof($fileHandle) && '' === $data)) { + fclose($this->fileHandles[$type]); + unset($this->fileHandles[$type]); + } + } + + return $read; + } + + /** + * {@inheritdoc} + */ + public function areOpen() + { + return (bool) $this->pipes && (bool) $this->fileHandles; + } + + /** + * {@inheritdoc} + */ + public function close() + { + parent::close(); + foreach ($this->fileHandles as $handle) { + fclose($handle); + } + $this->fileHandles = []; + } + + /** + * 创建一个新的 WindowsPipes 实例。 + * @param Process $process + * @param $input + * @return self + */ + public static function create(Process $process, $input) + { + return new static($process->isOutputDisabled(), $input); + } + + /** + * 删除临时文件 + */ + private function removeFiles() + { + foreach ($this->files as $filename) { + if (file_exists($filename)) { + @unlink($filename); + } + } + $this->files = []; + } + + /** + * 写入到 stdin 输入 + * @param bool $blocking + * @param bool $close + */ + private function write($blocking, $close) + { + if (empty($this->pipes)) { + return; + } + + $this->unblock(); + + $r = null !== $this->input ? ['input' => $this->input] : null; + $w = isset($this->pipes[0]) ? [$this->pipes[0]] : null; + $e = null; + + if (false === $n = @stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) { + if (!$this->hasSystemCallBeenInterrupted()) { + $this->pipes = []; + } + + return; + } + + if (0 === $n) { + return; + } + + if (null !== $r && 0 < count($r)) { + $data = ''; + while ($dataread = fread($r['input'], self::CHUNK_SIZE)) { + $data .= $dataread; + } + + $this->inputBuffer .= $data; + + if (false === $data || (true === $close && feof($r['input']) && '' === $data)) { + $this->input = null; + } + } + + if (null !== $w && 0 < count($w)) { + while (strlen($this->inputBuffer)) { + $written = fwrite($w[0], $this->inputBuffer, 2 << 18); + if ($written > 0) { + $this->inputBuffer = (string) substr($this->inputBuffer, $written); + } else { + break; + } + } + } + + if ('' === $this->inputBuffer && null === $this->input && isset($this->pipes[0])) { + fclose($this->pipes[0]); + unset($this->pipes[0]); + } + } +} diff --git a/thinkphp/library/think/response/Json.php b/thinkphp/library/think/response/Json.php new file mode 100644 index 0000000..c906bfc --- /dev/null +++ b/thinkphp/library/think/response/Json.php @@ -0,0 +1,51 @@ + +// +---------------------------------------------------------------------- + +namespace think\response; + +use think\Response; + +class Json extends Response +{ + // 输出参数 + protected $options = [ + 'json_encode_param' => JSON_UNESCAPED_UNICODE, + ]; + + protected $contentType = 'application/json'; + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + * @throws \Exception + */ + protected function output($data) + { + try { + // 返回JSON数据格式到客户端 包含状态信息 + $data = json_encode($data, $this->options['json_encode_param']); + + if ($data === false) { + throw new \InvalidArgumentException(json_last_error_msg()); + } + + return $data; + } catch (\Exception $e) { + if ($e->getPrevious()) { + throw $e->getPrevious(); + } + throw $e; + } + } + +} diff --git a/thinkphp/library/think/response/Jsonp.php b/thinkphp/library/think/response/Jsonp.php new file mode 100644 index 0000000..404bacb --- /dev/null +++ b/thinkphp/library/think/response/Jsonp.php @@ -0,0 +1,58 @@ + +// +---------------------------------------------------------------------- + +namespace think\response; + +use think\Request; +use think\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'; + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + * @throws \Exception + */ + protected function output($data) + { + try { + // 返回JSON数据格式到客户端 包含状态信息 [当url_common_param为false时是无法获取到$_GET的数据的,故使用Request来获取] + $var_jsonp_handler = Request::instance()->param($this->options['var_jsonp_handler'], ""); + $handler = !empty($var_jsonp_handler) ? $var_jsonp_handler : $this->options['default_jsonp_handler']; + + $data = json_encode($data, $this->options['json_encode_param']); + + if ($data === false) { + 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/thinkphp/library/think/response/Redirect.php b/thinkphp/library/think/response/Redirect.php new file mode 100644 index 0000000..91694cb --- /dev/null +++ b/thinkphp/library/think/response/Redirect.php @@ -0,0 +1,105 @@ + +// +---------------------------------------------------------------------- + +namespace think\response; + +use think\Request; +use think\Response; +use think\Session; +use think\Url; + +class Redirect extends Response +{ + + protected $options = []; + + // URL参数 + protected $params = []; + + public function __construct($data = '', $code = 302, array $header = [], array $options = []) + { + parent::__construct($data, $code, $header, $options); + $this->cacheControl('no-cache,must-revalidate'); + } + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + */ + protected function output($data) + { + $this->header['Location'] = $this->getTargetUrl(); + 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) { + Session::flash($key, $val); + } + } else { + Session::flash($name, $value); + } + return $this; + } + + /** + * 获取跳转地址 + * @return string + */ + public function getTargetUrl() + { + if (strpos($this->data, '://') || (0 === strpos($this->data, '/') && empty($this->params))) { + return $this->data; + } else { + return Url::build($this->data, $this->params); + } + } + + public function params($params = []) + { + $this->params = $params; + return $this; + } + + /** + * 记住当前url后跳转 + * @return $this + */ + public function remember() + { + Session::set('redirect_url', Request::instance()->url()); + return $this; + } + + /** + * 跳转到上次记住的url + * @return $this + */ + public function restore() + { + if (Session::has('redirect_url')) { + $this->data = Session::get('redirect_url'); + Session::delete('redirect_url'); + } + return $this; + } +} diff --git a/thinkphp/library/think/response/View.php b/thinkphp/library/think/response/View.php new file mode 100644 index 0000000..48f944a --- /dev/null +++ b/thinkphp/library/think/response/View.php @@ -0,0 +1,89 @@ + +// +---------------------------------------------------------------------- + +namespace think\response; + +use think\Config; +use think\Response; +use think\View as ViewTemplate; + +class View extends Response +{ + // 输出参数 + protected $options = []; + protected $vars = []; + protected $replace = []; + protected $contentType = 'text/html'; + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + */ + protected function output($data) + { + // 渲染模板输出 + return ViewTemplate::instance(Config::get('template'), Config::get('view_replace_str')) + ->fetch($data, $this->vars, $this->replace); + } + + /** + * 获取视图变量 + * @access public + * @param string $name 模板变量 + * @return mixed + */ + public function getVars($name = null) + { + if (is_null($name)) { + return $this->vars; + } else { + return isset($this->vars[$name]) ? $this->vars[$name] : null; + } + } + + /** + * 模板变量赋值 + * @access public + * @param mixed $name 变量名 + * @param mixed $value 变量值 + * @return $this + */ + public function assign($name, $value = '') + { + if (is_array($name)) { + $this->vars = array_merge($this->vars, $name); + return $this; + } else { + $this->vars[$name] = $value; + } + return $this; + } + + /** + * 视图内容替换 + * @access public + * @param string|array $content 被替换内容(支持批量替换) + * @param string $replace 替换内容 + * @return $this + */ + public function replace($content, $replace = '') + { + if (is_array($content)) { + $this->replace = array_merge($this->replace, $content); + } else { + $this->replace[$content] = $replace; + } + return $this; + } + +} diff --git a/thinkphp/library/think/response/Xml.php b/thinkphp/library/think/response/Xml.php new file mode 100644 index 0000000..3bdc052 --- /dev/null +++ b/thinkphp/library/think/response/Xml.php @@ -0,0 +1,102 @@ + +// +---------------------------------------------------------------------- + +namespace think\response; + +use think\Collection; +use think\Model; +use think\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'; + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + */ + protected function output($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编码 + * @param mixed $data 数据 + * @param string $root 根节点名 + * @param string $item 数字索引的子节点名 + * @param string $attr 根节点属性 + * @param string $id 数字索引子节点key转换的属性名 + * @param string $encoding 数据编码 + * @return string + */ + protected function xmlEncode($data, $root, $item, $attr, $id, $encoding) + { + 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 .= "<{$root}{$attr}>"; + $xml .= $this->dataToXml($data, $item, $id); + $xml .= ""; + return $xml; + } + + /** + * 数据XML编码 + * @param mixed $data 数据 + * @param string $item 数字索引时的节点名称 + * @param string $id 数字索引key转换为的属性名 + * @return string + */ + protected function dataToXml($data, $item, $id) + { + $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 .= ""; + } + return $xml; + } +} diff --git a/thinkphp/library/think/session/driver/Memcache.php b/thinkphp/library/think/session/driver/Memcache.php new file mode 100644 index 0000000..877d7bd --- /dev/null +++ b/thinkphp/library/think/session/driver/Memcache.php @@ -0,0 +1,118 @@ + +// +---------------------------------------------------------------------- + +namespace think\session\driver; + +use SessionHandler; +use think\Exception; + +class Memcache extends SessionHandler +{ + protected $handler = null; + protected $config = [ + 'host' => '127.0.0.1', // memcache主机 + 'port' => 11211, // memcache端口 + 'expire' => 3600, // session有效期 + 'timeout' => 0, // 连接超时时间(单位:毫秒) + 'persistent' => true, // 长连接 + 'session_name' => '', // memcache key前缀 + ]; + + public function __construct($config = []) + { + $this->config = array_merge($this->config, $config); + } + + /** + * 打开Session + * @access public + * @param string $savePath + * @param mixed $sessName + */ + public function open($savePath, $sessName) + { + // 检测php环境 + if (!extension_loaded('memcache')) { + throw new Exception('not support:memcache'); + } + $this->handler = new \Memcache; + // 支持集群 + $hosts = explode(',', $this->config['host']); + $ports = explode(',', $this->config['port']); + if (empty($ports[0])) { + $ports[0] = 11211; + } + // 建立连接 + foreach ((array) $hosts as $i => $host) { + $port = isset($ports[$i]) ? $ports[$i] : $ports[0]; + $this->config['timeout'] > 0 ? + $this->handler->addServer($host, $port, $this->config['persistent'], 1, $this->config['timeout']) : + $this->handler->addServer($host, $port, $this->config['persistent'], 1); + } + return true; + } + + /** + * 关闭Session + * @access public + */ + public function close() + { + $this->gc(ini_get('session.gc_maxlifetime')); + $this->handler->close(); + $this->handler = null; + return true; + } + + /** + * 读取Session + * @access public + * @param string $sessID + */ + public function read($sessID) + { + return (string) $this->handler->get($this->config['session_name'] . $sessID); + } + + /** + * 写入Session + * @access public + * @param string $sessID + * @param String $sessData + * @return bool + */ + public function write($sessID, $sessData) + { + return $this->handler->set($this->config['session_name'] . $sessID, $sessData, 0, $this->config['expire']); + } + + /** + * 删除Session + * @access public + * @param string $sessID + * @return bool + */ + public function destroy($sessID) + { + return $this->handler->delete($this->config['session_name'] . $sessID); + } + + /** + * Session 垃圾回收 + * @access public + * @param string $sessMaxLifeTime + * @return true + */ + public function gc($sessMaxLifeTime) + { + return true; + } +} diff --git a/thinkphp/library/think/session/driver/Memcached.php b/thinkphp/library/think/session/driver/Memcached.php new file mode 100644 index 0000000..2994a07 --- /dev/null +++ b/thinkphp/library/think/session/driver/Memcached.php @@ -0,0 +1,126 @@ + +// +---------------------------------------------------------------------- + +namespace think\session\driver; + +use SessionHandler; +use think\Exception; + +class Memcached extends SessionHandler +{ + protected $handler = null; + protected $config = [ + 'host' => '127.0.0.1', // memcache主机 + 'port' => 11211, // memcache端口 + 'expire' => 3600, // session有效期 + 'timeout' => 0, // 连接超时时间(单位:毫秒) + 'session_name' => '', // memcache key前缀 + 'username' => '', //账号 + 'password' => '', //密码 + ]; + + public function __construct($config = []) + { + $this->config = array_merge($this->config, $config); + } + + /** + * 打开Session + * @access public + * @param string $savePath + * @param mixed $sessName + */ + public function open($savePath, $sessName) + { + // 检测php环境 + if (!extension_loaded('memcached')) { + throw new Exception('not support:memcached'); + } + $this->handler = new \Memcached; + // 设置连接超时时间(单位:毫秒) + if ($this->config['timeout'] > 0) { + $this->handler->setOption(\Memcached::OPT_CONNECT_TIMEOUT, $this->config['timeout']); + } + // 支持集群 + $hosts = explode(',', $this->config['host']); + $ports = explode(',', $this->config['port']); + if (empty($ports[0])) { + $ports[0] = 11211; + } + // 建立连接 + $servers = []; + foreach ((array) $hosts as $i => $host) { + $servers[] = [$host, (isset($ports[$i]) ? $ports[$i] : $ports[0]), 1]; + } + $this->handler->addServers($servers); + if ('' != $this->config['username']) { + $this->handler->setOption(\Memcached::OPT_BINARY_PROTOCOL, true); + $this->handler->setSaslAuthData($this->config['username'], $this->config['password']); + } + return true; + } + + /** + * 关闭Session + * @access public + */ + public function close() + { + $this->gc(ini_get('session.gc_maxlifetime')); + $this->handler->quit(); + $this->handler = null; + return true; + } + + /** + * 读取Session + * @access public + * @param string $sessID + */ + public function read($sessID) + { + return (string) $this->handler->get($this->config['session_name'] . $sessID); + } + + /** + * 写入Session + * @access public + * @param string $sessID + * @param String $sessData + * @return bool + */ + public function write($sessID, $sessData) + { + return $this->handler->set($this->config['session_name'] . $sessID, $sessData, $this->config['expire']); + } + + /** + * 删除Session + * @access public + * @param string $sessID + * @return bool + */ + public function destroy($sessID) + { + return $this->handler->delete($this->config['session_name'] . $sessID); + } + + /** + * Session 垃圾回收 + * @access public + * @param string $sessMaxLifeTime + * @return true + */ + public function gc($sessMaxLifeTime) + { + return true; + } +} diff --git a/thinkphp/library/think/session/driver/Redis.php b/thinkphp/library/think/session/driver/Redis.php new file mode 100644 index 0000000..a426f0a --- /dev/null +++ b/thinkphp/library/think/session/driver/Redis.php @@ -0,0 +1,128 @@ + +// +---------------------------------------------------------------------- + +namespace think\session\driver; + +use SessionHandler; +use think\Exception; + +class Redis extends SessionHandler +{ + /** @var \Redis */ + protected $handler = null; + protected $config = [ + 'host' => '127.0.0.1', // redis主机 + 'port' => 6379, // redis端口 + 'password' => '', // 密码 + 'select' => 0, // 操作库 + 'expire' => 3600, // 有效期(秒) + 'timeout' => 0, // 超时时间(秒) + 'persistent' => true, // 是否长连接 + 'session_name' => '', // sessionkey前缀 + ]; + + public function __construct($config = []) + { + $this->config = array_merge($this->config, $config); + } + + /** + * 打开Session + * @access public + * @param string $savePath + * @param mixed $sessName + * @return bool + * @throws Exception + */ + public function open($savePath, $sessName) + { + // 检测php环境 + if (!extension_loaded('redis')) { + throw new Exception('not support:redis'); + } + $this->handler = new \Redis; + + // 建立连接 + $func = $this->config['persistent'] ? 'pconnect' : 'connect'; + $this->handler->$func($this->config['host'], $this->config['port'], $this->config['timeout']); + + if ('' != $this->config['password']) { + $this->handler->auth($this->config['password']); + } + + if (0 != $this->config['select']) { + $this->handler->select($this->config['select']); + } + + return true; + } + + /** + * 关闭Session + * @access public + */ + public function close() + { + $this->gc(ini_get('session.gc_maxlifetime')); + $this->handler->close(); + $this->handler = null; + return true; + } + + /** + * 读取Session + * @access public + * @param string $sessID + * @return string + */ + public function read($sessID) + { + return (string) $this->handler->get($this->config['session_name'] . $sessID); + } + + /** + * 写入Session + * @access public + * @param string $sessID + * @param String $sessData + * @return bool + */ + public function write($sessID, $sessData) + { + if ($this->config['expire'] > 0) { + return $this->handler->setex($this->config['session_name'] . $sessID, $this->config['expire'], $sessData); + } else { + return $this->handler->set($this->config['session_name'] . $sessID, $sessData); + } + } + + /** + * 删除Session + * @access public + * @param string $sessID + * @return bool + */ + public function destroy($sessID) + { + return $this->handler->del($this->config['session_name'] . $sessID) > 0; + } + + /** + * Session 垃圾回收 + * @access public + * @param string $sessMaxLifeTime + * @return bool + */ + public function gc($sessMaxLifeTime) + { + return true; + } +} diff --git a/thinkphp/library/think/template/TagLib.php b/thinkphp/library/think/template/TagLib.php new file mode 100644 index 0000000..c5b72f9 --- /dev/null +++ b/thinkphp/library/think/template/TagLib.php @@ -0,0 +1,334 @@ + +// +---------------------------------------------------------------------- + +namespace think\template; + +use think\Exception; + +/** + * ThinkPHP标签库TagLib解析基类 + * @category Think + * @package Think + * @subpackage Template + * @author liu21st + */ +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 \stdClass $template 模板引擎对象 + */ + public function __construct($template) + { + $this->tpl = $template; + } + + /** + * 按签标库替换页面中的标签 + * @access public + * @param string $content 模板内容 + * @param string $lib 标签库名 + * @return void + */ + public function parseTag(&$content, $lib = '') + { + $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 = ''; + 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); + } + return; + } + + /** + * 按标签生成正则 + * @access private + * @param array|string $tags 标签名 + * @param boolean $close 是否为闭合标签 + * @return string + */ + public function getRegex($tags, $close) + { + $begin = $this->tpl->config('taglib_begin'); + $end = $this->tpl->config('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($str, $name, $alias = '') + { + $regex = '/\s+(?>(?P[\w-]+)\s*)=(?>\s*)([\"\'])(?P(?:(?!\\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->config('taglib_begin_origin') . $name); + $_taglibs[$name][1] = strlen($this->tpl->config('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($condition) + { + if (strpos($condition, ':')) { + $condition = ' ' . substr(strstr($condition, ':'), 1); + } + $condition = str_ireplace(array_keys($this->comparison), array_values($this->comparison), $condition); + $this->tpl->parseVar($condition); + // $this->tpl->parseVarFunction($condition); // XXX: 此句能解析表达式中用|分隔的函数,但表达式中如果有|、||这样的逻辑运算就产生了歧异 + return $condition; + } + + /** + * 自动识别构建变量 + * @access public + * @param string $name 变量描述 + * @return string + */ + public function autoBuildVar(&$name) + { + $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); + return $name; + } + + /** + * 获取标签列表 + * @access public + * @return array + */ + // 获取标签定义 + public function getTags() + { + return $this->tags; + } +} diff --git a/thinkphp/library/think/template/driver/File.php b/thinkphp/library/think/template/driver/File.php new file mode 100644 index 0000000..a9a86bf --- /dev/null +++ b/thinkphp/library/think/template/driver/File.php @@ -0,0 +1,74 @@ + +// +---------------------------------------------------------------------- + +namespace think\template\driver; + +use think\Exception; + +class File +{ + protected $cacheFile; + + /** + * 写入编译缓存 + * @param string $cacheFile 缓存的文件名 + * @param string $content 缓存的内容 + * @return void|array + */ + public function write($cacheFile, $content) + { + // 检测模板目录 + $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); + } + } + + /** + * 读取编译编译 + * @param string $cacheFile 缓存的文件名 + * @param array $vars 变量数组 + * @return void + */ + public function read($cacheFile, $vars = []) + { + $this->cacheFile = $cacheFile; + if (!empty($vars) && is_array($vars)) { + // 模板阵列变量分解成为独立变量 + extract($vars, EXTR_OVERWRITE); + } + //载入模版缓存文件 + include $this->cacheFile; + } + + /** + * 检查编译缓存是否有效 + * @param string $cacheFile 缓存的文件名 + * @param int $cacheTime 缓存时间 + * @return boolean + */ + public function check($cacheFile, $cacheTime) + { + // 缓存文件不存在, 直接返回false + if (!file_exists($cacheFile)) { + return false; + } + if (0 != $cacheTime && $_SERVER['REQUEST_TIME'] > filemtime($cacheFile) + $cacheTime) { + // 缓存是否在有效期 + return false; + } + return true; + } +} diff --git a/thinkphp/library/think/template/taglib/Cx.php b/thinkphp/library/think/template/taglib/Cx.php new file mode 100644 index 0000000..31e0698 --- /dev/null +++ b/thinkphp/library/think/template/taglib/Cx.php @@ -0,0 +1,673 @@ + +// +---------------------------------------------------------------------- + +namespace think\template\taglib; + +use think\template\TagLib; + +/** + * CX标签库解析类 + * @category Think + * @package Think + * @subpackage Driver.Taglib + * @author liu21st + */ +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($tag, $content) + { + $parseStr = ''; + return $parseStr; + } + + /** + * volist标签解析 循环输出数据集 + * 格式: + * {volist name="userList" id="user" empty=""} + * {user.username} + * {user.email} + * {/volist} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string|void + */ + public function tagVolist($tag, $content) + { + $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'; + // 允许使用函数设定数据集 {$vo.name} + $parseStr = '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 .= ''; + + if (!empty($parseStr)) { + return $parseStr; + } + return; + } + + /** + * 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|void + */ + public function tagForeach($tag, $content) + { + // 直接使用表达式 + if (!empty($tag['expression'])) { + $expression = ltrim(rtrim($tag['expression'], ')'), '('); + $expression = $this->autoBuildVar($expression); + $parseStr = ''; + $parseStr .= $content; + $parseStr .= ''; + 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 = '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 .= ''; + + if (!empty($parseStr)) { + return $parseStr; + } + return; + } + + /** + * 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($tag, $content) + { + $condition = !empty($tag['expression']) ? $tag['expression'] : $tag['condition']; + $condition = $this->parseCondition($condition); + $parseStr = '' . $content . ''; + return $parseStr; + } + + /** + * elseif标签解析 + * 格式:见if标签 + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagElseif($tag, $content) + { + $condition = !empty($tag['expression']) ? $tag['expression'] : $tag['condition']; + $condition = $this->parseCondition($condition); + $parseStr = ''; + return $parseStr; + } + + /** + * else标签解析 + * 格式:见if标签 + * @access public + * @param array $tag 标签属性 + * @return string + */ + public function tagElse($tag) + { + $parseStr = ''; + 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($tag, $content) + { + $name = !empty($tag['expression']) ? $tag['expression'] : $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = '' . $content . ''; + return $parseStr; + } + + /** + * case标签解析 需要配合switch才有效 + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagCase($tag, $content) + { + $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 = '' . $content; + $isBreak = isset($tag['break']) ? $tag['break'] : ''; + if ('' == $isBreak || $isBreak) { + $parseStr .= ''; + } + return $parseStr; + } + + /** + * default标签解析 需要配合switch才有效 + * 使用: {default /}ddfdf + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagDefault($tag) + { + $parseStr = ''; + 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($tag, $content) + { + $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 = '' . $content . ''; + 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($tag, $content) + { + $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 = '= $_RANGE_VAR_[0] && ' . $name . '<= $_RANGE_VAR_[1]):?>' . $content . ''; + } elseif ('notbetween' == $type) { + $parseStr = '$_RANGE_VAR_[1]):?>' . $content . ''; + } else { + $fun = ('in' == $type) ? 'in_array' : '!in_array'; + $parseStr = '' . $content . ''; + } + return $parseStr; + } + + /** + * present标签解析 + * 如果某个变量已经设置 则输出内容 + * 格式: {present name="" }content{/present} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagPresent($tag, $content) + { + $name = $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = '' . $content . ''; + return $parseStr; + } + + /** + * notpresent标签解析 + * 如果某个变量没有设置,则输出内容 + * 格式: {notpresent name="" }content{/notpresent} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagNotpresent($tag, $content) + { + $name = $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = '' . $content . ''; + return $parseStr; + } + + /** + * empty标签解析 + * 如果某个变量为empty 则输出内容 + * 格式: {empty name="" }content{/empty} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagEmpty($tag, $content) + { + $name = $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = 'isEmpty())): ?>' . $content . ''; + return $parseStr; + } + + /** + * notempty标签解析 + * 如果某个变量不为empty 则输出内容 + * 格式: {notempty name="" }content{/notempty} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagNotempty($tag, $content) + { + $name = $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = 'isEmpty()))): ?>' . $content . ''; + return $parseStr; + } + + /** + * 判断是否已经定义了该常量 + * {defined name='TXT'}已定义{/defined} + * @param array $tag + * @param string $content + * @return string + */ + public function tagDefined($tag, $content) + { + $name = $tag['name']; + $parseStr = '' . $content . ''; + return $parseStr; + } + + /** + * 判断是否没有定义了该常量 + * {notdefined name='TXT'}已定义{/notdefined} + * @param array $tag + * @param string $content + * @return string + */ + public function tagNotdefined($tag, $content) + { + $name = $tag['name']; + $parseStr = '' . $content . ''; + 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($tag, $content) + { + $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 .= ''; + $endStr = ''; + } + + // 文件方式导入 + $array = explode(',', $file); + foreach ($array as $val) { + $type = strtolower(substr(strrchr($val, '.'), 1)); + switch ($type) { + case 'js': + $parseStr .= ''; + break; + case 'css': + $parseStr .= ''; + break; + case 'php': + $parseStr .= ''; + break; + } + } + return $parseStr . $endStr; + } + + /** + * assign标签解析 + * 在模板中给某个变量赋值 支持变量赋值 + * 格式: {assign name="" value="" /} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagAssign($tag, $content) + { + $name = $this->autoBuildVar($tag['name']); + $flag = substr($tag['value'], 0, 1); + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($tag['value']); + } else { + $value = '\'' . $tag['value'] . '\''; + } + $parseStr = ''; + return $parseStr; + } + + /** + * define标签解析 + * 在模板中定义常量 支持变量赋值 + * 格式: {define name="" value="" /} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagDefine($tag, $content) + { + $name = '\'' . $tag['name'] . '\''; + $flag = substr($tag['value'], 0, 1); + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($tag['value']); + } else { + $value = '\'' . $tag['value'] . '\''; + } + $parseStr = ''; + return $parseStr; + } + + /** + * for标签解析 + * 格式: + * {for start="" end="" comparison="" step="" name=""} + * content + * {/for} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagFor($tag, $content) + { + //设置默认值 + $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 = 'parseCondition('$' . $name . ' ' . $comparison . ' $__FOR_END_' . $rand . '__') . ';$' . $name . '+=' . $step . '){ ?>'; + $parseStr .= $content; + $parseStr .= ''; + 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($tag, $content) + { + $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 ''; + } + + /** + * 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($tag, $content) + { + $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 = '' . $content . '' : '?>'; + return $parseStr; + } +} diff --git a/thinkphp/library/think/view/driver/Php.php b/thinkphp/library/think/view/driver/Php.php new file mode 100644 index 0000000..f594a43 --- /dev/null +++ b/thinkphp/library/think/view/driver/Php.php @@ -0,0 +1,160 @@ + +// +---------------------------------------------------------------------- + +namespace think\view\driver; + +use think\App; +use think\exception\TemplateNotFoundException; +use think\Loader; +use think\Log; +use think\Request; + +class Php +{ + // 模板引擎参数 + protected $config = [ + // 视图基础目录(集中式) + 'view_base' => '', + // 模板起始路径 + 'view_path' => '', + // 模板文件后缀 + 'view_suffix' => 'php', + // 模板文件名分隔符 + 'view_depr' => DS, + // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 + 'auto_rule' => 1, + ]; + protected $template; + protected $content; + + public function __construct($config = []) + { + $this->config = array_merge($this->config, $config); + } + + /** + * 检测是否存在模板文件 + * @access public + * @param string $template 模板文件或者模板规则 + * @return bool + */ + public function exists($template) + { + 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($template, $data = []) + { + if ('' == pathinfo($template, PATHINFO_EXTENSION)) { + // 获取模板文件名 + $template = $this->parseTemplate($template); + } + // 模板不存在 抛出异常 + if (!is_file($template)) { + throw new TemplateNotFoundException('template not exists:' . $template, $template); + } + $this->template = $template; + // 记录视图信息 + App::$debug && Log::record('[ VIEW ] ' . $template . ' [ ' . var_export(array_keys($data), true) . ' ]', 'info'); + + extract($data, EXTR_OVERWRITE); + include $this->template; + } + + /** + * 渲染模板内容 + * @access public + * @param string $content 模板内容 + * @param array $data 模板变量 + * @return void + */ + public function display($content, $data = []) + { + $this->content = $content; + + extract($data, EXTR_OVERWRITE); + eval('?>' . $this->content); + } + + /** + * 自动定位模板文件 + * @access private + * @param string $template 模板文件规则 + * @return string + */ + private function parseTemplate($template) + { + if (empty($this->config['view_path'])) { + $this->config['view_path'] = App::$modulePath . 'view' . DS; + } + + $request = Request::instance(); + // 获取视图根目录 + if (strpos($template, '@')) { + // 跨模块调用 + list($module, $template) = explode('@', $template); + } + if ($this->config['view_base']) { + // 基础视图目录 + $module = isset($module) ? $module : $request->module(); + $path = $this->config['view_base'] . ($module ? $module . DS : ''); + } else { + $path = isset($module) ? APP_PATH . $module . DS . 'view' . DS : $this->config['view_path']; + } + + $depr = $this->config['view_depr']; + if (0 !== strpos($template, '/')) { + $template = str_replace(['/', ':'], $depr, $template); + $controller = Loader::parseName($request->controller()); + if ($controller) { + if ('' == $template) { + // 如果模板文件名为空 按照默认规则定位 + $template = str_replace('.', DS, $controller) . $depr . (1 == $this->config['auto_rule'] ? Loader::parseName($request->action(true)) : $request->action()); + } elseif (false === strpos($template, $depr)) { + $template = str_replace('.', DS, $controller) . $depr . $template; + } + } + } else { + $template = str_replace(['/', ':'], $depr, substr($template, 1)); + } + return $path . ltrim($template, '/') . '.' . ltrim($this->config['view_suffix'], '.'); + } + + /** + * 配置模板引擎 + * @access private + * @param string|array $name 参数名 + * @param mixed $value 参数值 + * @return void + */ + public function config($name, $value = null) + { + if (is_array($name)) { + $this->config = array_merge($this->config, $name); + } elseif (is_null($value)) { + return isset($this->config[$name]) ? $this->config[$name] : null; + } else { + $this->config[$name] = $value; + } + } + +} diff --git a/thinkphp/library/think/view/driver/Think.php b/thinkphp/library/think/view/driver/Think.php new file mode 100644 index 0000000..a314ad6 --- /dev/null +++ b/thinkphp/library/think/view/driver/Think.php @@ -0,0 +1,167 @@ + +// +---------------------------------------------------------------------- + +namespace think\view\driver; + +use think\App; +use think\exception\TemplateNotFoundException; +use think\Loader; +use think\Log; +use think\Request; +use think\Template; + +class Think +{ + // 模板引擎实例 + private $template; + // 模板引擎参数 + protected $config = [ + // 视图基础目录(集中式) + 'view_base' => '', + // 模板起始路径 + 'view_path' => '', + // 模板文件后缀 + 'view_suffix' => 'html', + // 模板文件名分隔符 + 'view_depr' => DS, + // 是否开启模板编译缓存,设为false则每次都会重新编译 + 'tpl_cache' => true, + // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 + 'auto_rule' => 1, + ]; + + public function __construct($config = []) + { + $this->config = array_merge($this->config, $config); + if (empty($this->config['view_path'])) { + $this->config['view_path'] = App::$modulePath . 'view' . DS; + } + + $this->template = new Template($this->config); + } + + /** + * 检测是否存在模板文件 + * @access public + * @param string $template 模板文件或者模板规则 + * @return bool + */ + public function exists($template) + { + if ('' == pathinfo($template, PATHINFO_EXTENSION)) { + // 获取模板文件名 + $template = $this->parseTemplate($template); + } + return is_file($template); + } + + /** + * 渲染模板文件 + * @access public + * @param string $template 模板文件 + * @param array $data 模板变量 + * @param array $config 模板参数 + * @return void + */ + public function fetch($template, $data = [], $config = []) + { + if ('' == pathinfo($template, PATHINFO_EXTENSION)) { + // 获取模板文件名 + $template = $this->parseTemplate($template); + } + // 模板不存在 抛出异常 + if (!is_file($template)) { + throw new TemplateNotFoundException('template not exists:' . $template, $template); + } + // 记录视图信息 + App::$debug && Log::record('[ VIEW ] ' . $template . ' [ ' . var_export(array_keys($data), true) . ' ]', 'info'); + $this->template->fetch($template, $data, $config); + } + + /** + * 渲染模板内容 + * @access public + * @param string $template 模板内容 + * @param array $data 模板变量 + * @param array $config 模板参数 + * @return void + */ + public function display($template, $data = [], $config = []) + { + $this->template->display($template, $data, $config); + } + + /** + * 自动定位模板文件 + * @access private + * @param string $template 模板文件规则 + * @return string + */ + private function parseTemplate($template) + { + // 分析模板文件规则 + $request = Request::instance(); + // 获取视图根目录 + if (strpos($template, '@')) { + // 跨模块调用 + list($module, $template) = explode('@', $template); + } + if ($this->config['view_base']) { + // 基础视图目录 + $module = isset($module) ? $module : $request->module(); + $path = $this->config['view_base'] . ($module ? $module . DS : ''); + } else { + $path = isset($module) ? APP_PATH . $module . DS . 'view' . DS : $this->config['view_path']; + } + + $depr = $this->config['view_depr']; + if (0 !== strpos($template, '/')) { + $template = str_replace(['/', ':'], $depr, $template); + $controller = Loader::parseName($request->controller()); + if ($controller) { + if ('' == $template) { + // 如果模板文件名为空 按照默认规则定位 + $template = str_replace('.', DS, $controller) . $depr . (1 == $this->config['auto_rule'] ? Loader::parseName($request->action(true)) : $request->action()); + } elseif (false === strpos($template, $depr)) { + $template = str_replace('.', DS, $controller) . $depr . $template; + } + } + } else { + $template = str_replace(['/', ':'], $depr, substr($template, 1)); + } + return $path . ltrim($template, '/') . '.' . ltrim($this->config['view_suffix'], '.'); + } + + /** + * 配置或者获取模板引擎参数 + * @access private + * @param string|array $name 参数名 + * @param mixed $value 参数值 + * @return mixed + */ + public function config($name, $value = null) + { + if (is_array($name)) { + $this->template->config($name); + $this->config = array_merge($this->config, $name); + } elseif (is_null($value)) { + return $this->template->config($name); + } else { + $this->template->$name = $value; + $this->config[$name] = $value; + } + } + + public function __call($method, $params) + { + return call_user_func_array([$this->template, $method], $params); + } +} diff --git a/thinkphp/library/traits/controller/Jump.php b/thinkphp/library/traits/controller/Jump.php new file mode 100644 index 0000000..6a57224 --- /dev/null +++ b/thinkphp/library/traits/controller/Jump.php @@ -0,0 +1,167 @@ +error(); + * $this->redirect(); + * } + * } + */ +namespace traits\controller; + +use think\Config; +use think\exception\HttpResponseException; +use think\Request; +use think\Response; +use think\response\Redirect; +use think\Url; +use think\View as ViewTemplate; + +trait Jump +{ + /** + * 操作成功跳转的快捷方法 + * @access protected + * @param mixed $msg 提示信息 + * @param string $url 跳转的 URL 地址 + * @param mixed $data 返回的数据 + * @param int $wait 跳转等待时间 + * @param array $header 发送的 Header 信息 + * @return void + * @throws HttpResponseException + */ + protected function success($msg = '', $url = null, $data = '', $wait = 3, array $header = []) + { + if (is_null($url) && !is_null(Request::instance()->server('HTTP_REFERER'))) { + $url = Request::instance()->server('HTTP_REFERER'); + } elseif ('' !== $url && !strpos($url, '://') && 0 !== strpos($url, '/')) { + $url = Url::build($url); + } + + $type = $this->getResponseType(); + $result = [ + 'code' => 1, + 'msg' => $msg, + 'data' => $data, + 'url' => $url, + 'wait' => $wait, + ]; + + if ('html' == strtolower($type)) { + $template = Config::get('template'); + $view = Config::get('view_replace_str'); + + $result = ViewTemplate::instance($template, $view) + ->fetch(Config::get('dispatch_success_tmpl'), $result); + } + + $response = Response::create($result, $type)->header($header); + + throw new HttpResponseException($response); + } + + /** + * 操作错误跳转的快捷方法 + * @access protected + * @param mixed $msg 提示信息 + * @param string $url 跳转的 URL 地址 + * @param mixed $data 返回的数据 + * @param int $wait 跳转等待时间 + * @param array $header 发送的 Header 信息 + * @return void + * @throws HttpResponseException + */ + protected function error($msg = '', $url = null, $data = '', $wait = 3, array $header = []) + { + if (is_null($url)) { + $url = Request::instance()->isAjax() ? '' : 'javascript:history.back(-1);'; + } elseif ('' !== $url && !strpos($url, '://') && 0 !== strpos($url, '/')) { + $url = Url::build($url); + } + + $type = $this->getResponseType(); + $result = [ + 'code' => 0, + 'msg' => $msg, + 'data' => $data, + 'url' => $url, + 'wait' => $wait, + ]; + + if ('html' == strtolower($type)) { + $template = Config::get('template'); + $view = Config::get('view_replace_str'); + + $result = ViewTemplate::instance($template, $view) + ->fetch(Config::get('dispatch_error_tmpl'), $result); + } + + $response = Response::create($result, $type)->header($header); + + throw new HttpResponseException($response); + } + + /** + * 返回封装后的 API 数据到客户端 + * @access protected + * @param mixed $data 要返回的数据 + * @param int $code 返回的 code + * @param mixed $msg 提示信息 + * @param string $type 返回数据格式 + * @param array $header 发送的 Header 信息 + * @return void + * @throws HttpResponseException + */ + protected function result($data, $code = 0, $msg = '', $type = '', array $header = []) + { + $result = [ + 'code' => $code, + 'msg' => $msg, + 'time' => Request::instance()->server('REQUEST_TIME'), + 'data' => $data, + ]; + $type = $type ?: $this->getResponseType(); + $response = Response::create($result, $type)->header($header); + + throw new HttpResponseException($response); + } + + /** + * URL 重定向 + * @access protected + * @param string $url 跳转的 URL 表达式 + * @param array|int $params 其它 URL 参数 + * @param int $code http code + * @param array $with 隐式传参 + * @return void + * @throws HttpResponseException + */ + protected function redirect($url, $params = [], $code = 302, $with = []) + { + if (is_integer($params)) { + $code = $params; + $params = []; + } + + $response = new Redirect($url); + $response->code($code)->params($params)->with($with); + + throw new HttpResponseException($response); + } + + /** + * 获取当前的 response 输出类型 + * @access protected + * @return string + */ + protected function getResponseType() + { + return Request::instance()->isAjax() + ? Config::get('default_ajax_return') + : Config::get('default_return_type'); + } +} diff --git a/thinkphp/library/traits/model/SoftDelete.php b/thinkphp/library/traits/model/SoftDelete.php new file mode 100644 index 0000000..70f31ba --- /dev/null +++ b/thinkphp/library/traits/model/SoftDelete.php @@ -0,0 +1,200 @@ +getDeleteTimeField(); + + if ($field && !empty($this->data[$field])) { + return true; + } + return false; + } + + /** + * 查询包含软删除的数据 + * @access public + * @return Query + */ + public static function withTrashed() + { + return (new static )->getQuery(); + } + + /** + * 只查询软删除数据 + * @access public + * @return Query + */ + public static function onlyTrashed() + { + $model = new static(); + $field = $model->getDeleteTimeField(true); + + if ($field) { + return $model->getQuery()->useSoftDelete($field, ['not null', '']); + } else { + return $model->getQuery(); + } + } + + /** + * 删除当前的记录 + * @access public + * @param bool $force 是否强制删除 + * @return integer + */ + public function delete($force = false) + { + if (false === $this->trigger('before_delete', $this)) { + return false; + } + + $name = $this->getDeleteTimeField(); + if ($name && !$force) { + // 软删除 + $this->data[$name] = $this->autoWriteTimestamp($name); + $result = $this->isUpdate()->save(); + } else { + // 强制删除当前模型数据 + $result = $this->getQuery()->where($this->getWhere())->delete(); + } + + // 关联删除 + if (!empty($this->relationWrite)) { + foreach ($this->relationWrite as $key => $name) { + $name = is_numeric($key) ? $name : $key; + $result = $this->getRelation($name); + if ($result instanceof Model) { + $result->delete(); + } elseif ($result instanceof Collection || is_array($result)) { + foreach ($result as $model) { + $model->delete(); + } + } + } + } + + $this->trigger('after_delete', $this); + + // 清空原始数据 + $this->origin = []; + + return $result; + } + + /** + * 删除记录 + * @access public + * @param mixed $data 主键列表(支持闭包查询条件) + * @param bool $force 是否强制删除 + * @return integer 成功删除的记录数 + */ + public static function destroy($data, $force = false) + { + if (is_null($data)) { + return 0; + } + + // 包含软删除数据 + $query = (new static())->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; + } + + $count = 0; + if ($resultSet = $query->select($data)) { + foreach ($resultSet as $data) { + $result = $data->delete($force); + $count += $result; + } + } + + return $count; + } + + /** + * 恢复被软删除的记录 + * @access public + * @param array $where 更新条件 + * @return integer + */ + public function restore($where = []) + { + if (empty($where)) { + $pk = $this->getPk(); + $where[$pk] = $this->getData($pk); + } + + $name = $this->getDeleteTimeField(); + + if ($name) { + // 恢复删除 + return $this->getQuery() + ->useSoftDelete($name, ['not null', '']) + ->where($where) + ->update([$name => null]); + } else { + return 0; + } + } + + /** + * 查询默认不包含软删除数据 + * @access protected + * @param Query $query 查询对象 + * @return Query + */ + protected function base($query) + { + $field = $this->getDeleteTimeField(true); + return $field ? $query->useSoftDelete($field) : $query; + } + + /** + * 获取软删除字段 + * @access public + * @param bool $read 是否查询操作(写操作的时候会自动去掉表别名) + * @return string + */ + protected function getDeleteTimeField($read = false) + { + $field = property_exists($this, 'deleteTime') && isset($this->deleteTime) ? + $this->deleteTime : + 'delete_time'; + + if (false === $field) { + return false; + } + + if (!strpos($field, '.')) { + $field = '__TABLE__.' . $field; + } + + if (!$read && strpos($field, '.')) { + $array = explode('.', $field); + $field = array_pop($array); + } + + return $field; + } +} diff --git a/thinkphp/library/traits/think/Instance.php b/thinkphp/library/traits/think/Instance.php new file mode 100644 index 0000000..428c8fd --- /dev/null +++ b/thinkphp/library/traits/think/Instance.php @@ -0,0 +1,54 @@ + +// +---------------------------------------------------------------------- + +namespace traits\think; + +use think\Exception; + +trait Instance +{ + /** + * @var null|static 实例对象 + */ + protected static $instance = null; + + /** + * 获取示例 + * @param array $options 实例配置 + * @return static + */ + public static function instance($options = []) + { + if (is_null(self::$instance)) self::$instance = new self($options); + + return self::$instance; + } + + /** + * 静态调用 + * @param string $method 调用方法 + * @param array $params 调用参数 + * @return mixed + * @throws Exception + */ + public static function __callStatic($method, array $params) + { + if (is_null(self::$instance)) self::$instance = new self(); + + $call = substr($method, 1); + + if (0 !== strpos($method, '_') || !is_callable([self::$instance, $call])) { + throw new Exception("method not exists:" . $method); + } + + return call_user_func_array([self::$instance, $call], $params); + } +} diff --git a/thinkphp/logo.png b/thinkphp/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..25fd0593688de5c9f4cd321da1a72ab9566fe331 GIT binary patch literal 6995 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 literal 0 HcmV?d00001 diff --git a/thinkphp/phpunit.xml b/thinkphp/phpunit.xml new file mode 100644 index 0000000..7c6ef03 --- /dev/null +++ b/thinkphp/phpunit.xml @@ -0,0 +1,35 @@ + + + + + ./tests/thinkphp/ + + + + + + + + ./ + + tests + vendor + + + + + + + + + + diff --git a/thinkphp/start.php b/thinkphp/start.php new file mode 100644 index 0000000..adb1bc6 --- /dev/null +++ b/thinkphp/start.php @@ -0,0 +1,19 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +// ThinkPHP 引导文件 +// 1. 加载基础文件 +require __DIR__ . '/base.php'; + +// 2. 执行应用 +App::run()->send(); diff --git a/thinkphp/tests/.gitignore b/thinkphp/tests/.gitignore new file mode 100644 index 0000000..a0306ec --- /dev/null +++ b/thinkphp/tests/.gitignore @@ -0,0 +1,4 @@ +/runtime/ +/application/common.php +/application/demo/ +/application/runtime/ \ No newline at end of file diff --git a/thinkphp/tests/README.md b/thinkphp/tests/README.md new file mode 100644 index 0000000..0c1d086 --- /dev/null +++ b/thinkphp/tests/README.md @@ -0,0 +1,132 @@ +## 测试目录结构 + +测试文件主要在 tests 文件下面,主要有以下几个文件夹 + +- conf 测试环境配置文件。 +- script 测试环境配置脚本。 +- thinkphp 测试用例和相关文件,与项目文件夹结构一致。 +- mock.php 测试入口文件。 + +## 主要测试流程 + +thinkphp5 的测试的主要流程是跟 thinkphp 的系统流程是相似的,大体的流程为: + +1. 引用 mock.php 文件加载框架 + +2. 根据文件目录,添加测试文件 + +3. 执行单元测试,输出结果 + +## 测试举例 + +例如测试 thinkphp 里的 apc 缓存,将分为以下几个过程: + +1. 创建 apcTest.php 文件 + +该文件应与 apc.php 目录路径 `thinkphp/library/think/cache/driver` 一致,命名空间与目录所在一致,并引用 `PHPUnit_Framework_TestCase`。 + + ```php + markTestSkipped('apc扩展不可用!'); + }; + ``` + + - 编写测试用例 + + *具体写法参照 [PHPUnit 官方文档](https://phpunit.de/manual/4.8/zh_cn/index.html)* + + ```php + public function testGet() + { + App::run(); + $this->assertInstanceOf( + '\think\cache\driver\Apc', + Cache::connect(['type' => 'apc', 'expire' => 1]) + ); + $this->assertTrue(Cache::set('key', 'value')); + $this->assertEquals('value', Cache::get('key')); + $this->assertTrue(Cache::rm('key')); + $this->assertFalse(Cache::get('key')); + $this->assertTrue(Cache::clear('key')); + Config::reset(); + } + ``` + +3. 执行单元测试命令 + + 在项目根目录执行 + + ```bash + $ phpunit + ``` + + 若想看到所有结果,请添加-v参数 + + ```bash + $ phpunit -v + ``` + +4. 输出结果 + +## 相关文档 + +[各个部分单元测试说明](http://www.kancloud.cn/brother_simon/tp5_test/96971 "各部分单元测试说明") + +## 大家一起来 + +单元测试的内容会跟框架同步,测试内容方方面面,是一个相对复杂的模块,同时也是一个值得重视的部分。希望大家能够多多提出意见,多多参与。如果你有任何问题或想法,可以随时提 issue,我们期待着收到听大家的质疑和讨论。 + +## 任务进度 + +单元测试任务进度,请大家认领模块 + +|模块|认领人|进度| +|---|---|---| +|Base||| +|App|Haotong Lin|√| +|Build|刘志淳|| +|Config|Haotong Lin|√| +|Cache||| +|Controller|Haotong Lin|√| +|Cookie|Haotong Lin|√| +|Db||| +|Debug|大漠|√| +|Error|大漠|| +|Exception|Haotong Lin|√| +|Hook|流年|√| +|Input|Haotong Lin|√| +|Lang|流年|√| +|Loader|流年|| +|Log||| +|Model||| +|Response|大漠|√| +|Route|流年|| +|Session|大漠|√| +|Template|oldrind|| +|Url|流年|| +|View|mahuan|| diff --git a/thinkphp/tests/application/config.php b/thinkphp/tests/application/config.php new file mode 100644 index 0000000..d94a864 --- /dev/null +++ b/thinkphp/tests/application/config.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- +// $Id$ + +return [ + 'url_route_on' => true, + 'log' => [ + 'type' => 'file', // 支持 socket trace file + ], + 'view' => [ + // 模板引擎 + 'engine_type' => 'think', + // 模板引擎配置 + 'engine_config' => [ + // 模板路径 + 'view_path' => '', + // 模板后缀 + 'view_suffix' => '.html', + // 模板文件名分隔符 + 'view_depr' => DS, + ], + // 输出字符串替换 + 'parse_str' => [], + ], +]; diff --git a/thinkphp/tests/application/database.php b/thinkphp/tests/application/database.php new file mode 100644 index 0000000..24434ef --- /dev/null +++ b/thinkphp/tests/application/database.php @@ -0,0 +1,44 @@ + +// +---------------------------------------------------------------------- +// $Id$ + +return [ + // 数据库类型 + 'type' => 'mysql', + // 数据库连接DSN配置 + 'dsn' => '', + // 服务器地址 + 'hostname' => '127.0.0.1', + // 数据库名 + 'database' => '', + // 数据库用户名 + 'username' => 'root', + // 数据库密码 + 'password' => '', + // 数据库连接端口 + 'hostport' => '', + // 数据库连接参数 + 'params' => [], + // 数据库编码默认采用utf8 + 'charset' => 'utf8', + // 数据库表前缀 + 'prefix' => '', + // 数据库调试模式 + 'debug' => true, + // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) + 'deploy' => 0, + // 数据库读写是否分离 主从式有效 + 'rw_separate' => false, + // 读写分离后 主服务器数量 + 'master_num' => 1, + // 指定从服务器序号 + 'slave_no' => '', +]; diff --git a/thinkphp/tests/application/index/controller/Index.php b/thinkphp/tests/application/index/controller/Index.php new file mode 100644 index 0000000..803fe95 --- /dev/null +++ b/thinkphp/tests/application/index/controller/Index.php @@ -0,0 +1,10 @@ +*{ padding: 0; margin: 0; } div{ padding: 4px 48px;} a{color:#2E5CD5;cursor: pointer;text-decoration: none} a:hover{text-decoration:underline; } body{ background: #fff; font-family: "Century Gothic","Microsoft yahei"; color: #333;font-size:18px} h1{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.6em; font-size: 42px }

    :)

    ThinkPHP V5
    十年磨一剑 - 为API开发设计的高性能框架

    [ V5.0 版本由 七牛云 独家赞助发布 ]
    '; + } +} diff --git a/thinkphp/tests/application/route.php b/thinkphp/tests/application/route.php new file mode 100644 index 0000000..45bcf79 --- /dev/null +++ b/thinkphp/tests/application/route.php @@ -0,0 +1,22 @@ + +// +---------------------------------------------------------------------- +// $Id$ + +return [ + '__pattern__' => [ + 'name' => '\w+', + ], + '[hello]' => [ + ':id' => ['index/hello', ['method' => 'get'], ['id' => '\d+']], + ':name' => ['index/hello', ['method' => 'post']], + ], + +]; diff --git a/thinkphp/tests/application/views/display.html b/thinkphp/tests/application/views/display.html new file mode 100644 index 0000000..e99d302 --- /dev/null +++ b/thinkphp/tests/application/views/display.html @@ -0,0 +1 @@ +{$name??'default'} \ No newline at end of file diff --git a/thinkphp/tests/application/views/display.phtml b/thinkphp/tests/application/views/display.phtml new file mode 100644 index 0000000..e99d302 --- /dev/null +++ b/thinkphp/tests/application/views/display.phtml @@ -0,0 +1 @@ +{$name??'default'} \ No newline at end of file diff --git a/thinkphp/tests/application/views/extend.html b/thinkphp/tests/application/views/extend.html new file mode 100644 index 0000000..922192d --- /dev/null +++ b/thinkphp/tests/application/views/extend.html @@ -0,0 +1,2 @@ +{extend name="extend2" /} +{block name="head"}header{/block} \ No newline at end of file diff --git a/thinkphp/tests/application/views/extend2.html b/thinkphp/tests/application/views/extend2.html new file mode 100644 index 0000000..eb22e1d --- /dev/null +++ b/thinkphp/tests/application/views/extend2.html @@ -0,0 +1,17 @@ +{layout name="layout2" replace="[__REPLACE__]" /} +{block name="head"}{/block} +
    + {include file="include" name="info" value="$info.value" /} +{block name="main"} +{block name="side"} + side +{/block} +{block name="mainbody"} + +{/block} +{/block} +{literal} + {$name} +{/literal} + +
    \ No newline at end of file diff --git a/thinkphp/tests/application/views/include.html b/thinkphp/tests/application/views/include.html new file mode 100644 index 0000000..e01ae9e --- /dev/null +++ b/thinkphp/tests/application/views/include.html @@ -0,0 +1,2 @@ + +{include file="include2" /} \ No newline at end of file diff --git a/thinkphp/tests/application/views/include2.html b/thinkphp/tests/application/views/include2.html new file mode 100644 index 0000000..24bdacb --- /dev/null +++ b/thinkphp/tests/application/views/include2.html @@ -0,0 +1 @@ +{$info.value}: \ No newline at end of file diff --git a/thinkphp/tests/application/views/layout.html b/thinkphp/tests/application/views/layout.html new file mode 100644 index 0000000..be8d4a0 --- /dev/null +++ b/thinkphp/tests/application/views/layout.html @@ -0,0 +1,2 @@ +
    {__CONTENT__} +
    \ No newline at end of file diff --git a/thinkphp/tests/application/views/layout2.html b/thinkphp/tests/application/views/layout2.html new file mode 100644 index 0000000..a5b7572 --- /dev/null +++ b/thinkphp/tests/application/views/layout2.html @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/thinkphp/tests/conf/memcached.ini b/thinkphp/tests/conf/memcached.ini new file mode 100644 index 0000000..7d09664 --- /dev/null +++ b/thinkphp/tests/conf/memcached.ini @@ -0,0 +1 @@ +extension=memcached.so diff --git a/thinkphp/tests/conf/redis.ini b/thinkphp/tests/conf/redis.ini new file mode 100644 index 0000000..6aecae4 --- /dev/null +++ b/thinkphp/tests/conf/redis.ini @@ -0,0 +1 @@ +extension=redis.so diff --git a/thinkphp/tests/conf/timezone.ini b/thinkphp/tests/conf/timezone.ini new file mode 100644 index 0000000..ced3e0d --- /dev/null +++ b/thinkphp/tests/conf/timezone.ini @@ -0,0 +1 @@ +date.timezone = UTC \ No newline at end of file diff --git a/thinkphp/tests/mock.php b/thinkphp/tests/mock.php new file mode 100644 index 0000000..0c29e59 --- /dev/null +++ b/thinkphp/tests/mock.php @@ -0,0 +1,20 @@ + +// +---------------------------------------------------------------------- + +// 测试入口文件 +$_SERVER['REQUEST_METHOD'] = 'GET'; +// 定义项目测试基础路径 +define('TEST_PATH', __DIR__ . '/'); +// 定义项目路径 +define('APP_PATH', __DIR__ . '/application/'); +// 加载框架基础文件 +require __DIR__ . '/../base.php'; +\think\Loader::addNamespace('tests', TEST_PATH); diff --git a/thinkphp/tests/script/install.sh b/thinkphp/tests/script/install.sh new file mode 100644 index 0000000..8344169 --- /dev/null +++ b/thinkphp/tests/script/install.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +if [ $(phpenv version-name) != "hhvm" ]; then + cp tests/extensions/$(phpenv version-name)/*.so $(php-config --extension-dir) + + phpenv config-add tests/conf/memcached.ini + phpenv config-add tests/conf/redis.ini + + phpenv config-add tests/conf/timezone.ini +fi + +composer install --no-interaction --ignore-platform-reqs +composer update diff --git a/thinkphp/tests/thinkphp/baseTest.php b/thinkphp/tests/thinkphp/baseTest.php new file mode 100644 index 0000000..18f1d3d --- /dev/null +++ b/thinkphp/tests/thinkphp/baseTest.php @@ -0,0 +1,39 @@ + +// +---------------------------------------------------------------------- + +/** + * 保证运行环境正常 + */ +class baseTest extends \PHPUnit_Framework_TestCase +{ + public function testConstants() + { + $this->assertNotEmpty(THINK_START_TIME); + $this->assertNotEmpty(THINK_START_MEM); + $this->assertNotEmpty(THINK_VERSION); + $this->assertNotEmpty(DS); + $this->assertNotEmpty(THINK_PATH); + $this->assertNotEmpty(LIB_PATH); + $this->assertNotEmpty(EXTEND_PATH); + $this->assertNotEmpty(CORE_PATH); + $this->assertNotEmpty(TRAIT_PATH); + $this->assertNotEmpty(APP_PATH); + $this->assertNotEmpty(RUNTIME_PATH); + $this->assertNotEmpty(LOG_PATH); + $this->assertNotEmpty(CACHE_PATH); + $this->assertNotEmpty(TEMP_PATH); + $this->assertNotEmpty(VENDOR_PATH); + $this->assertNotEmpty(EXT); + $this->assertNotEmpty(ENV_PREFIX); + $this->assertTrue(!is_null(IS_WIN)); + $this->assertTrue(!is_null(IS_CLI)); + } +} diff --git a/thinkphp/tests/thinkphp/library/think/appTest.php b/thinkphp/tests/thinkphp/library/think/appTest.php new file mode 100644 index 0000000..016bbf9 --- /dev/null +++ b/thinkphp/tests/thinkphp/library/think/appTest.php @@ -0,0 +1,90 @@ + +// +---------------------------------------------------------------------- + +/** + * app类测试 + * @author Haotong Lin + */ + +namespace tests\thinkphp\library\think; + +use think\App; +use think\Config; +use think\Request; + +function func_trim($value) +{ + return trim($value); +} + +function func_strpos($haystack, $needle) +{ + return strpos($haystack, $needle); +} + +class AppInvokeMethodTestClass +{ + public static function staticRun($string) + { + return $string; + } + + public function run($string) + { + return $string; + } +} + +class appTest extends \PHPUnit_Framework_TestCase +{ + public function testRun() + { + $response = App::run(Request::create("http://www.example.com")); + + $expectOutputString = '

    :)

    ThinkPHP V5
    十年磨一剑 - 为API开发设计的高性能框架

    [ V5.0 版本由 七牛云 独家赞助发布 ]
    '; + + $this->assertEquals($expectOutputString, $response->getContent()); + $this->assertEquals(200, $response->getCode()); + + $this->assertEquals(true, function_exists('lang')); + $this->assertEquals(true, function_exists('config')); + $this->assertEquals(true, function_exists('input')); + + $this->assertEquals(Config::get('default_timezone'), date_default_timezone_get()); + + } + + // function调度 + public function testInvokeFunction() + { + $args1 = ['a b c ']; + $this->assertEquals( + trim($args1[0]), + App::invokeFunction('tests\thinkphp\library\think\func_trim', $args1) + ); + + $args2 = ['abcdefg', 'g']; + $this->assertEquals( + strpos($args2[0], $args2[1]), + App::invokeFunction('tests\thinkphp\library\think\func_strpos', $args2) + ); + } + + // 类method调度 + public function testInvokeMethod() + { + $result = App::invokeMethod(['tests\thinkphp\library\think\AppInvokeMethodTestClass', 'run'], ['thinkphp']); + $this->assertEquals('thinkphp', $result); + + $result = App::invokeMethod('tests\thinkphp\library\think\AppInvokeMethodTestClass::staticRun', ['thinkphp']); + $this->assertEquals('thinkphp', $result); + } +} diff --git a/thinkphp/tests/thinkphp/library/think/behavior/One.php b/thinkphp/tests/thinkphp/library/think/behavior/One.php new file mode 100644 index 0000000..1ec6e66 --- /dev/null +++ b/thinkphp/tests/thinkphp/library/think/behavior/One.php @@ -0,0 +1,15 @@ + +// +---------------------------------------------------------------------- + +/** + * build测试 + * @author 刘志淳 + */ + +namespace tests\thinkphp\library\think; + +use think\Build; + +class buildTest extends \PHPUnit_Framework_TestCase +{ + public function testRun() + { + $build = [ + // Test run directory + '__dir__' => ['runtime/cache', 'runtime/log', 'runtime/temp', 'runtime/template'], + '__file__' => ['common.php'], + + // Test generation module + 'demo' => [ + '__file__' => ['common.php'], + '__dir__' => ['behavior', 'controller', 'model', 'view', 'service'], + 'controller' => ['Index', 'Test', 'UserType'], + 'model' => ['User', 'UserType'], + 'service' => ['User', 'UserType'], + 'view' => ['index/index'], + ], + ]; + Build::run($build); + + $this->buildFileExists($build); + } + + protected function buildFileExists($build) + { + foreach ($build as $module => $list) { + if ('__dir__' == $module || '__file__' == $module) { + foreach ($list as $file) { + $this->assertFileExists(APP_PATH . $file); + } + } else { + foreach ($list as $path => $moduleList) { + if ('__file__' == $path || '__dir__' == $path) { + foreach ($moduleList as $file) { + $this->assertFileExists(APP_PATH . $module . '/' . $file); + } + } else { + foreach ($moduleList as $file) { + if ('view' == $path) { + $file_name = APP_PATH . $module . '/' . $path . '/' . $file . '.html'; + } else { + $file_name = APP_PATH . $module . '/' . $path . '/' . $file . EXT; + } + $this->assertFileExists($file_name); + } + } + } + $this->assertFileExists(APP_PATH . ($module ? $module . DS : '') . 'config.php'); + } + } + } +} diff --git a/thinkphp/tests/thinkphp/library/think/cache/driver/cacheTestCase.php b/thinkphp/tests/thinkphp/library/think/cache/driver/cacheTestCase.php new file mode 100644 index 0000000..5b2f2a3 --- /dev/null +++ b/thinkphp/tests/thinkphp/library/think/cache/driver/cacheTestCase.php @@ -0,0 +1,207 @@ + +// +---------------------------------------------------------------------- + +/** + * 缓存抽象类,提供一些测试 + * @author simon + */ + +namespace tests\thinkphp\library\think\cache\driver; + +use think\Cache; + +abstract class cacheTestCase extends \PHPUnit_Framework_TestCase +{ + + /** + * 获取缓存句柄,子类必须有 + * @access protected + */ + abstract protected function getCacheInstance(); + + /** + * tearDown函数 + */ + protected function tearDown() + { + } + + /** + * 设定一组测试值,包括测试字符串、整数、数组和对象 + * @return mixed + * @access public + */ + public function prepare() + { + $cache = $this->getCacheInstance(); + $cache->clear(); + $cache->set('string_test', 'string_test'); + $cache->set('number_test', 11); + $cache->set('array_test', ['array_test' => 'array_test']); + return $cache; + } + + /** + * 测试缓存设置,包括测试字符串、整数、数组和对象 + * @return mixed + * @access public + */ + public function testSet() + { + $cache = $this->getCacheInstance(); + $this->assertTrue($cache->set('string_test', 'string_test')); + $this->assertTrue($cache->set('number_test', 11)); + $this->assertTrue($cache->set('array_test', ['array_test' => 'array_test'])); + } + + /** + * 测试缓存自增 + * @return mixed + * @access public + */ + public function testInc() + { + $cache = $this->getCacheInstance(); + $this->assertEquals(14, $cache->inc('number_test', 3)); + } + + /** + * 测试缓存自减 + * @return mixed + * @access public + */ + public function testDec() + { + $cache = $this->getCacheInstance(); + $this->assertEquals(8, $cache->dec('number_test', 6)); + } + + /** + * 测试缓存读取,包括测试字符串、整数、数组和对象 + * @return mixed + * @access public + */ + public function testGet() + { + $cache = $this->prepare(); + $this->assertEquals('string_test', $cache->get('string_test')); + $this->assertEquals(11, $cache->get('number_test')); + $array = $cache->get('array_test'); + $this->assertArrayHasKey('array_test', $array); + $this->assertEquals('array_test', $array['array_test']); + + $result = $cache->set('no_expire', 1, 0); + $this->assertTrue($result); + } + + /** + * 测试缓存存在情况,包括测试字符串、整数、数组和对象 + * @return mixed + * @access public + */ + public function testExists() + { + $cache = $this->prepare(); + $this->assertNotEmpty($cache->has('string_test')); + $this->assertNotEmpty($cache->has('number_test')); + $this->assertFalse($cache->has('not_exists')); + } + + /** + * 测试缓存不存在情况,包括测试字符串、整数、数组和对象 + * @return mixed + * @access public + */ + public function testGetNonExistent() + { + $cache = $this->getCacheInstance(); + $this->assertFalse($cache->get('non_existent_key', false)); + } + + /** + * 测试特殊值缓存,包括测试字符串、整数、数组和对象 + * @return mixed + * @access public + */ + public function testStoreSpecialValues() + { + $cache = $this->getCacheInstance(); + $cache->set('null_value', null); + //清空缓存后,返回null而不是false + $this->assertTrue(is_null($cache->get('null_value'))); + } + + /** + * 缓存过期测试 + * @return mixed + * @access public + */ + public function testExpire() + { + $cache = $this->getCacheInstance(); + $this->assertTrue($cache->set('expire_test', 'expire_test', 1)); + usleep(600000); + $this->assertEquals('expire_test', $cache->get('expire_test')); + usleep(800000); + $this->assertFalse($cache->get('expire_test')); + } + + /** + * 删除缓存测试 + * @return mixed + * @access public + */ + public function testDelete() + { + $cache = $this->prepare(); + $this->assertNotNull($cache->rm('number_test')); + $this->assertFalse($cache->get('number_test')); + } + + /** + * 获取并删除缓存测试 + * @return mixed + * @access public + */ + public function testPull() + { + $cache = $this->prepare(); + $this->assertEquals(11, $cache->pull('number_test')); + $this->assertFalse($cache->get('number_test')); + } + + /** + * 清空缓存测试 + * @return mixed + * @access public + */ + public function testClear() + { + $cache = $this->prepare(); + $this->assertTrue($cache->clear()); + $this->assertFalse($cache->get('number_test')); + } + + public function testStaticCall() + { + $this->assertTrue(Cache::set('a', 1)); + $this->assertEquals(1, Cache::get('a')); + $this->assertEquals(2, Cache::inc('a')); + $this->assertEquals(4, Cache::inc('a', 2)); + $this->assertEquals(4, Cache::get('a')); + $this->assertEquals(3, Cache::dec('a')); + $this->assertEquals(1, Cache::dec('a', 2)); + $this->assertEquals(1, Cache::get('a')); + $this->assertNotNull(Cache::rm('a')); + $this->assertNotNull(Cache::clear()); + } + +} diff --git a/thinkphp/tests/thinkphp/library/think/cache/driver/fileTest.php b/thinkphp/tests/thinkphp/library/think/cache/driver/fileTest.php new file mode 100644 index 0000000..eb2099c --- /dev/null +++ b/thinkphp/tests/thinkphp/library/think/cache/driver/fileTest.php @@ -0,0 +1,46 @@ + +// +---------------------------------------------------------------------- + +/** + * File缓存驱动测试 + * @author 刘志淳 + */ + +namespace tests\thinkphp\library\think\cache\driver; + +class fileTest extends cacheTestCase +{ + private $_cacheInstance = null; + + /** + * 基境缓存类型 + */ + protected function setUp() + { + \think\Cache::connect(['type' => 'File', 'path' => CACHE_PATH]); + } + + /** + * @return FileCache + */ + protected function getCacheInstance() + { + if (null === $this->_cacheInstance) { + $this->_cacheInstance = new \think\cache\driver\File(); + } + return $this->_cacheInstance; + } + + // skip testExpire + public function testExpire() + { + } +} diff --git a/thinkphp/tests/thinkphp/library/think/cache/driver/liteTest.php b/thinkphp/tests/thinkphp/library/think/cache/driver/liteTest.php new file mode 100644 index 0000000..19cbb9e --- /dev/null +++ b/thinkphp/tests/thinkphp/library/think/cache/driver/liteTest.php @@ -0,0 +1,69 @@ + +// +---------------------------------------------------------------------- + +/** + * Lite缓存驱动测试 + * @author 刘志淳 + */ + +namespace tests\thinkphp\library\think\cache\driver; + +use think\Cache; + +class liteTest extends \PHPUnit_Framework_TestCase +{ + protected function getCacheInstance() + { + return Cache::connect(['type' => 'Lite', 'path' => CACHE_PATH]); + } + + /** + * 测试缓存读取 + * @return mixed + * @access public + */ + public function testGet() + { + $cache = $this->getCacheInstance(); + $this->assertFalse($cache->get('test')); + } + + /** + * 测试缓存设置 + * @return mixed + * @access public + */ + public function testSet() + { + $cache = $this->getCacheInstance(); + $this->assertNotEmpty($cache->set('test', 'test')); + } + + /** + * 删除缓存测试 + * @return mixed + * @access public + */ + public function testRm() + { + $cache = $this->getCacheInstance(); + $this->assertTrue($cache->rm('test')); + } + + /** + * 清空缓存测试 + * @return mixed + * @access public + */ + public function testClear() + { + } +} diff --git a/thinkphp/tests/thinkphp/library/think/cache/driver/memcacheTest.php b/thinkphp/tests/thinkphp/library/think/cache/driver/memcacheTest.php new file mode 100644 index 0000000..8975fa2 --- /dev/null +++ b/thinkphp/tests/thinkphp/library/think/cache/driver/memcacheTest.php @@ -0,0 +1,49 @@ + +// +---------------------------------------------------------------------- + +/** + * Memcache缓存驱动测试 + * @author 刘志淳 + */ + +namespace tests\thinkphp\library\think\cache\driver; + +class memcacheTest extends cacheTestCase +{ + private $_cacheInstance = null; + + /** + * 基境缓存类型 + */ + protected function setUp() + { + if (!extension_loaded('memcache')) { + $this->markTestSkipped("Memcache没有安装,已跳过测试!"); + } + \think\Cache::connect(['type' => 'memcache', 'expire' => 2]); + } + + /** + * @return ApcCache + */ + protected function getCacheInstance() + { + if (null === $this->_cacheInstance) { + $this->_cacheInstance = new \think\cache\driver\Memcache(['length' => 3]); + } + return $this->_cacheInstance; + } + + // skip testExpire + public function testExpire() + { + } +} diff --git a/thinkphp/tests/thinkphp/library/think/cache/driver/memcachedTest.php b/thinkphp/tests/thinkphp/library/think/cache/driver/memcachedTest.php new file mode 100644 index 0000000..d528dd2 --- /dev/null +++ b/thinkphp/tests/thinkphp/library/think/cache/driver/memcachedTest.php @@ -0,0 +1,72 @@ + +// +---------------------------------------------------------------------- + +/** + * Memcached缓存驱动测试 + * @author 7IN0SAN9 + */ + +namespace tests\thinkphp\library\think\cache\driver; + +class memcachedTest extends cacheTestCase +{ + private $_cacheInstance = null; + /** + * 基境缓存类型 + */ + protected function setUp() + { + if (!extension_loaded("memcached") && !extension_loaded('memcache')) { + $this->markTestSkipped("Memcached或Memcache没有安装,已跳过测试!"); + } + \think\Cache::connect(array('type' => 'memcached', 'expire' => 2)); + } + /** + * @return ApcCache + */ + protected function getCacheInstance() + { + if (null === $this->_cacheInstance) { + $this->_cacheInstance = new \think\cache\driver\Memcached(['length' => 3]); + } + return $this->_cacheInstance; + } + /** + * 缓存过期测试《提出来测试,因为目前看通不过缓存过期测试,所以还需研究》 + * @return mixed + * @access public + */ + public function testExpire() + { + } + + public function testStaticCall() + { + } + + /** + * 测试缓存自增 + * @return mixed + * @access public + */ + public function testInc() + { + } + + /** + * 测试缓存自减 + * @return mixed + * @access public + */ + public function testDec() + { + } +} diff --git a/thinkphp/tests/thinkphp/library/think/cache/driver/redisTest.php b/thinkphp/tests/thinkphp/library/think/cache/driver/redisTest.php new file mode 100644 index 0000000..2000ee4 --- /dev/null +++ b/thinkphp/tests/thinkphp/library/think/cache/driver/redisTest.php @@ -0,0 +1,66 @@ + +// +---------------------------------------------------------------------- + +/** + * Redis缓存驱动测试 + * @author 7IN0SAN9 + */ + +namespace tests\thinkphp\library\think\cache\driver; + +class redisTest extends cacheTestCase +{ + private $_cacheInstance = null; + + protected function setUp() + { + if (!extension_loaded("redis")) { + $this->markTestSkipped("Redis没有安装,已跳过测试!"); + } + \think\Cache::connect(array('type' => 'redis', 'expire' => 2)); + } + + protected function getCacheInstance() + { + if (null === $this->_cacheInstance) { + $this->_cacheInstance = new \think\cache\driver\Redis(['length' => 3]); + } + return $this->_cacheInstance; + } + + public function testGet() + { + $cache = $this->prepare(); + $this->assertEquals('string_test', $cache->get('string_test')); + $this->assertEquals(11, $cache->get('number_test')); + $result = $cache->get('array_test'); + $this->assertEquals('array_test', $result['array_test']); + } + + public function testStoreSpecialValues() + { + $redis = new \think\cache\driver\Redis(['length' => 3]); + $redis->set('key', 'value'); + $redis->get('key'); + + $redis->handler()->setnx('key', 'value'); + $value = $redis->handler()->get('key'); + $this->assertEquals('value', $value); + + $redis->handler()->hset('hash', 'key', 'value'); + $value = $redis->handler()->hget('hash', 'key'); + $this->assertEquals('value', $value); + } + + public function testExpire() + { + } +} diff --git a/thinkphp/tests/thinkphp/library/think/cacheTest.php b/thinkphp/tests/thinkphp/library/think/cacheTest.php new file mode 100644 index 0000000..dbcd705 --- /dev/null +++ b/thinkphp/tests/thinkphp/library/think/cacheTest.php @@ -0,0 +1,315 @@ + +// +---------------------------------------------------------------------- + +namespace tests\thinkphp\library\think; + +use tests\thinkphp\library\think\config\ConfigInitTrait; +use think\Cache; +use think\Config; + +class cacheTest extends \PHPUnit_Framework_TestCase +{ + use ConfigInitTrait { + ConfigInitTrait::tearDown as ConfigTearDown; + } + + public function tearDown() + { + $this->ConfigTearDown(); + + call_user_func(\Closure::bind(function () { + Cache::$handler = null; + Cache::$instance = []; + Cache::$readTimes = 0; + Cache::$writeTimes = 0; + }, null, '\think\Cache')); + } + + /** + * @dataProvider provideTestConnect + */ + public function testConnect($options, $expected) + { + $connection = Cache::connect($options); + $this->assertInstanceOf($expected, $connection); + $this->assertSame($connection, Cache::connect($options)); + + $instance = $this->getPropertyVal('instance'); + $this->assertArrayHasKey(md5(serialize($options)), $instance); + + $newConnection = Cache::connect($options, true); + $newInstance = $this->getPropertyVal('instance'); + $this->assertInstanceOf($expected, $connection); + $this->assertNotSame($connection, $newConnection); + $this->assertEquals($instance, $newInstance); + } + + /** + * @dataProvider provideTestInit + */ + public function testInit($options, $expected) + { + $connection = Cache::init($options); + $this->assertInstanceOf($expected, $connection); + + $connectionNew = Cache::init(['type' => 'foo']); + $this->assertSame($connection, $connectionNew); + } + + public function testStore() + { + Config::set('cache.redis', ['type' => 'redis']); + + $connectionDefault = Cache::store(); + $this->assertInstanceOf('\think\cache\driver\File', $connectionDefault); + + Config::set('cache.type', false); + $connectionNotRedis = Cache::store('redis'); + $this->assertSame($connectionDefault, $connectionNotRedis); + + Config::set('cache.type', 'complex'); + $connectionRedis = Cache::store('redis'); + $this->assertNotSame($connectionNotRedis, $connectionRedis); + $this->assertInstanceOf('\think\cache\driver\Redis', $connectionRedis); + + // 即便成功切换到其他存储类型,也不影响原先的操作句柄 + $this->assertSame($connectionDefault, Cache::store()); + } + + public function testHas() + { + $key = $this->buildTestKey('Has'); + + $this->assertFalse(Cache::has($key)); + + Cache::set($key, 5); + $this->assertTrue(Cache::has($key)); + } + + public function testGet() + { + $key = $this->buildTestKey('Get'); + + $this->assertFalse(Cache::get($key)); + + $this->assertEquals('default', Cache::get($key, 'default')); + + Cache::set($key, 5); + $this->assertSame(5, Cache::get($key)); + } + + public function testSet() + { + $key = $this->buildTestKey('Set'); + + $this->assertTrue(Cache::set(null, null)); + $this->assertTrue(Cache::set($key, 'ThinkPHP3.2')); + $this->assertTrue(Cache::set($key, 'ThinkPHP5.0', null)); + $this->assertTrue(Cache::set($key, 'ThinkPHP5.0', -1)); + $this->assertTrue(Cache::set($key, 'ThinkPHP5.0', 0)); + $this->assertTrue(Cache::set($key, 'ThinkPHP5.0', 7200)); + } + + public function testInc() + { + $key = $this->buildTestKey('Inc'); + + Cache::inc($key); + $this->assertEquals(1, Cache::get($key)); + + Cache::inc($key, '2'); + $this->assertEquals(3, Cache::get($key)); + + Cache::inc($key, -1); + $this->assertEquals(2, Cache::get($key)); + + Cache::inc($key, null); + $this->assertEquals(2, Cache::get($key)); + + Cache::inc($key, true); + $this->assertEquals(3, Cache::get($key)); + + Cache::inc($key, false); + $this->assertEquals(3, Cache::get($key)); + + Cache::inc($key, 0.789); + $this->assertEquals(3.789, Cache::get($key)); + } + public function testDec() + { + $key = $this->buildTestKey('Dec'); + + Cache::dec($key); + $this->assertEquals(-1, Cache::get($key)); + + Cache::dec($key, '2'); + $this->assertEquals(-3, Cache::get($key)); + + Cache::dec($key, -1); + $this->assertEquals(-2, Cache::get($key)); + + Cache::dec($key, null); + $this->assertEquals(-2, Cache::get($key)); + + Cache::dec($key, true); + $this->assertEquals(-3, Cache::get($key)); + + Cache::dec($key, false); + $this->assertEquals(-3, Cache::get($key)); + + Cache::dec($key, 0.359); + $this->assertEquals(-3.359, Cache::get($key)); + } + + public function testRm() + { + $key = $this->buildTestKey('Rm'); + + $this->assertFalse(Cache::rm($key)); + + Cache::set($key, 'ThinkPHP'); + $this->assertTrue(Cache::rm($key)); + } + + public function testClear() + { + $key1 = $this->buildTestKey('Clear1'); + $key2 = $this->buildTestKey('Clear2'); + + Cache::set($key1, 'ThinkPHP3.2'); + Cache::set($key2, 'ThinkPHP5.0'); + + $this->assertEquals('ThinkPHP3.2', Cache::get($key1)); + $this->assertEquals('ThinkPHP5.0', Cache::get($key2)); + Cache::clear(); + $this->assertFalse(Cache::get($key1)); + $this->assertFalse(Cache::get($key2)); + } + + public function testPull() + { + $key = $this->buildTestKey('Pull'); + + $this->assertNull(Cache::pull($key)); + + Cache::set($key, 'ThinkPHP'); + $this->assertEquals('ThinkPHP', Cache::pull($key)); + $this->assertFalse(Cache::get($key)); + } + + public function testRemember() + { + $key1 = $this->buildTestKey('Remember1'); + $key2 = $this->buildTestKey('Remember2'); + + $this->assertEquals('ThinkPHP3.2', Cache::remember($key1, 'ThinkPHP3.2')); + $this->assertEquals('ThinkPHP3.2', Cache::remember($key1, 'ThinkPHP5.0')); + + $this->assertEquals('ThinkPHP5.0', Cache::remember($key2, function () { + return 'ThinkPHP5.0'; + })); + $this->assertEquals('ThinkPHP5.0', Cache::remember($key2, function () { + return 'ThinkPHP3.2'; + })); + } + + public function testTag() + { + $key = $this->buildTestKey('Tag'); + + $cacheTagWithoutName = Cache::tag(null); + $this->assertInstanceOf('think\cache\Driver', $cacheTagWithoutName); + + $cacheTagWithKey = Cache::tag($key, [1, 2, 3]); + $this->assertSame($cacheTagWithoutName, $cacheTagWithKey); + } + + protected function getPropertyVal($name) + { + static $reflectionClass; + if (empty($reflectionClass)) { + $reflectionClass = new \ReflectionClass('\think\Cache'); + } + + $property = $reflectionClass->getProperty($name); + $property->setAccessible(true); + + return $property->getValue(); + } + + public function provideTestConnect() + { + $provideData = []; + + $options = ['type' => null]; + $expected = '\think\cache\driver\File'; + $provideData[] = [$options, $expected]; + + $options = ['type' => 'File']; + $expected = '\think\cache\driver\File'; + $provideData[] = [$options, $expected]; + + $options = ['type' => 'Lite']; + $expected = '\think\cache\driver\Lite'; + $provideData[] = [$options, $expected]; + + $options = ['type' => 'Memcached']; + $expected = '\think\cache\driver\Memcached'; + $provideData[] = [$options, $expected]; + + $options = ['type' => 'Redis']; + $expected = '\think\cache\driver\Redis'; + $provideData[] = [$options, $expected]; + + // TODO + // $options = ['type' => 'Memcache']; + // $expected = '\think\cache\driver\Memcache'; + // $provideData[] = [$options, $expected]; + // + // $options = ['type' => 'Wincache']; + // $expected = '\think\cache\driver\Wincache'; + // $provideData[] = [$options, $expected]; + + // $options = ['type' => 'Sqlite']; + // $expected = '\think\cache\driver\Sqlite'; + // $provideData[] = [$options, $expected]; + // + // $options = ['type' => 'Xcache']; + // $expected = '\think\cache\driver\Xcache'; + // $provideData[] = [$options, $expected]; + + return $provideData; + } + + public function provideTestInit() + { + $provideData = []; + + $options = []; + $expected = '\think\cache\driver\File'; + $provideData[] = [$options, $expected]; + + $options = ['type' => 'File']; + $expected = '\think\cache\driver\File'; + $provideData[] = [$options, $expected]; + + $options = ['type' => 'Lite']; + $expected = '\think\cache\driver\Lite'; + $provideData[] = [$options, $expected]; + + return $provideData; + } + + protected function buildTestKey($tag) + { + return sprintf('ThinkPHP_Test_%s_%d_%d', $tag, time(), rand(0, 10000)); + } +} diff --git a/thinkphp/tests/thinkphp/library/think/config/ConfigInitTrait.php b/thinkphp/tests/thinkphp/library/think/config/ConfigInitTrait.php new file mode 100644 index 0000000..ce2b397 --- /dev/null +++ b/thinkphp/tests/thinkphp/library/think/config/ConfigInitTrait.php @@ -0,0 +1,52 @@ + + + + 1 + + \ No newline at end of file diff --git a/thinkphp/tests/thinkphp/library/think/config/driver/iniTest.php b/thinkphp/tests/thinkphp/library/think/config/driver/iniTest.php new file mode 100644 index 0000000..66b38d9 --- /dev/null +++ b/thinkphp/tests/thinkphp/library/think/config/driver/iniTest.php @@ -0,0 +1,36 @@ + +// +---------------------------------------------------------------------- + +/** + * Ini配置测试 + * @author 7IN0SAN9 + */ + +namespace tests\thinkphp\library\think\config\driver; + +use tests\thinkphp\library\think\config\ConfigInitTrait; +use think\config; + +class iniTest extends \PHPUnit_Framework_TestCase +{ + use ConfigInitTrait; + + public function testParse() + { + Config::parse('inistring=1', 'ini'); + $this->assertEquals(1, Config::get('inistring')); + Config::reset(); + Config::parse(__DIR__ . '/fixtures/config.ini'); + $this->assertTrue(Config::has('inifile')); + $this->assertEquals(1, Config::get('inifile')); + Config::reset(); + } +} diff --git a/thinkphp/tests/thinkphp/library/think/config/driver/jsonTest.php b/thinkphp/tests/thinkphp/library/think/config/driver/jsonTest.php new file mode 100644 index 0000000..583e6dd --- /dev/null +++ b/thinkphp/tests/thinkphp/library/think/config/driver/jsonTest.php @@ -0,0 +1,36 @@ + +// +---------------------------------------------------------------------- + +/** + * Xml配置测试 + * @author 7IN0SAN9 + */ + +namespace tests\thinkphp\library\think\config\driver; + +use tests\thinkphp\library\think\config\ConfigInitTrait; +use think\config; + +class jsonTest extends \PHPUnit_Framework_TestCase +{ + use ConfigInitTrait; + + public function testParse() + { + Config::parse('{"jsonstring":1}', 'json'); + $this->assertEquals(1, Config::get('jsonstring')); + Config::reset(); + Config::parse(__DIR__ . '/fixtures/config.json'); + $this->assertTrue(Config::has('jsonstring')); + $this->assertEquals(1, Config::get('jsonstring')); + Config::reset(); + } +} diff --git a/thinkphp/tests/thinkphp/library/think/config/driver/xmlTest.php b/thinkphp/tests/thinkphp/library/think/config/driver/xmlTest.php new file mode 100644 index 0000000..e2225f7 --- /dev/null +++ b/thinkphp/tests/thinkphp/library/think/config/driver/xmlTest.php @@ -0,0 +1,36 @@ + +// +---------------------------------------------------------------------- + +/** + * Xml配置测试 + * @author 7IN0SAN9 + */ + +namespace tests\thinkphp\library\think\config\driver; + +use tests\thinkphp\library\think\config\ConfigInitTrait; +use think\config; + +class xmlTest extends \PHPUnit_Framework_TestCase +{ + use ConfigInitTrait; + + public function testParse() + { + Config::parse('1', 'xml'); + $this->assertEquals(1, Config::get('xmlstring')); + Config::reset(); + Config::parse(__DIR__ . '/fixtures/config.xml'); + $this->assertTrue(Config::has('xmlfile.istrue')); + $this->assertEquals(1, Config::get('xmlfile.istrue')); + Config::reset(); + } +} diff --git a/thinkphp/tests/thinkphp/library/think/configTest.php b/thinkphp/tests/thinkphp/library/think/configTest.php new file mode 100644 index 0000000..4c92df5 --- /dev/null +++ b/thinkphp/tests/thinkphp/library/think/configTest.php @@ -0,0 +1,149 @@ + +// +---------------------------------------------------------------------- + +/** + * 配置测试 + * @author Haotong Lin + */ + +namespace tests\thinkphp\library\think; + +use tests\thinkphp\library\think\config\ConfigInitTrait; +use think\Config; + +class configTest extends \PHPUnit_Framework_TestCase +{ + use ConfigInitTrait; + + public function testRange() + { + // test default range + $this->assertEquals('_sys_', call_user_func(self::$internalRangeFoo)); + + $this->assertTrue(is_array(call_user_func(self::$internalConfigFoo))); + // test range initialization + Config::range('_test_'); + $this->assertEquals('_test_', call_user_func(self::$internalRangeFoo)); + $this->assertEquals([], call_user_func(self::$internalConfigFoo)['_test_']); + } + + // public function testParse() + // { + // see \think\config\driver\...Test.php + // } + + public function testLoad() + { + $file = APP_PATH . 'config' . EXT; + $config = array_change_key_case(include $file); + $name = '_name_'; + $range = '_test_'; + + $this->assertEquals($config, Config::load($file, $name, $range)); + $this->assertNotEquals(null, Config::load($file, $name, $range)); + } + + public function testHas() + { + $range = '_test_'; + $this->assertFalse(Config::has('abcd', $range)); + + call_user_func(self::$internalConfigFoo, [ + $range => ['abcd' => 'value'], + ]); + $this->assertTrue(Config::has('abcd', $range)); + + // else ... + $this->assertFalse(Config::has('abcd.efg', $range)); + + call_user_func(self::$internalConfigFoo, [ + $range => ['abcd' => ['efg' => 'value']], + ]); + $this->assertTrue(Config::has('abcd.efg', $range)); + } + + public function testGet() + { + $range = '_test_'; + call_user_func(self::$internalConfigFoo, [ + $range => [] + ]); + $this->assertEquals([], Config::get(null, $range)); + $this->assertEquals(null, Config::get(null, 'does_not_exist')); + $value = 'value'; + // test getting configuration + call_user_func(self::$internalConfigFoo, [ + $range => ['abcd' => 'efg'] + ]); + $this->assertEquals('efg', Config::get('abcd', $range)); + $this->assertEquals(null, Config::get('does_not_exist', $range)); + $this->assertEquals(null, Config::get('abcd', 'does_not_exist')); + // test getting configuration with dot syntax + call_user_func(self::$internalConfigFoo, [ + $range => ['one' => ['two' => $value]] + ]); + $this->assertEquals($value, Config::get('one.two', $range)); + $this->assertEquals(null, Config::get('one.does_not_exist', $range)); + $this->assertEquals(null, Config::get('one.two', 'does_not_exist')); + } + + public function testSet() + { + $range = '_test_'; + + // without dot syntax + $name = 'name'; + $value = 'value'; + Config::set($name, $value, $range); + $config = call_user_func(self::$internalConfigFoo); + $this->assertEquals($value, $config[$range][$name]); + // with dot syntax + $name = 'one.two'; + $value = 'dot value'; + Config::set($name, $value, $range); + $config = call_user_func(self::$internalConfigFoo); + $this->assertEquals($value, $config[$range]['one']['two']); + // if (is_array($name)): + // see testLoad() + // ... + // test getting all configurations...? + // return self::$config[$range]; ?? + $value = ['all' => 'configuration']; + call_user_func(self::$internalConfigFoo, [$range => $value]); + $this->assertEquals($value, Config::set(null, null, $range)); + $this->assertNotEquals(null, Config::set(null, null, $range)); + } + + public function testReset() + { + $range = '_test_'; + call_user_func(self::$internalConfigFoo, [$range => ['abcd' => 'efg']]); + + // clear all configurations + Config::reset(true); + $config = call_user_func(self::$internalConfigFoo); + $this->assertEquals([], $config); + // clear the configuration in range of parameter. + call_user_func(self::$internalConfigFoo, [ + $range => [ + 'abcd' => 'efg', + 'hijk' => 'lmn', + ], + 'a' => 'b', + ]); + Config::reset($range); + $config = call_user_func(self::$internalConfigFoo); + $this->assertEquals([ + $range => [], + 'a' => 'b', + ], $config); + } +} diff --git a/thinkphp/tests/thinkphp/library/think/controller/.gitignore b/thinkphp/tests/thinkphp/library/think/controller/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/thinkphp/tests/thinkphp/library/think/controller/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/thinkphp/tests/thinkphp/library/think/controllerTest.php b/thinkphp/tests/thinkphp/library/think/controllerTest.php new file mode 100644 index 0000000..4164770 --- /dev/null +++ b/thinkphp/tests/thinkphp/library/think/controllerTest.php @@ -0,0 +1,194 @@ + +// +---------------------------------------------------------------------- + +/** + * 控制器测试 + * @author Haotong Lin + */ + +namespace tests\thinkphp\library\think; + +use ReflectionClass; +use think\Controller; +use think\Request; +use think\View; + +require_once CORE_PATH . '../../helper.php'; + +class Foo extends Controller +{ + public $test = 'test'; + + public function _initialize() + { + $this->test = 'abcd'; + } + + public function assignTest() + { + $this->assign('abcd', 'dcba'); + $this->assign(['key1' => 'value1', 'key2' => 'value2']); + } + + public function fetchTest() + { + $template = APP_PATH . 'views' . DS .'display.html'; + return $this->fetch($template, ['name' => 'ThinkPHP']); + } + + public function displayTest() + { + $template = APP_PATH . 'views' . DS .'display.html'; + return $this->display($template, ['name' => 'ThinkPHP']); + } + public function test() + { + $data = [ + 'username' => 'username', + 'nickname' => 'nickname', + 'password' => '123456', + 'repassword' => '123456', + 'email' => 'abc@abc.com', + 'sex' => '0', + 'age' => '20', + 'code' => '1234', + ]; + + $validate = [ + ['username', 'length:5,15', '用户名长度为5到15个字符'], + ['nickname', 'require', '请填昵称'], + ['password', '[\w-]{6,15}', '密码长度为6到15个字符'], + ['repassword', 'confirm:password', '两次密码不一到致'], + ['email', 'filter:validate_email', '邮箱格式错误'], + ['sex', 'in:0,1', '性别只能为为男或女'], + ['age', 'between:1,80', '年龄只能在10-80之间'], + ]; + return $this->validate($data, $validate); + } +} + +class Bar extends Controller +{ + public $test = 1; + + public $beforeActionList = ['action1', 'action2']; + + public function action1() + { + $this->test += 2; + return 'action1'; + } + + public function action2() + { + $this->test += 4; + return 'action2'; + } +} + +class Baz extends Controller +{ + public $test = 1; + + public $beforeActionList = [ + 'action1' => ['only' => 'index'], + 'action2' => ['except' => 'index'], + 'action3' => ['only' => 'abcd'], + 'action4' => ['except' => 'abcd'], + ]; + + public function action1() + { + $this->test += 2; + return 'action1'; + } + + public function action2() + { + $this->test += 4; + return 'action2'; + } + + public function action3() + { + $this->test += 8; + return 'action2'; + } + + public function action4() + { + $this->test += 16; + return 'action2'; + } +} + +class controllerTest extends \PHPUnit_Framework_TestCase +{ + public function testInitialize() + { + $foo = new Foo(Request::instance()); + $this->assertEquals('abcd', $foo->test); + } + + public function testBeforeAction() + { + $obj = new Bar(Request::instance()); + $this->assertEquals(7, $obj->test); + + $obj = new Baz(Request::instance()); + $this->assertEquals(19, $obj->test); + } + + private function getView($controller) + { + $view = new View(); + $rc = new ReflectionClass(get_class($controller)); + $property = $rc->getProperty('view'); + $property->setAccessible(true); + $property->setValue($controller, $view); + return $view; + } + + public function testFetch() + { + $controller = new Foo(Request::instance()); + $view = $this->getView($controller); + $template = APP_PATH . 'views' . DS .'display.html'; + $viewFetch = $view->fetch($template, ['name' => 'ThinkPHP']); + $this->assertEquals($controller->fetchTest(), $viewFetch); + } + + public function testDisplay() + { + $controller = new Foo; + $view = $this->getView($controller); + $template = APP_PATH . 'views' . DS .'display.html'; + $viewFetch = $view->display($template, ['name' => 'ThinkPHP']); + + $this->assertEquals($controller->displayTest(), $viewFetch); + } + + public function testAssign() + { + $controller = new Foo(Request::instance()); + $view = $this->getView($controller); + $controller->assignTest(); + $expect = ['abcd' => 'dcba', 'key1' => 'value1', 'key2' => 'value2']; + $this->assertAttributeEquals($expect, 'data', $view); + } + + public function testValidate() + { + $controller = new Foo(Request::instance()); + $result = $controller->test(); + $this->assertTrue($result); + } +} diff --git a/thinkphp/tests/thinkphp/library/think/cookieTest.php b/thinkphp/tests/thinkphp/library/think/cookieTest.php new file mode 100644 index 0000000..24cb153 --- /dev/null +++ b/thinkphp/tests/thinkphp/library/think/cookieTest.php @@ -0,0 +1,150 @@ + +// +---------------------------------------------------------------------- + +/** + * Cookie测试 + * @author Haotong Lin + */ + +namespace tests\thinkphp\library\think; + +use ReflectionClass; +use think\Cookie; + +class cookieTest extends \PHPUnit_Framework_TestCase +{ + protected $ref; + + protected $default = [ + // cookie 名称前缀 + 'prefix' => '', + // cookie 保存时间 + 'expire' => 0, + // cookie 保存路径 + 'path' => '/', + // cookie 有效域名 + 'domain' => '', + // cookie 启用安全传输 + 'secure' => false, + // httponly设置 + 'httponly' => '', + // 是否使用 setcookie + 'setcookie' => false, + ]; + + protected function setUp() + { + $reflectedClass = new ReflectionClass('\think\Cookie'); + $reflectedPropertyConfig = $reflectedClass->getProperty('config'); + $reflectedPropertyConfig->setAccessible(true); + $reflectedPropertyConfig->setValue($this->default); + $this->ref = $reflectedPropertyConfig; + } + + public function testInit() + { + $config = [ + // cookie 名称前缀 + 'prefix' => 'think_', + // cookie 保存时间 + 'expire' => 0, + // cookie 保存路径 + 'path' => '/path/to/test/', + // cookie 有效域名 + 'domain' => '.thinkphp.cn', + // cookie 启用安全传输 + 'secure' => true, + // httponly设置 + 'httponly' => '1', + ]; + Cookie::init($config); + + $this->assertEquals( + array_merge($this->default, array_change_key_case($config)), + $this->ref->getValue() + ); + } + + public function testPrefix() + { + $this->assertEquals($this->default['prefix'], Cookie::prefix()); + + $prefix = '_test_'; + $this->assertNotEquals($prefix, Cookie::prefix()); + Cookie::prefix($prefix); + + $config = $this->ref->getValue(); + $this->assertEquals($prefix, $config['prefix']); + } + + public function testSet() + { + $value = 'value'; + + $name = 'name1'; + Cookie::set($name, $value, 10); + $this->assertEquals($value, $_COOKIE[$this->default['prefix'] . $name]); + + $name = 'name2'; + Cookie::set($name, $value, null); + $this->assertEquals($value, $_COOKIE[$this->default['prefix'] . $name]); + + $name = 'name3'; + Cookie::set($name, $value, 'expire=100&prefix=pre_'); + $this->assertEquals($value, $_COOKIE['pre_' . $name]); + + $name = 'name4'; + $value = ['_test_中文_']; + Cookie::set($name, $value); + $this->assertEquals('think:' . json_encode([urlencode('_test_中文_')]), $_COOKIE[$name]); + } + + public function testGet() + { + $_COOKIE = [ + 'a' => 'b', + 'pre_abc' => 'c', + 'd' => 'think:' . json_encode([urlencode('_test_中文_')]), + ]; + $this->assertEquals('b', Cookie::get('a')); + $this->assertEquals(null, Cookie::get('does_not_exist')); + $this->assertEquals('c', Cookie::get('abc', 'pre_')); + $this->assertEquals(['_test_中文_'], Cookie::get('d')); + } + + public function testDelete() + { + $_COOKIE = [ + 'a' => 'b', + 'pre_abc' => 'c', + ]; + $this->assertEquals('b', Cookie::get('a')); + Cookie::delete('a'); + $this->assertEquals(null, Cookie::get('a')); + + $this->assertEquals('c', Cookie::get('abc', 'pre_')); + Cookie::delete('abc', 'pre_'); + $this->assertEquals(null, Cookie::get('abc', 'pre_')); + } + + public function testClear() + { + $_COOKIE = []; + $this->assertEquals(null, Cookie::clear()); + + $_COOKIE = [ + 'a' => 'b', + 'pre_abc' => 'c', + ]; + Cookie::clear('pre_'); + $this->assertEquals(['a' => 'b'], $_COOKIE); + } +} diff --git a/thinkphp/tests/thinkphp/library/think/db/driver/.gitignore b/thinkphp/tests/thinkphp/library/think/db/driver/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/thinkphp/tests/thinkphp/library/think/db/driver/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/thinkphp/tests/thinkphp/library/think/dbTest.php b/thinkphp/tests/thinkphp/library/think/dbTest.php new file mode 100644 index 0000000..5724ee2 --- /dev/null +++ b/thinkphp/tests/thinkphp/library/think/dbTest.php @@ -0,0 +1,352 @@ + +// +---------------------------------------------------------------------- + +/** + * Db类测试 + * @author: 刘志淳 + */ + +namespace tests\thinkphp\library\think; + +use think\Db; + +class dbTest extends \PHPUnit_Framework_TestCase +{ + // 获取测试数据库配置 + private function getConfig() + { + return [ + // 数据库类型 + 'type' => 'mysql', + // 服务器地址 + 'hostname' => '127.0.0.1', + // 数据库名 + 'database' => 'test', + // 用户名 + 'username' => 'root', + // 密码 + 'password' => '', + // 端口 + 'hostport' => '', + // 连接dsn + 'dsn' => '', + // 数据库连接参数 + 'params' => [], + // 数据库编码默认采用utf8 + 'charset' => 'utf8', + // 数据库表前缀 + 'prefix' => 'tp_', + // 数据库调试模式 + 'debug' => true, + // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) + 'deploy' => 0, + // 数据库读写是否分离 主从式有效 + 'rw_separate' => false, + // 读写分离后 主服务器数量 + 'master_num' => 1, + // 指定从服务器序号 + 'slave_no' => '', + // 是否严格检查字段是否存在 + 'fields_strict' => true, + // 数据集返回类型 array 数组 collection Collection对象 + 'resultset_type' => 'array', + // 是否自动写入时间戳字段 + 'auto_timestamp' => false, + // 是否需要进行SQL性能分析 + 'sql_explain' => false, + ]; + } + + // 获取创建数据库 SQL + private function getCreateTableSql() + { + $sql[] = <<getConfig(); + $result = Db::connect($config)->execute('show databases'); + $this->assertNotEmpty($result); + } + + public function testExecute() + { + $config = $this->getConfig(); + $sql = $this->getCreateTableSql(); + foreach ($sql as $one) { + Db::connect($config)->execute($one); + } + $tableNum = Db::connect($config)->execute("show tables;"); + $this->assertEquals(4, $tableNum); + } + + public function testQuery() + { + $config = $this->getConfig(); + $sql = $this->getCreateTableSql(); + Db::connect($config)->batchQuery($sql); + + $tableQueryResult = Db::connect($config)->query("show tables;"); + + $this->assertTrue(is_array($tableQueryResult)); + + $tableNum = count($tableQueryResult); + $this->assertEquals(4, $tableNum); + } + + public function testBatchQuery() + { + $config = $this->getConfig(); + $sql = $this->getCreateTableSql(); + Db::connect($config)->batchQuery($sql); + + $tableNum = Db::connect($config)->execute("show tables;"); + $this->assertEquals(4, $tableNum); + } + + public function testTable() + { + $config = $this->getConfig(); + $tableName = 'tp_user'; + $result = Db::connect($config)->table($tableName); + $this->assertEquals($tableName, $result->getOptions()['table']); + } + + public function testName() + { + $config = $this->getConfig(); + $tableName = 'user'; + $result = Db::connect($config)->name($tableName); + $this->assertEquals($config['prefix'] . $tableName, $result->getTable()); + } + + public function testInsert() + { + $config = $this->getConfig(); + $data = [ + 'username' => 'chunice', + 'password' => md5('chunice'), + 'status' => 1, + 'create_time' => time(), + ]; + $result = Db::connect($config)->name('user')->insert($data); + $this->assertEquals(1, $result); + } + + public function testUpdate() + { + $config = $this->getConfig(); + $data = [ + 'username' => 'chunice_update', + 'password' => md5('chunice'), + 'status' => 1, + 'create_time' => time(), + ]; + $result = Db::connect($config)->name('user')->where('username', 'chunice')->update($data); + $this->assertEquals(1, $result); + } + + public function testFind() + { + $config = $this->getConfig(); + $mustFind = Db::connect($config)->name('user')->where('username', 'chunice_update')->find(); + $this->assertNotEmpty($mustFind); + $mustNotFind = Db::connect($config)->name('user')->where('username', 'chunice')->find(); + $this->assertEmpty($mustNotFind); + } + + public function testInsertAll() + { + $config = $this->getConfig(); + + $data = [ + ['username' => 'foo', 'password' => md5('foo'), 'status' => 1, 'create_time' => time()], + ['username' => 'bar', 'password' => md5('bar'), 'status' => 1, 'create_time' => time()], + ]; + + $insertNum = Db::connect($config)->name('user')->insertAll($data); + $this->assertEquals(count($data), $insertNum); + } + + public function testSelect() + { + $config = $this->getConfig(); + $mustFound = Db::connect($config)->name('user')->where('status', 1)->select(); + $this->assertNotEmpty($mustFound); + $mustNotFound = Db::connect($config)->name('user')->where('status', 0)->select(); + $this->assertEmpty($mustNotFound); + } + + public function testValue() + { + $config = $this->getConfig(); + $username = Db::connect($config)->name('user')->where('id', 1)->value('username'); + $this->assertEquals('chunice_update', $username); + $usernameNull = Db::connect($config)->name('user')->where('id', 0)->value('username'); + $this->assertEmpty($usernameNull); + } + + public function testColumn() + { + $config = $this->getConfig(); + $username = Db::connect($config)->name('user')->where('status', 1)->column('username'); + $this->assertNotEmpty($username); + $usernameNull = Db::connect($config)->name('user')->where('status', 0)->column('username'); + $this->assertEmpty($usernameNull); + + } + + public function testInsertGetId() + { + $config = $this->getConfig(); + $id = Db::connect($config)->name('user')->order('id', 'desc')->value('id'); + + $data = [ + 'username' => uniqid(), + 'password' => md5('chunice'), + 'status' => 1, + 'create_time' => time(), + ]; + $lastId = Db::connect($config)->name('user')->insertGetId($data); + $this->assertEquals($id + 1, $lastId); + + } + + public function testGetLastInsId() + { + $config = $this->getConfig(); + $data = [ + 'username' => uniqid(), + 'password' => md5('chunice'), + 'status' => 1, + 'create_time' => time(), + ]; + $lastId = Db::connect($config)->name('user')->insertGetId($data); + + $lastInsId = Db::connect($config)->name('user')->getLastInsID(); + $this->assertEquals($lastId, $lastInsId); + } + + public function testSetField() + { + $config = $this->getConfig(); + + $setFieldNum = Db::connect($config)->name('user')->where('id', 1)->setField('username', 'chunice_setField'); + $this->assertEquals(1, $setFieldNum); + + $setFieldNum = Db::connect($config)->name('user')->where('id', 1)->setField('username', 'chunice_setField'); + $this->assertEquals(0, $setFieldNum); + } + + public function testSetInc() + { + $config = $this->getConfig(); + $originCreateTime = Db::connect($config)->name('user')->where('id', 1)->value('create_time'); + Db::connect($config)->name('user')->where('id', 1)->setInc('create_time'); + $newCreateTime = Db::connect($config)->name('user')->where('id', 1)->value('create_time'); + $this->assertEquals($originCreateTime + 1, $newCreateTime); + + } + + public function testSetDec() + { + $config = $this->getConfig(); + $originCreateTime = Db::connect($config)->name('user')->where('id', 1)->value('create_time'); + Db::connect($config)->name('user')->where('id', 1)->setDec('create_time'); + $newCreateTime = Db::connect($config)->name('user')->where('id', 1)->value('create_time'); + $this->assertEquals($originCreateTime - 1, $newCreateTime); + } + + public function testDelete() + { + $config = $this->getConfig(); + Db::connect($config)->name('user')->where('id', 1)->delete(); + $result = Db::connect($config)->name('user')->where('id', 1)->find(); + $this->assertEmpty($result); + } + + public function testChunk() + { + // todo 暂未想到测试方法 + } + + public function testCache() + { + $config = $this->getConfig(); + $result = Db::connect($config)->name('user')->where('id', 1)->cache('key', 60)->find(); + $cache = \think\Cache::get('key'); + $this->assertEquals($result, $cache); + + $updateCache = Db::connect($config)->name('user')->cache('key')->find(1); + $this->assertEquals($cache, $updateCache); + } + +} diff --git a/thinkphp/tests/thinkphp/library/think/debugTest.php b/thinkphp/tests/thinkphp/library/think/debugTest.php new file mode 100644 index 0000000..36e1f04 --- /dev/null +++ b/thinkphp/tests/thinkphp/library/think/debugTest.php @@ -0,0 +1,220 @@ + +// +---------------------------------------------------------------------- + +/** + * Debug测试 + * @author 大漠 + */ + +namespace tests\thinkphp\library\think; + +use tests\thinkphp\library\think\config\ConfigInitTrait; +use think\Config; +use think\Debug; +use think\Response; +use think\response\Redirect; + +class debugTest extends \PHPUnit_Framework_TestCase +{ + use ConfigInitTrait; + + /** + * + * @var Debug + */ + protected $object; + + /** + * Sets up the fixture, for example, opens a network connection. + * This method is called before a test is executed. + */ + protected function setUp() + { + $this->object = new Debug(); + } + + /** + * Tears down the fixture, for example, closes a network connection. + * This method is called after a test is executed. + */ + protected function tearDown() + {} + + /** + * @covers \think\Debug::remark + * @todo Implement testRemark(). + */ + public function testRemark() + { + $name = "testremarkkey"; + Debug::remark($name); + } + + /** + * @covers \think\Debug::getRangeTime + * @todo Implement testGetRangeTime(). + */ + public function testGetRangeTime() + { + $start = "testGetRangeTimeStart"; + $end = "testGetRangeTimeEnd"; + Debug::remark($start); + usleep(20000); + // \think\Debug::remark($end); + + $time = Debug::getRangeTime($start, $end); + $this->assertLessThan(0.03, $time); + //$this->assertEquals(0.03, ceil($time)); + } + + /** + * @covers \think\Debug::getUseTime + * @todo Implement testGetUseTime(). + */ + public function testGetUseTime() + { + $time = Debug::getUseTime(); + $this->assertLessThan(30, $time); + } + + /** + * @covers \think\Debug::getThroughputRate + * @todo Implement testGetThroughputRate(). + */ + public function testGetThroughputRate() + { + usleep(100000); + $throughputRate = Debug::getThroughputRate(); + $this->assertLessThan(10, $throughputRate); + } + + /** + * @covers \think\Debug::getRangeMem + * @todo Implement testGetRangeMem(). + */ + public function testGetRangeMem() + { + $start = "testGetRangeMemStart"; + $end = "testGetRangeMemEnd"; + Debug::remark($start); + $str = ""; + for ($i = 0; $i < 10000; $i++) { + $str .= "mem"; + } + + $rangeMem = Debug::getRangeMem($start, $end); + + $this->assertLessThan(33, explode(" ", $rangeMem)[0]); + } + + /** + * @covers \think\Debug::getUseMem + * @todo Implement testGetUseMem(). + */ + public function testGetUseMem() + { + $useMem = Debug::getUseMem(); + + $this->assertLessThan(35, explode(" ", $useMem)[0]); + } + + /** + * @covers \think\Debug::getMemPeak + * @todo Implement testGetMemPeak(). + */ + public function testGetMemPeak() + { + $start = "testGetMemPeakStart"; + $end = "testGetMemPeakEnd"; + Debug::remark($start); + $str = ""; + for ($i = 0; $i < 100000; $i++) { + $str .= "mem"; + } + $memPeak = Debug::getMemPeak($start, $end); + $this->assertLessThan(500, explode(" ", $memPeak)[0]); + } + + /** + * @covers \think\Debug::getFile + * @todo Implement testGetFile(). + */ + public function testGetFile() + { + $count = Debug::getFile(); + + $this->assertEquals(count(get_included_files()), $count); + + $info = Debug::getFile(true); + $this->assertEquals(count(get_included_files()), count($info)); + + $this->assertContains("KB", $info[0]); + } + + /** + * @covers \think\Debug::dump + * @todo Implement testDump(). + */ + public function testDump() + { + if (strstr(PHP_VERSION, 'hhvm')) { + return; + } + + $var = []; + $var["key"] = "val"; + $output = Debug::dump($var, false, $label = "label"); + $array = explode("array", json_encode($output)); + if (IS_WIN) { + $this->assertEquals("(1) {\\n [\\\"key\\\"] => string(3) \\\"val\\\"\\n}\\n\\r\\n\"", end($array)); + } elseif (strstr(PHP_OS, 'Darwin')) { + $this->assertEquals("(1) {\\n [\\\"key\\\"] => string(3) \\\"val\\\"\\n}\\n\\n\"", end($array)); + } else { + $this->assertEquals("(1) {\\n 'key' =>\\n string(3) \\\"val\\\"\\n}\\n\\n\"", end($array)); + } + } + + /** + * @expectedException \think\exception\ClassNotFoundException + */ + public function testInjectWithErrorType() + { + Config::set('trace', ['type' => 'NullDebug']); + + $response = new Response(); + $context = 'TestWithErrorType'; + + Debug::inject($response, $context); + } + + public function testInject() + { + Config::set('trace', ['type' => 'Console']); + + $response = new Response(); + $context = 'TestWithoutBodyTag'; + Debug::inject($response, $context); + $this->assertNotEquals('TestWithoutBodyTag', $context); + $this->assertStringStartsWith('TestWithoutBodyTag', $context); + + $response = new Response(); + $context = ''; + Debug::inject($response, $context); + $this->assertNotEquals('', $context); + $this->assertStringStartsWith('', $context); + $this->assertStringEndsWith('', $context); + + $response = new Redirect(); + $context = ''; + Debug::inject($response, $context); + $this->assertEquals('', $context); + } +} diff --git a/thinkphp/tests/thinkphp/library/think/exceptionTest.php b/thinkphp/tests/thinkphp/library/think/exceptionTest.php new file mode 100644 index 0000000..66957ce --- /dev/null +++ b/thinkphp/tests/thinkphp/library/think/exceptionTest.php @@ -0,0 +1,52 @@ + +// +---------------------------------------------------------------------- + +/** + * exception类测试 + * @author Haotong Lin + */ + +namespace tests\thinkphp\library\think; + +use ReflectionMethod; +use think\Exception as ThinkException; +use think\exception\HttpException; + +class MyException extends ThinkException +{ + +} + +class exceptionTest extends \PHPUnit_Framework_TestCase +{ + public function testGetHttpStatus() + { + try { + throw new HttpException(404, "Error Processing Request"); + } catch (HttpException $e) { + $this->assertEquals(404, $e->getStatusCode()); + } + } + + public function testDebugData() + { + $data = ['a' => 'b', 'c' => 'd']; + try { + $e = new MyException("Error Processing Request", 1); + $method = new ReflectionMethod($e, 'setData'); + $method->setAccessible(true); + $method->invokeArgs($e, ['test', $data]); + throw $e; + } catch (MyException $e) { + $this->assertEquals(['test' => $data], $e->getData()); + } + } +} diff --git a/thinkphp/tests/thinkphp/library/think/hookTest.php b/thinkphp/tests/thinkphp/library/think/hookTest.php new file mode 100644 index 0000000..930ecf5 --- /dev/null +++ b/thinkphp/tests/thinkphp/library/think/hookTest.php @@ -0,0 +1,67 @@ + +// +---------------------------------------------------------------------- + +/** + * Hook类测试 + * @author liu21st + */ + +namespace tests\thinkphp\library\think; + +use think\Hook; + +class hookTest extends \PHPUnit_Framework_TestCase +{ + + public function testRun() + { + Hook::add('my_pos', '\tests\thinkphp\library\think\behavior\One'); + Hook::add('my_pos', ['\tests\thinkphp\library\think\behavior\Two']); + Hook::add('my_pos', '\tests\thinkphp\library\think\behavior\Three', true); + $data['id'] = 0; + $data['name'] = 'thinkphp'; + Hook::listen('my_pos', $data); + $this->assertEquals(2, $data['id']); + $this->assertEquals('thinkphp', $data['name']); + $this->assertEquals([ + '\tests\thinkphp\library\think\behavior\Three', + '\tests\thinkphp\library\think\behavior\One', + '\tests\thinkphp\library\think\behavior\Two'], + Hook::get('my_pos')); + } + + public function testImport() + { + Hook::import(['my_pos' => [ + '\tests\thinkphp\library\think\behavior\One', + '\tests\thinkphp\library\think\behavior\Three'], + ]); + Hook::import(['my_pos' => ['\tests\thinkphp\library\think\behavior\Two']], false); + Hook::import(['my_pos' => ['\tests\thinkphp\library\think\behavior\Three', '_overlay' => true]]); + $data['id'] = 0; + $data['name'] = 'thinkphp'; + Hook::listen('my_pos', $data); + $this->assertEquals(3, $data['id']); + + } + + public function testExec() + { + $data['id'] = 0; + $data['name'] = 'thinkphp'; + $this->assertEquals(true, Hook::exec('\tests\thinkphp\library\think\behavior\One')); + $this->assertEquals(false, Hook::exec('\tests\thinkphp\library\think\behavior\One', 'test', $data)); + $this->assertEquals('test', $data['name']); + $this->assertEquals('Closure', Hook::exec(function (&$data) {$data['name'] = 'Closure';return 'Closure';})); + + } + +} diff --git a/thinkphp/tests/thinkphp/library/think/lang/lang.php b/thinkphp/tests/thinkphp/library/think/lang/lang.php new file mode 100644 index 0000000..96880b1 --- /dev/null +++ b/thinkphp/tests/thinkphp/library/think/lang/lang.php @@ -0,0 +1,4 @@ +'加载', +]; diff --git a/thinkphp/tests/thinkphp/library/think/langTest.php b/thinkphp/tests/thinkphp/library/think/langTest.php new file mode 100644 index 0000000..360ee43 --- /dev/null +++ b/thinkphp/tests/thinkphp/library/think/langTest.php @@ -0,0 +1,76 @@ + +// +---------------------------------------------------------------------- + +/** + * Lang测试 + * @author liu21st + */ + +namespace tests\thinkphp\library\think; + +use think\Config; +use think\Lang; + +class langTest extends \PHPUnit_Framework_TestCase +{ + + public function testSetAndGet() + { + Lang::set('hello,%s', '欢迎,%s'); + $this->assertEquals('欢迎,ThinkPHP', Lang::get('hello,%s', ['ThinkPHP'])); + Lang::set('hello,%s', '歡迎,%s', 'zh-tw'); + $this->assertEquals('歡迎,ThinkPHP', Lang::get('hello,%s', ['ThinkPHP'], 'zh-tw')); + Lang::set(['hello' => '欢迎', 'use' => '使用']); + $this->assertEquals('欢迎', Lang::get('hello')); + $this->assertEquals('欢迎', Lang::get('HELLO')); + $this->assertEquals('使用', Lang::get('use')); + + Lang::set('hello,{:name}', '欢迎,{:name}'); + $this->assertEquals('欢迎,liu21st', Lang::get('hello,{:name}', ['name' => 'liu21st'])); + } + + public function testLoad() + { + Lang::load(__DIR__ . DS . 'lang' . DS . 'lang.php'); + $this->assertEquals('加载', Lang::get('load')); + Lang::load(__DIR__ . DS . 'lang' . DS . 'lang.php', 'test'); + $this->assertEquals('加载', Lang::get('load', [], 'test')); + } + + public function testDetect() + { + + Config::set('lang_list', ['zh-cn', 'zh-tw']); + Lang::set('hello', '欢迎', 'zh-cn'); + Lang::set('hello', '歡迎', 'zh-tw'); + + Config::set('lang_detect_var', 'lang'); + Config::set('lang_cookie_var', 'think_cookie'); + + $_GET['lang'] = 'zh-tw'; + Lang::detect(); + $this->assertEquals('歡迎', Lang::get('hello')); + + $_GET['lang'] = 'zh-cn'; + Lang::detect(); + $this->assertEquals('欢迎', Lang::get('hello')); + + } + + public function testRange() + { + $this->assertEquals('zh-cn', Lang::range()); + Lang::set('hello', '欢迎', 'test'); + Lang::range('test'); + $this->assertEquals('test', Lang::range()); + $this->assertEquals('欢迎', Lang::get('hello')); + } +} diff --git a/thinkphp/tests/thinkphp/library/think/loader/test/Hello.php b/thinkphp/tests/thinkphp/library/think/loader/test/Hello.php new file mode 100644 index 0000000..c141456 --- /dev/null +++ b/thinkphp/tests/thinkphp/library/think/loader/test/Hello.php @@ -0,0 +1,7 @@ + +// +---------------------------------------------------------------------- + +/** + * Loader测试 + * @author liu21st + */ + +namespace tests\thinkphp\library\think; + +use think\Loader; + +class loaderTest extends \PHPUnit_Framework_TestCase +{ + + public function testAutoload() + { + $this->assertEquals(false, Loader::autoload('\think\Url')); + $this->assertEquals(false, Loader::autoload('think\Test')); + $this->assertEquals(false, Loader::autoload('my\HelloTest')); + } + + public function testAddClassMap() + { + Loader::addClassMap('my\hello\Test', __DIR__ . DS . 'loader' . DS . 'Test.php'); + } + + public function testAddNamespace() + { + Loader::addNamespace('top', __DIR__ . DS . 'loader' . DS); + $this->assertEquals(true, Loader::autoload('top\test\Hello')); + } + + public function testAddNamespaceAlias() + { + Loader::addNamespaceAlias('top', 'top\test'); + Loader::addNamespaceAlias(['top' => 'top\test', 'app' => 'app\index']); + //$this->assertEquals(true, Loader::autoload('top\Hello')); + } + + public function testTable() + { + Loader::db('mysql://root@127.0.0.1/test#utf8'); + } + + public function testImport() + { + $this->assertEquals(false, Loader::import('think.log.driver.MyTest')); + } + + public function testParseName() + { + $this->assertEquals('HelloTest', Loader::parseName('hello_test', 1)); + $this->assertEquals('hello_test', Loader::parseName('HelloTest', 0)); + } + + public function testParseClass() + { + $this->assertEquals('app\index\controller\User', Loader::parseClass('index', 'controller', 'user')); + $this->assertEquals('app\index\controller\user\Type', Loader::parseClass('index', 'controller', 'user.type')); + $this->assertEquals('app\admin\model\UserType', Loader::parseClass('admin', 'model', 'user_type')); + } +} diff --git a/thinkphp/tests/thinkphp/library/think/log/driver/fileTest.php b/thinkphp/tests/thinkphp/library/think/log/driver/fileTest.php new file mode 100644 index 0000000..ad6dd22 --- /dev/null +++ b/thinkphp/tests/thinkphp/library/think/log/driver/fileTest.php @@ -0,0 +1,34 @@ + +// +---------------------------------------------------------------------- + +/** + * Test File Log + */ +namespace tests\thinkphp\library\think\log\driver; + +use think\Log; + +class fileTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + Log::init(['type' => 'file']); + } + + public function testRecord() + { + $record_msg = 'record'; + Log::record($record_msg, 'notice'); + $logs = Log::getLog(); + + $this->assertEquals([], $logs); + } +} diff --git a/thinkphp/tests/thinkphp/library/think/logTest.php b/thinkphp/tests/thinkphp/library/think/logTest.php new file mode 100644 index 0000000..e786b17 --- /dev/null +++ b/thinkphp/tests/thinkphp/library/think/logTest.php @@ -0,0 +1,39 @@ + +// +---------------------------------------------------------------------- + +/** + * Log测试 + * @author liu21st + */ +namespace tests\thinkphp\library\think; + +use think\Log; + +class logTest extends \PHPUnit_Framework_TestCase +{ + + public function testSave() + { + Log::init(['type' => 'test']); + Log::clear(); + Log::record('test'); + Log::record([1, 2, 3]); + $this->assertTrue(Log::save()); + } + + public function testWrite() + { + Log::init(['type' => 'test']); + Log::clear(); + $this->assertTrue(Log::write('hello', 'info')); + $this->assertTrue(Log::write([1, 2, 3], 'log')); + } +} diff --git a/thinkphp/tests/thinkphp/library/think/model/.gitignore b/thinkphp/tests/thinkphp/library/think/model/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/thinkphp/tests/thinkphp/library/think/model/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/thinkphp/tests/thinkphp/library/think/paginateTest.php b/thinkphp/tests/thinkphp/library/think/paginateTest.php new file mode 100644 index 0000000..8cd4550 --- /dev/null +++ b/thinkphp/tests/thinkphp/library/think/paginateTest.php @@ -0,0 +1,40 @@ + +// +---------------------------------------------------------------------- + +namespace tests\thinkphp\library\think; + +use think\paginator\driver\Bootstrap; + +class paginateTest extends \PHPUnit_Framework_TestCase +{ + public function testPaginatorInfo() + { + $p = Bootstrap::make($array = ['item3', 'item4'], 2, 2, 4); + + $this->assertEquals(4, $p->total()); + + $this->assertEquals(2, $p->listRows()); + + $this->assertEquals(2, $p->currentPage()); + + $p2 = Bootstrap::make($array2 = ['item3', 'item4'], 2, 2, 2); + $this->assertEquals(1, $p2->currentPage()); + } + + public function testPaginatorRender() + { + $p = Bootstrap::make($array = ['item3', 'item4'], 2, 2, 100); + $render = ''; + + $this->assertEquals($render, $p->render()); + } + +} diff --git a/thinkphp/tests/thinkphp/library/think/requestTest.php b/thinkphp/tests/thinkphp/library/think/requestTest.php new file mode 100644 index 0000000..3da94a2 --- /dev/null +++ b/thinkphp/tests/thinkphp/library/think/requestTest.php @@ -0,0 +1,203 @@ + +// +---------------------------------------------------------------------- + +/** + * Db类测试 + */ + +namespace tests\thinkphp\library\think; + +use think\Config; +use think\Request; + +class requestTest extends \PHPUnit_Framework_TestCase +{ + protected $request; + + public function setUp() + { + //$request = Request::create('http://www.domain.com/index/index/hello/?name=thinkphp'); + + } + + public function testCreate() + { + $request = Request::create('http://www.thinkphp.cn/index/index/hello.html?name=thinkphp'); + $this->assertEquals('http://www.thinkphp.cn', $request->domain()); + $this->assertEquals('/index/index/hello.html?name=thinkphp', $request->url()); + $this->assertEquals('/index/index/hello.html', $request->baseurl()); + $this->assertEquals('index/index/hello.html', $request->pathinfo()); + $this->assertEquals('index/index/hello', $request->path()); + $this->assertEquals('html', $request->ext()); + $this->assertEquals('name=thinkphp', $request->query()); + $this->assertEquals('www.thinkphp.cn', $request->host()); + $this->assertEquals(80, $request->port()); + $this->assertEquals($_SERVER['REQUEST_TIME'], $request->time()); + $this->assertEquals($_SERVER['REQUEST_TIME_FLOAT'], $request->time(true)); + $this->assertEquals('GET', $request->method()); + $this->assertEquals(['name' => 'thinkphp'], $request->param()); + $this->assertFalse($request->isSsl()); + $this->assertEquals('http', $request->scheme()); + } + + public function testDomain() + { + $request = Request::instance(); + $request->domain('http://thinkphp.cn'); + $this->assertEquals('http://thinkphp.cn', $request->domain()); + } + + public function testUrl() + { + $request = Request::instance(); + $request->url('/index.php/index/hello?name=thinkphp'); + $this->assertEquals('/index.php/index/hello?name=thinkphp', $request->url()); + $this->assertEquals('http://thinkphp.cn/index.php/index/hello?name=thinkphp', $request->url(true)); + } + + public function testBaseUrl() + { + $request = Request::instance(); + $request->baseurl('/index.php/index/hello'); + $this->assertEquals('/index.php/index/hello', $request->baseurl()); + $this->assertEquals('http://thinkphp.cn/index.php/index/hello', $request->baseurl(true)); + } + + public function testbaseFile() + { + $request = Request::instance(); + $request->basefile('/index.php'); + $this->assertEquals('/index.php', $request->basefile()); + $this->assertEquals('http://thinkphp.cn/index.php', $request->basefile(true)); + } + + public function testroot() + { + $request = Request::instance(); + $request->root('/index.php'); + $this->assertEquals('/index.php', $request->root()); + $this->assertEquals('http://thinkphp.cn/index.php', $request->root(true)); + } + + public function testType() + { + $request = Request::instance(); + $request->server(['HTTP_ACCEPT' => 'application/json']); + + $this->assertEquals('json', $request->type()); + $request->mimeType('test', 'application/test'); + $request->mimeType(['test' => 'application/test']); + $request->server(['HTTP_ACCEPT' => 'application/test']); + + $this->assertEquals('test', $request->type()); + } + + public function testmethod() + { + $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] = 'DELETE'; + + $request = Request::create('', ''); + $this->assertEquals('DELETE', $request->method()); + $this->assertEquals('GET', $request->method(true)); + + Config::set('var_method', '_method'); + $_POST['_method'] = 'POST'; + $request = Request::create('', ''); + $this->assertEquals('POST', $request->method()); + $this->assertEquals('GET', $request->method(true)); + $this->assertTrue($request->isPost()); + $this->assertFalse($request->isGet()); + $this->assertFalse($request->isPut()); + $this->assertFalse($request->isDelete()); + $this->assertFalse($request->isHead()); + $this->assertFalse($request->isPatch()); + $this->assertFalse($request->isOptions()); + } + + public function testCli() + { + $request = Request::instance(); + $this->assertTrue($request->isCli()); + } + + public function testVar() + { + Config::set('app_multi_module', true); + $request = Request::create(''); + $request->route(['name' => 'thinkphp', 'id' => 6]); + $request->get(['id' => 10]); + $request->post(['id' => 8]); + $request->put(['id' => 7]); + $request->request(['test' => 'value']); + $this->assertEquals(['name' => 'thinkphp', 'id' => 6], $request->route()); + //$this->assertEquals(['id' => 10], $request->get()); + $this->assertEquals('thinkphp', $request->route('name')); + $this->assertEquals('default', $request->route('test', 'default')); + $this->assertEquals(10, $request->get('id')); + $this->assertEquals(0, $request->get('ids', 0)); + $this->assertEquals(8, $request->post('id')); + $this->assertEquals(7, $request->put('id')); + $this->assertEquals('value', $request->request('test')); + $this->assertEquals('thinkphp', $request->param('name')); + $this->assertEquals(6, $request->param('id')); + $this->assertFalse($request->has('user_id')); + $this->assertTrue($request->has('test', 'request')); + $this->assertEquals(['id' => 6], $request->only('id')); + $this->assertEquals(['name' => 'thinkphp', 'lang' => 'zh-cn'], $request->except('id')); + $this->assertEquals('THINKPHP', $request->param('name', '', 'strtoupper')); + } + + public function testIsAjax() + { + $request = Request::create(''); + + $this->assertFalse($request->isAjax()); + + $_SERVER['HTTP_X_REQUESTED_WITH'] = 'xmlhttprequest'; + $this->assertFalse($request->isAjax()); + $this->assertFalse($request->isAjax(true)); + + $request->server(['HTTP_X_REQUESTED_WITH' => 'xmlhttprequest']); + $this->assertTrue($request->isAjax()); + } + + public function testIsPjax() + { + $request = Request::create(''); + + $this->assertFalse($request->isPjax()); + + $_SERVER['HTTP_X_PJAX'] = true; + $this->assertFalse($request->isPjax()); + $this->assertFalse($request->isPjax(true)); + + $request->server(['HTTP_X_PJAX' => true]); + $this->assertTrue($request->isPjax()); + $request->server(['HTTP_X_PJAX' => false]); + $this->assertTrue($request->isPjax()); + } + + public function testIsMobile() + { + $request = Request::create(''); + $_SERVER['HTTP_VIA'] = 'wap'; + $this->assertTrue($request->isMobile()); + } + + public function testBind() + { + $request = Request::create(''); + $request->user = 'User1'; + $request->bind(['user' => 'User2']); + $this->assertEquals('User2', $request->user); + } + +} diff --git a/thinkphp/tests/thinkphp/library/think/responseTest.php b/thinkphp/tests/thinkphp/library/think/responseTest.php new file mode 100644 index 0000000..62d1574 --- /dev/null +++ b/thinkphp/tests/thinkphp/library/think/responseTest.php @@ -0,0 +1,95 @@ + +// +---------------------------------------------------------------------- + +/** + * Response测试 + * @author 大漠 + */ + +namespace tests\thinkphp\library\think; + +use think\Config; +use think\Request; +use think\Response; + +class responseTest extends \PHPUnit_Framework_TestCase +{ + + /** + * + * @var \think\Response + */ + protected $object; + + protected $default_return_type; + + protected $default_ajax_return; + + /** + * Sets up the fixture, for example, opens a network connection. + * This method is called before a test is executed. + */ + protected function setUp() + { + // 1. + // restore_error_handler(); + // Warning: Cannot modify header information - headers already sent by (output started at PHPUnit\Util\Printer.php:173) + // more see in https://www.analysisandsolutions.com/blog/html/writing-phpunit-tests-for-wordpress-plugins-wp-redirect-and-continuing-after-php-errors.htm + + // 2. + // the Symfony used the HeaderMock.php + + // 3. + // not run the eclipse will held, and travis-ci.org Searching for coverage reports + // **> Python coverage not found + // **> No coverage report found. + // add the + // /** + // * @runInSeparateProcess + // */ + if (!$this->default_return_type) { + $this->default_return_type = Config::get('default_return_type'); + } + if (!$this->default_ajax_return) { + $this->default_ajax_return = Config::get('default_ajax_return'); + } + } + + /** + * Tears down the fixture, for example, closes a network connection. + * This method is called after a test is executed. + */ + protected function tearDown() + { + Config::set('default_ajax_return', $this->default_ajax_return); + Config::set('default_return_type', $this->default_return_type); + } + + /** + * @covers think\Response::send + * @todo Implement testSend(). + */ + public function testSend() + { + $dataArr = []; + $dataArr["key"] = "value"; + + $response = Response::create($dataArr, 'json'); + $result = $response->getContent(); + $this->assertEquals('{"key":"value"}', $result); + $request = Request::instance(); + $request->get(['callback' => 'callback']); + $response = Response::create($dataArr, 'jsonp'); + $result = $response->getContent(); + $this->assertEquals('callback({"key":"value"});', $result); + } + +} diff --git a/thinkphp/tests/thinkphp/library/think/routeTest.php b/thinkphp/tests/thinkphp/library/think/routeTest.php new file mode 100644 index 0000000..31e1b86 --- /dev/null +++ b/thinkphp/tests/thinkphp/library/think/routeTest.php @@ -0,0 +1,287 @@ + +// +---------------------------------------------------------------------- + +/** + * Route测试 + * @author liu21st + */ + +namespace tests\thinkphp\library\think; + +use think\Config; +use think\Request; +use think\Route; + +class routeTest extends \PHPUnit_Framework_TestCase +{ + + protected function setUp() + { + Config::set('app_multi_module', true); + } + + public function testRegister() + { + $request = Request::instance(); + Route::get('hello/:name', 'index/hello'); + Route::get(['hello/:name' => 'index/hello']); + Route::post('hello/:name', 'index/post'); + Route::put('hello/:name', 'index/put'); + Route::delete('hello/:name', 'index/delete'); + Route::patch('hello/:name', 'index/patch'); + Route::any('user/:id', 'index/user'); + $result = Route::check($request, 'hello/thinkphp'); + $this->assertEquals([null, 'index', 'hello'], $result['module']); + $this->assertEquals(['hello' => true, 'user/:id' => true, 'hello/:name' => ['rule' => 'hello/:name', 'route' => 'index/hello', 'var' => ['name' => 1], 'option' => [], 'pattern' => []]], Route::rules('GET')); + Route::rule('type1/:name', 'index/type', 'PUT|POST'); + Route::rule(['type2/:name' => 'index/type1']); + Route::rule([['type3/:name', 'index/type2', ['method' => 'POST']]]); + Route::rule(['name', 'type4/:name'], 'index/type4'); + } + + public function testImport() + { + $rule = [ + '__domain__' => ['subdomain2.thinkphp.cn' => 'blog1'], + '__alias__' => ['blog1' => 'blog1'], + '__rest__' => ['res' => ['index/blog']], + 'bbb' => ['index/blog1', ['method' => 'POST']], + 'ddd' => '', + ['hello1/:ddd', 'index/hello1', ['method' => 'POST']], + ]; + Route::import($rule); + } + + public function testResource() + { + $request = Request::instance(); + Route::resource('res', 'index/blog'); + Route::resource(['res' => ['index/blog']]); + $result = Route::check($request, 'res'); + $this->assertEquals(['index', 'blog', 'index'], $result['module']); + $result = Route::check($request, 'res/create'); + $this->assertEquals(['index', 'blog', 'create'], $result['module']); + $result = Route::check($request, 'res/8'); + $this->assertEquals(['index', 'blog', 'read'], $result['module']); + $result = Route::check($request, 'res/8/edit'); + $this->assertEquals(['index', 'blog', 'edit'], $result['module']); + + Route::resource('blog.comment', 'index/comment'); + $result = Route::check($request, 'blog/8/comment/10'); + $this->assertEquals(['index', 'comment', 'read'], $result['module']); + $result = Route::check($request, 'blog/8/comment/10/edit'); + $this->assertEquals(['index', 'comment', 'edit'], $result['module']); + + } + + public function testRest() + { + $request = Request::instance(); + Route::rest('read', ['GET', '/:id', 'look']); + Route::rest('create', ['GET', '/create', 'add']); + Route::rest(['read' => ['GET', '/:id', 'look'], 'create' => ['GET', '/create', 'add']]); + Route::resource('res', 'index/blog'); + $result = Route::check($request, 'res/create'); + $this->assertEquals(['index', 'blog', 'add'], $result['module']); + $result = Route::check($request, 'res/8'); + $this->assertEquals(['index', 'blog', 'look'], $result['module']); + + } + + public function testMixVar() + { + $request = Request::instance(); + Route::get('hello-', 'index/hello', [], ['name' => '\w+']); + $result = Route::check($request, 'hello-thinkphp'); + $this->assertEquals([null, 'index', 'hello'], $result['module']); + Route::get('hello-', 'index/hello', [], ['name' => '\w+', 'id' => '\d+']); + $result = Route::check($request, 'hello-thinkphp2016'); + $this->assertEquals([null, 'index', 'hello'], $result['module']); + Route::get('hello-/[:id]', 'index/hello', [], ['name' => '\w+', 'id' => '\d+']); + $result = Route::check($request, 'hello-thinkphp/2016'); + $this->assertEquals([null, 'index', 'hello'], $result['module']); + } + + public function testParseUrl() + { + $result = Route::parseUrl('hello'); + $this->assertEquals(['hello', null, null], $result['module']); + $result = Route::parseUrl('index/hello'); + $this->assertEquals(['index', 'hello', null], $result['module']); + $result = Route::parseUrl('index/hello?name=thinkphp'); + $this->assertEquals(['index', 'hello', null], $result['module']); + $result = Route::parseUrl('index/user/hello'); + $this->assertEquals(['index', 'user', 'hello'], $result['module']); + $result = Route::parseUrl('index/user/hello/name/thinkphp'); + $this->assertEquals(['index', 'user', 'hello'], $result['module']); + $result = Route::parseUrl('index-index-hello', '-'); + $this->assertEquals(['index', 'index', 'hello'], $result['module']); + } + + public function testCheckRoute() + { + Route::get('hello/:name', 'index/hello'); + Route::get('blog/:id', 'blog/read', [], ['id' => '\d+']); + $request = Request::instance(); + $this->assertEquals(false, Route::check($request, 'test/thinkphp')); + $this->assertEquals(false, Route::check($request, 'blog/thinkphp')); + $result = Route::check($request, 'blog/5'); + $this->assertEquals([null, 'blog', 'read'], $result['module']); + $result = Route::check($request, 'hello/thinkphp/abc/test'); + $this->assertEquals([null, 'index', 'hello'], $result['module']); + } + + public function testCheckRouteGroup() + { + $request = Request::instance(); + Route::pattern(['id' => '\d+']); + Route::pattern('name', '\w{6,25}'); + Route::group('group', [':id' => 'index/hello', ':name' => 'index/say']); + $this->assertEquals(false, Route::check($request, 'empty/think')); + $result = Route::check($request, 'group/think'); + $this->assertEquals(false, $result['module']); + $result = Route::check($request, 'group/10'); + $this->assertEquals([null, 'index', 'hello'], $result['module']); + $result = Route::check($request, 'group/thinkphp'); + $this->assertEquals([null, 'index', 'say'], $result['module']); + Route::group('group2', function () { + Route::group('group3', [':id' => 'index/hello', ':name' => 'index/say']); + Route::rule(':name', 'index/hello'); + Route::auto('index'); + }); + $result = Route::check($request, 'group2/thinkphp'); + $this->assertEquals([null, 'index', 'hello'], $result['module']); + $result = Route::check($request, 'group2/think'); + $this->assertEquals(['index', 'group2', 'think'], $result['module']); + $result = Route::check($request, 'group2/group3/thinkphp'); + $this->assertEquals([null, 'index', 'say'], $result['module']); + Route::group('group4', function () { + Route::group('group3', [':id' => 'index/hello', ':name' => 'index/say']); + Route::rule(':name', 'index/hello'); + Route::miss('index/__miss__'); + }); + $result = Route::check($request, 'group4/thinkphp'); + $this->assertEquals([null, 'index', 'hello'], $result['module']); + $result = Route::check($request, 'group4/think'); + $this->assertEquals([null, 'index', '__miss__'], $result['module']); + + Route::group(['prefix' => 'prefix/'], function () { + Route::rule('hello4/:name', 'hello'); + }); + Route::group(['prefix' => 'prefix/'], [ + 'hello4/:name' => 'hello', + ]); + $result = Route::check($request, 'hello4/thinkphp'); + $this->assertEquals([null, 'prefix', 'hello'], $result['module']); + Route::group('group5', [ + [':name', 'hello', ['method' => 'GET|POST']], + ':id' => 'hello', + ], ['prefix' => 'prefix/']); + $result = Route::check($request, 'group5/thinkphp'); + $this->assertEquals([null, 'prefix', 'hello'], $result['module']); + } + + public function testControllerRoute() + { + $request = Request::instance(); + Route::controller('controller', 'index/Blog'); + $result = Route::check($request, 'controller/info'); + $this->assertEquals(['index', 'Blog', 'getinfo'], $result['module']); + Route::setMethodPrefix('GET', 'read'); + Route::setMethodPrefix(['get' => 'read']); + Route::controller('controller', 'index/Blog'); + $result = Route::check($request, 'controller/phone'); + $this->assertEquals(['index', 'Blog', 'readphone'], $result['module']); + } + + public function testAliasRoute() + { + $request = Request::instance(); + Route::alias('alias', 'index/Alias'); + $result = Route::check($request, 'alias/info'); + $this->assertEquals('index/Alias/info', $result['module']); + } + + public function testRouteToModule() + { + $request = Request::instance(); + Route::get('hello/:name', 'index/hello'); + Route::get('blog/:id', 'blog/read', [], ['id' => '\d+']); + $this->assertEquals(false, Route::check($request, 'test/thinkphp')); + $this->assertEquals(false, Route::check($request, 'blog/thinkphp')); + $result = Route::check($request, 'hello/thinkphp'); + $this->assertEquals([null, 'index', 'hello'], $result['module']); + $result = Route::check($request, 'blog/5'); + $this->assertEquals([null, 'blog', 'read'], $result['module']); + } + + public function testRouteToController() + { + $request = Request::instance(); + Route::get('say/:name', '@index/hello'); + $this->assertEquals(['type' => 'controller', 'controller' => 'index/hello', 'var' => []], Route::check($request, 'say/thinkphp')); + } + + public function testRouteToMethod() + { + $request = Request::instance(); + Route::get('user/:name', '\app\index\service\User::get', [], ['name' => '\w+']); + Route::get('info/:name', '\app\index\model\Info@getInfo', [], ['name' => '\w+']); + $this->assertEquals(['type' => 'method', 'method' => '\app\index\service\User::get', 'var' => []], Route::check($request, 'user/thinkphp')); + $this->assertEquals(['type' => 'method', 'method' => ['\app\index\model\Info', 'getInfo'], 'var' => []], Route::check($request, 'info/thinkphp')); + } + + public function testRouteToRedirect() + { + $request = Request::instance(); + Route::get('art/:id', '/article/read/id/:id', [], ['id' => '\d+']); + $this->assertEquals(['type' => 'redirect', 'url' => '/article/read/id/8', 'status' => 301], Route::check($request, 'art/8')); + } + + public function testBind() + { + $request = Request::instance(); + Route::bind('index/blog'); + Route::get('blog/:id', 'index/blog/read'); + $result = Route::check($request, 'blog/10'); + $this->assertEquals(['index', 'blog', 'read'], $result['module']); + $result = Route::parseUrl('test'); + $this->assertEquals(['index', 'blog', 'test'], $result['module']); + + Route::bind('\app\index\controller', 'namespace'); + $this->assertEquals(['type' => 'method', 'method' => ['\app\index\controller\Blog', 'read'], 'var' => []], Route::check($request, 'blog/read')); + + Route::bind('\app\index\controller\Blog', 'class'); + $this->assertEquals(['type' => 'method', 'method' => ['\app\index\controller\Blog', 'read'], 'var' => []], Route::check($request, 'read')); + } + + public function testDomain() + { + $request = Request::create('http://subdomain.thinkphp.cn'); + Route::domain('subdomain.thinkphp.cn', 'sub?abc=test&status=1'); + $rules = Route::rules('GET'); + Route::checkDomain($request, $rules); + $this->assertEquals('sub', Route::getbind('module')); + $this->assertEquals('test', $_GET['abc']); + $this->assertEquals(1, $_GET['status']); + + Route::domain('subdomain.thinkphp.cn', '\app\index\controller'); + $rules = Route::rules('GET'); + Route::checkDomain($request, $rules); + $this->assertEquals('\app\index\controller', Route::getbind('namespace')); + + Route::domain(['subdomain.thinkphp.cn' => '@\app\index\controller\blog']); + $rules = Route::rules('GET'); + Route::checkDomain($request, $rules); + $this->assertEquals('\app\index\controller\blog', Route::getbind('class')); + + } +} diff --git a/thinkphp/tests/thinkphp/library/think/session/.gitignore b/thinkphp/tests/thinkphp/library/think/session/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/thinkphp/tests/thinkphp/library/think/session/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/thinkphp/tests/thinkphp/library/think/sessionTest.php b/thinkphp/tests/thinkphp/library/think/sessionTest.php new file mode 100644 index 0000000..5fd95f1 --- /dev/null +++ b/thinkphp/tests/thinkphp/library/think/sessionTest.php @@ -0,0 +1,319 @@ + +// +---------------------------------------------------------------------- + +/** + * Session测试 + * @author 大漠 + */ + +namespace tests\thinkphp\library\think; + +use think\Session; + +class sessionTest extends \PHPUnit_Framework_TestCase +{ + + /** + * + * @var \think\Session + */ + protected $object; + + /** + * Sets up the fixture, for example, opens a network connection. + * This method is called before a test is executed. + */ + protected function setUp() + { + // $this->object = new Session (); + // register_shutdown_function ( function () { + // } ); // 此功能无法取消,需要回调函数配合。 + set_exception_handler(function () {}); + set_error_handler(function () {}); + } + + /** + * Tears down the fixture, for example, closes a network connection. + * This method is called after a test is executed. + */ + protected function tearDown() + { + register_shutdown_function('think\Error::appShutdown'); + set_error_handler('think\Error::appError'); + set_exception_handler('think\Error::appException'); + } + + /** + * @covers think\Session::prefix + * + * @todo Implement testPrefix(). + */ + public function testPrefix() + { + Session::prefix(null); + Session::prefix('think_'); + + $this->assertEquals('think_', Session::prefix()); + } + + /** + * @covers think\Session::init + * + * @todo Implement testInit(). + */ + public function testInit() + { + Session::prefix(null); + $config = [ + // cookie 名称前缀 + 'prefix' => 'think_', + // cookie 保存时间 + 'expire' => 60, + // cookie 保存路径 + 'path' => '/path/to/test/session/', + // cookie 有效域名 + 'domain' => '.thinkphp.cn', + 'var_session_id' => 'sessionidtest', + 'id' => 'sess_8fhgkjuakhatbeg2fa14lo84q1', + 'name' => 'session_name', + 'use_trans_sid' => '1', + 'use_cookies' => '1', + 'cache_limiter' => '60', + 'cache_expire' => '60', + 'type' => '', // memcache + 'namespace' => '\\think\\session\\driver\\', // ? + 'auto_start' => '1', + ]; + + $_REQUEST[$config['var_session_id']] = $config['id']; + Session::init($config); + + // 开始断言 + $this->assertEquals($config['prefix'], Session::prefix()); + $this->assertEquals($config['id'], $_REQUEST[$config['var_session_id']]); + $this->assertEquals($config['name'], session_name()); + + $this->assertEquals($config['path'], session_save_path()); + $this->assertEquals($config['use_cookies'], ini_get('session.use_cookies')); + $this->assertEquals($config['domain'], ini_get('session.cookie_domain')); + $this->assertEquals($config['expire'], ini_get('session.gc_maxlifetime')); + $this->assertEquals($config['expire'], ini_get('session.cookie_lifetime')); + + $this->assertEquals($config['cache_limiter'], session_cache_limiter($config['cache_limiter'])); + $this->assertEquals($config['cache_expire'], session_cache_expire($config['cache_expire'])); + + // 检测分支 + $_REQUEST[$config['var_session_id']] = null; + session_write_close(); + session_destroy(); + + Session::init($config); + + // 测试auto_start + // PHP_SESSION_DISABLED + // PHP_SESSION_NONE + // PHP_SESSION_ACTIVE + // session_status() + if (strstr(PHP_VERSION, 'hhvm')) { + $this->assertEquals('', ini_get('session.auto_start')); + } else { + $this->assertEquals(0, ini_get('session.auto_start')); + } + + $this->assertEquals($config['use_trans_sid'], ini_get('session.use_trans_sid')); + + Session::init($config); + $this->assertEquals($config['id'], session_id()); + } + + /** + * 单独重现异常 + * @expectedException \think\Exception + */ + public function testException() + { + $config = [ + // cookie 名称前缀 + 'prefix' => 'think_', + // cookie 保存时间 + 'expire' => 0, + // cookie 保存路径 + 'path' => '/path/to/test/session/', + // cookie 有效域名 + 'domain' => '.thinkphp.cn', + 'var_session_id' => 'sessionidtest', + 'id' => 'sess_8fhgkjuakhatbeg2fa14lo84q1', + 'name' => 'session_name', + 'use_trans_sid' => '1', + 'use_cookies' => '1', + 'cache_limiter' => '60', + 'cache_expire' => '60', + 'type' => '\\think\\session\\driver\\Memcache', // + 'auto_start' => '1', + ]; + + // 测试session驱动是否存在 + // @expectedException 异常类名 + $this->setExpectedException('\think\exception\ClassNotFoundException', 'error session handler'); + + Session::init($config); + } + + /** + * @covers think\Session::set + * + * @todo Implement testSet(). + */ + public function testSet() + { + Session::prefix(null); + Session::set('sessionname', 'sessionvalue'); + $this->assertEquals('sessionvalue', $_SESSION['sessionname']); + + Session::set('sessionnamearr.subname', 'sessionvalue'); + $this->assertEquals('sessionvalue', $_SESSION['sessionnamearr']['subname']); + + Session::set('sessionnameper', 'sessionvalue', 'think_'); + $this->assertEquals('sessionvalue', $_SESSION['think_']['sessionnameper']); + + Session::set('sessionnamearrper.subname', 'sessionvalue', 'think_'); + $this->assertEquals('sessionvalue', $_SESSION['think_']['sessionnamearrper']['subname']); + } + + /** + * @covers think\Session::get + * + * @todo Implement testGet(). + */ + public function testGet() + { + Session::prefix(null); + + Session::set('sessionnameget', 'sessionvalue'); + $this->assertEquals(Session::get('sessionnameget'), $_SESSION['sessionnameget']); + + Session::set('sessionnamegetarr.subname', 'sessionvalue'); + $this->assertEquals(Session::get('sessionnamegetarr.subname'), $_SESSION['sessionnamegetarr']['subname']); + + Session::set('sessionnamegetarrperall', 'sessionvalue', 'think_'); + $this->assertEquals(Session::get('', 'think_')['sessionnamegetarrperall'], $_SESSION['think_']['sessionnamegetarrperall']); + + Session::set('sessionnamegetper', 'sessionvalue', 'think_'); + $this->assertEquals(Session::get('sessionnamegetper', 'think_'), $_SESSION['think_']['sessionnamegetper']); + + Session::set('sessionnamegetarrper.subname', 'sessionvalue', 'think_'); + $this->assertEquals(Session::get('sessionnamegetarrper.subname', 'think_'), $_SESSION['think_']['sessionnamegetarrper']['subname']); + } + + public function testPull() + { + Session::prefix(null); + Session::set('sessionnamedel', 'sessionvalue'); + $this->assertEquals('sessionvalue', Session::pull('sessionnameget')); + $this->assertNull(Session::get('sessionnameget')); + } + + /** + * @covers think\Session::delete + * + * @todo Implement testDelete(). + */ + public function testDelete() + { + Session::prefix(null); + Session::set('sessionnamedel', 'sessionvalue'); + Session::delete('sessionnamedel'); + $this->assertEmpty($_SESSION['sessionnamedel']); + + Session::set('sessionnamedelarr.subname', 'sessionvalue'); + Session::delete('sessionnamedelarr.subname'); + $this->assertEmpty($_SESSION['sessionnamedelarr']['subname']); + + Session::set('sessionnamedelper', 'sessionvalue', 'think_'); + Session::delete('sessionnamedelper', 'think_'); + $this->assertEmpty($_SESSION['think_']['sessionnamedelper']); + + Session::set('sessionnamedelperarr.subname', 'sessionvalue', 'think_'); + Session::delete('sessionnamedelperarr.subname', 'think_'); + $this->assertEmpty($_SESSION['think_']['sessionnamedelperarr']['subname']); + } + + /** + * @covers think\Session::clear + * + * @todo Implement testClear(). + */ + public function testClear() + { + Session::prefix(null); + + Session::set('sessionnameclsper', 'sessionvalue1', 'think_'); + Session::clear('think_'); + $this->assertNull($_SESSION['think_']); + + Session::set('sessionnameclsper', 'sessionvalue1', 'think_'); + Session::clear(); + $this->assertEmpty($_SESSION); + } + + /** + * @covers think\Session::has + * + * @todo Implement testHas(). + */ + public function testHas() + { + Session::prefix(null); + Session::set('sessionnamehas', 'sessionvalue'); + $this->assertTrue(Session::has('sessionnamehas')); + + Session::set('sessionnamehasarr.subname', 'sessionvalue'); + $this->assertTrue(Session::has('sessionnamehasarr.subname')); + + Session::set('sessionnamehasper', 'sessionvalue', 'think_'); + $this->assertTrue(Session::has('sessionnamehasper', 'think_')); + + Session::set('sessionnamehasarrper.subname', 'sessionvalue', 'think_'); + $this->assertTrue(Session::has('sessionnamehasarrper.subname', 'think_')); + } + + /** + * @covers think\Session::pause + * + * @todo Implement testPause(). + */ + public function testPause() + { + Session::pause(); + } + + /** + * @covers think\Session::start + * + * @todo Implement testStart(). + */ + public function testStart() + { + Session::start(); + } + + /** + * @covers think\Session::destroy + * + * @todo Implement testDestroy(). + */ + public function testDestroy() + { + Session::set('sessionnamedestroy', 'sessionvalue'); + Session::destroy(); + $this->assertEmpty($_SESSION['sessionnamedestroy']); + } +} diff --git a/thinkphp/tests/thinkphp/library/think/template/driver/.gitignore b/thinkphp/tests/thinkphp/library/think/template/driver/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/thinkphp/tests/thinkphp/library/think/template/driver/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/thinkphp/tests/thinkphp/library/think/template/taglib/cxTest.php b/thinkphp/tests/thinkphp/library/think/template/taglib/cxTest.php new file mode 100644 index 0000000..8aee392 --- /dev/null +++ b/thinkphp/tests/thinkphp/library/think/template/taglib/cxTest.php @@ -0,0 +1,575 @@ + +// +---------------------------------------------------------------------- + +/** + * 模板测试 + * @author Haotong Lin + */ + +namespace tests\thinkphp\library\think\tempplate\taglib; + +use think\Template; +use think\template\taglib\Cx; + +class cxTest extends \PHPUnit_Framework_TestCase +{ + public function testPhp() + { + $template = new template(); + $cx = new Cx($template); + + $content = << +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + } + + public function testVolist() + { + $template = new template(); + $cx = new Cx($template); + + $content = <<\$vo): \$mod = (\$key % 2 );++\$key;?> + + +EOF; + $cx->parseTag($content); + $this->assertEquals($data, $content); + + $content = <<display($content, ['list' => [1, 2, 3, 4, 5]]); + $this->expectOutputString('234'); + } + + public function testForeach() + { + $template = new template(); + $cx = new Cx($template); + + $content = <<\$val} + +{/foreach} +EOF; + $data = <<\$val): ?> + + +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + + $content = <<\$val): ?> + + +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + + $content = <<display($content); + $this->expectOutputString('234'); + } + + public function testIf() + { + $template = new template(); + $cx = new Cx($template); + + $content = << +one + +two + +default + +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + } + + public function testSwitch() + { + $template = new template(); + $cx = new Cx($template); + + $content = << + +a + + +b + + +d + + +default + +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + } + + public function testCompare() + { + $template = new template(); + $cx = new Cx($template); + + $content = << +default + +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + + $content = << +default + +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + + $content = << +default + +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + + $content = << +default + +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + + $content = << '0'): ?> +default + +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + + $content = <<= '0'): ?> +default + +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + + $content = << +default + +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + + $content = << +default + +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + + $content = << +default + +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + + $content = << +default + +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + + } + + public function testRange() + { + $template = new template(); + $cx = new Cx($template); + + $content = << +default + +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + + $content = << +default + +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + + $content = <<display($content); + $this->expectOutputString('yesno'); + } + + public function testPresent() + { + $template = new template(); + $cx = new Cx($template); + + $content = << +default + +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + + $content = << +default + +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + } + + public function testEmpty() + { + $template = new template(); + $cx = new Cx($template); + + $content = <<isEmpty())): ?> +default + +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + + $content = <<isEmpty()))): ?> +default + +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + } + + public function testDefined() + { + $template = new template(); + $cx = new Cx($template); + + $content = << +default + +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + + $content = << +default + +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + } + + public function testImport() + { + $template = new template(); + $cx = new Cx($template); + + $content = << +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + + $content = << +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + + $content = << +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + } + + public function testAssign() + { + $template = new template(); + $cx = new Cx($template); + + $content = << +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + + $content = << +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + } + + public function testDefine() + { + $template = new template(); + $cx = new Cx($template); + + $content = << +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + + $content = << +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + } + + public function testFor() + { + $template = new template(); + + $content = <<display($content); + $this->expectOutputString('123456789'); + } + public function testUrl() + { + $template = new template(); + $content = <<display($content); + $this->expectOutputString(\think\Url::build('Index/index')); + } + + public function testFunction() + { + $template = new template(); + $data = [ + 'list' => ['language' => 'php', 'version' => ['5.4', '5.5']], + 'a' => '[', + 'b' => ']', + ]; + + $content = <<\$val} +{if is_array(\$val)} +{~\$func(\$val)} +{else} +{if !is_numeric(\$key)} +{\$key.':'.\$val.','} +{else} +{\$a.\$val.\$b} +{/if} +{/if} +{/foreach} +{/function} +EOF; + $template->display($content, $data); + $this->expectOutputString("language:php,[5.4][5.5]"); + } +} diff --git a/thinkphp/tests/thinkphp/library/think/templateTest.php b/thinkphp/tests/thinkphp/library/think/templateTest.php new file mode 100644 index 0000000..1135830 --- /dev/null +++ b/thinkphp/tests/thinkphp/library/think/templateTest.php @@ -0,0 +1,415 @@ + +// +---------------------------------------------------------------------- + +/** + * 模板测试 + * @author oldrind + */ + +namespace tests\thinkphp\library\think; + +use think\Cache; +use think\Template; + +class templateTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var Template + */ + protected $template; + + public function setUp() + { + $this->template = new Template(); + } + + public function testAssign() + { + $reflectProperty = new \ReflectionProperty(get_class($this->template), 'data'); + $reflectProperty->setAccessible(true); + + $this->template->assign('version', 'ThinkPHP3.2'); + $data = $reflectProperty->getValue($this->template); + $this->assertEquals('ThinkPHP3.2', $data['version']); + + $this->template->assign(['name' => 'Gao', 'version' => 'ThinkPHP5']); + $data = $reflectProperty->getValue($this->template); + $this->assertEquals('Gao', $data['name']); + $this->assertEquals('ThinkPHP5', $data['version']); + } + + public function testGet() + { + $this->template = new Template(); + $data = [ + 'project' => 'ThinkPHP', + 'version' => [ + 'ThinkPHP5' => ['Think5.0', 'Think5.1'] + ] + ]; + $this->template->assign($data); + + $this->assertSame($data, $this->template->get()); + $this->assertSame('ThinkPHP', $this->template->get('project')); + $this->assertSame(['Think5.0', 'Think5.1'], $this->template->get('version.ThinkPHP5')); + $this->assertNull($this->template->get('version.ThinkPHP3.2')); + } + + /** + * @dataProvider provideTestParseWithVar + */ + public function testParseWithVar($content, $expected) + { + $this->template = new Template(); + + $this->template->parse($content); + $this->assertEquals($expected, $content); + } + + /** + * @dataProvider provideTestParseWithVarFunction + */ + public function testParseWithVarFunction($content, $expected) + { + $this->template = new Template(); + + $this->template->parse($content); + $this->assertEquals($expected, $content); + } + + /** + * @dataProvider provideTestParseWithVarIdentify + */ + public function testParseWithVarIdentify($content, $expected, $config) + { + $this->template = new Template($config); + + $this->template->parse($content); + $this->assertEquals($expected, $content); + } + + /** + * @dataProvider provideTestParseWithThinkVar + */ + public function testParseWithThinkVar($content, $expected) + { + $config['tpl_begin'] = '{'; + $config['tpl_end'] = '}'; + $this->template = new Template($config); + + $_SERVER['SERVER_NAME'] = 'server_name'; + $_GET['action'] = 'action'; + $_POST['action'] = 'action'; + $_COOKIE['name'] = 'name'; + $_SESSION['action'] = ['name' => 'name']; + + $this->template->parse($content); + $this->assertEquals($expected, $content); + } + + /** + * @expectedException \think\exception\TemplateNotFoundException + */ + public function testFetchWithEmptyTemplate() + { + $this->template = new Template(); + + $this->template->fetch('Foo'); + } + + /** + * @dataProvider provideTestFetchWithNoCache + */ + public function testFetchWithNoCache($data, $expected) + { + $this->template = new Template(); + + $this->template->fetch($data['template'], $data['vars'], $data['config']); + + $this->expectOutputString($expected); + } + + public function testFetchWithCache() + { + $this->template = new Template(); + + $data = [ + 'name' => 'value' + ]; + $config = [ + 'cache_id' => 'TEST_FETCH_WITH_CACHE', + 'display_cache' => true, + ]; + + $this->template->fetch(APP_PATH . 'views' . DS .'display.html', $data, $config); + + $this->expectOutputString('value'); + $this->assertEquals('value', Cache::get($config['cache_id'])); + } + + public function testDisplay() + { + $config = [ + 'view_path' => APP_PATH . DS . 'views' . DS, + 'view_suffix' => '.html', + 'layout_on' => true, + 'layout_name' => 'layout' + ]; + + $this->template = new Template($config); + + $this->template->assign('files', ['extend' => 'extend', 'include' => 'include']); + $this->template->assign('user', ['name' => 'name', 'account' => 100]); + $this->template->assign('message', 'message'); + $this->template->assign('info', ['value' => 'value']); + + $content = << +header +
    + +value: + +main + + + side + + +value: + message{\$message} + + + mainbody + + + + {\$name} + + php code
    + +EOF; + $this->template->display($content); + $this->expectOutputString($expected); + } + + /** + * @dataProvider provideTestLayout + */ + public function testLayout($data, $expected) + { + $this->template = new Template(); + + $this->template->layout($data['name'], $data['replace']); + + $this->assertSame($expected['layout_on'], $this->template->config('layout_on')); + $this->assertSame($expected['layout_name'], $this->template->config('layout_name')); + $this->assertSame($expected['layout_item'], $this->template->config('layout_item')); + } + + public function testParseAttr() + { + $attributes = $this->template->parseAttr(""); + $this->assertSame(['version' => 'ThinkPHP', 'name' => 'Gao'], $attributes); + + $attributes = $this->template->parseAttr("TestCase", 'version'); + $this->assertSame('ThinkPHP', $attributes); + } + + public function testIsCache() + { + $this->template = new Template(); + $config = [ + 'cache_id' => rand(0, 10000) . rand(0, 10000) . time(), + 'display_cache' => true + ]; + + $this->assertFalse($this->template->isCache($config['cache_id'])); + + $this->template->fetch(APP_PATH . 'views' . DS .'display.html', [], $config); + $this->assertTrue($this->template->isCache($config['cache_id'])); + } + + public function provideTestParseWithVar() + { + return [ + ["{\$name.a.b}", ""], + ["{\$name.a??'test'}", ""], + ["{\$name.a?='test'}", ""], + ["{\$name.a?:'test'}", ""], + ["{\$name.a?\$name.b:'no'}", ""], + ["{\$name.a==\$name.b?='test'}", ""], + ["{\$name.a==\$name.b?'a':'b'}", ""], + ["{\$name.a|default='test'==\$name.b?'a':'b'}", ""], + ["{\$name.a|trim==\$name.b?='eq'}", ""], + ["{:ltrim(rtrim(\$name.a))}", ""], + ["{~echo(trim(\$name.a))}", ""], + ["{++\$name.a}", ""], + ["{/*\$name*/}", ""], + ["{\$0a}", "{\$0a}"] + ]; + } + + public function provideTestParseWithVarFunction() + { + return [ + ["{\$name.a.b|default='test'}", ""], + ["{\$create_time|date=\"y-m-d\",###}", ""], + ["{\$name}\n{\$name|trim|substr=0,3}", "\n"] + ]; + } + + public function provideTestParseWithVarIdentify() + { + $config['tpl_begin'] = '<#'; + $config['tpl_end'] = '#>'; + $config['tpl_var_identify'] = ''; + + return [ + [ + "<#\$info.a??'test'#>", + "a)) ? (is_array(\$info)?\$info['a']:\$info->a) : 'test'; ?>", + $config + ], + [ + "<#\$info.a?='test'#>", + "a)) echo 'test'; ?>", + $config + ], + [ + "<#\$info.a==\$info.b?='test'#>", + "a)==(is_array(\$info)?\$info['b']:\$info->b)) echo 'test'; ?>", + $config + ], + [ + "<#\$info.a|default='test'?'yes':'no'#>", + "a) ?: 'test')?'yes':'no'; ?>", + $config + ], + [ + "{\$info2.b|trim?'yes':'no'}", + "b)?'yes':'no'; ?>", + array_merge(['tpl_var_identify' => 'obj']) + ] + ]; + } + + public function provideTestParseWithThinkVar() + { + return [ + ["{\$Think.SERVER.SERVER_NAME}
    ", "server('SERVER_NAME'); ?>
    "], + ["{\$Think.GET.action}
    ", "get('action'); ?>
    "], + ["{\$Think.POST.action}
    ", "post('action'); ?>
    "], + ["{\$Think.COOKIE.action}
    ", "
    "], + ["{\$Think.COOKIE.action.name}
    ", "
    "], + ["{\$Think.SESSION.action}
    ", "
    "], + ["{\$Think.SESSION.action.name}
    ", "
    "], + ["{\$Think.ENV.OS}
    ", "env('OS'); ?>
    "], + ["{\$Think.REQUEST.action}
    ", "request('action'); ?>
    "], + ["{\$Think.CONST.THINK_VERSION}
    ", "
    "], + ["{\$Think.LANG.action}
    ", "
    "], + ["{\$Think.CONFIG.action.name}
    ", "
    "], + ["{\$Think.NOW}
    ", "
    "], + ["{\$Think.VERSION}
    ", "
    "], + ["{\$Think.LDELIM}
    ", "
    "], + ["{\$Think.RDELIM}
    ", "
    "], + ["{\$Think.THINK_VERSION}
    ", "
    "], + ["{\$Think.SITE.URL}", ""] + ]; + } + + public function provideTestFetchWithNoCache() + { + $provideData = []; + + $this->template = [ + 'template' => APP_PATH . 'views' . DS .'display.html', + 'vars' => [], + 'config' => [] + ]; + $expected = 'default'; + $provideData[] = [$this->template, $expected]; + + $this->template = [ + 'template' => APP_PATH . 'views' . DS .'display.html', + 'vars' => ['name' => 'ThinkPHP5'], + 'config' => [] + ]; + $expected = 'ThinkPHP5'; + $provideData[] = [$this->template, $expected]; + + $this->template = [ + 'template' => 'views@display', + 'vars' => [], + 'config' => [ + 'view_suffix' => 'html' + ] + ]; + $expected = 'default'; + $provideData[] = [$this->template, $expected]; + + $this->template = [ + 'template' => 'views@/display', + 'vars' => ['name' => 'ThinkPHP5'], + 'config' => [ + 'view_suffix' => 'phtml' + ] + ]; + $expected = 'ThinkPHP5'; + $provideData[] = [$this->template, $expected]; + + $this->template = [ + 'template' => 'display', + 'vars' => ['name' => 'ThinkPHP5'], + 'config' => [ + 'view_suffix' => 'html', + 'view_base' => APP_PATH . 'views' . DS + ] + ]; + $expected = 'ThinkPHP5'; + $provideData[] = [$this->template, $expected]; + + return $provideData; + } + + public function provideTestLayout() + { + $provideData = []; + + $data = ['name' => false, 'replace' => '']; + $expected = ['layout_on' => false, 'layout_name' => 'layout', 'layout_item' => '{__CONTENT__}']; + $provideData[] = [$data, $expected]; + + $data = ['name' => null, 'replace' => '']; + $expected = ['layout_on' => true, 'layout_name' => 'layout', 'layout_item' => '{__CONTENT__}']; + $provideData[] = [$data, $expected]; + + $data = ['name' => 'ThinkName', 'replace' => 'ThinkReplace']; + $expected = ['layout_on' => true, 'layout_name' => 'ThinkName', 'layout_item' => 'ThinkReplace']; + $provideData[] = [$data, $expected]; + + return $provideData; + } +} diff --git a/thinkphp/tests/thinkphp/library/think/urlTest.php b/thinkphp/tests/thinkphp/library/think/urlTest.php new file mode 100644 index 0000000..401d973 --- /dev/null +++ b/thinkphp/tests/thinkphp/library/think/urlTest.php @@ -0,0 +1,129 @@ + +// +---------------------------------------------------------------------- + +/** + * Url测试 + * @author liu21st + */ + +namespace tests\thinkphp\library\think; + +use tests\thinkphp\library\think\config\ConfigInitTrait; +use think\Config; +use think\Route; +use think\Url; + +class urlTest extends \PHPUnit_Framework_TestCase +{ + use ConfigInitTrait; + + public function setUp() + { + Route::rules(['get' => [], + 'post' => [], + 'put' => [], + 'delete' => [], + 'patch' => [], + 'head' => [], + 'options' => [], + '*' => [], + 'alias' => [], + 'domain' => [], + 'pattern' => [], + 'name' => []]); + Route::name([]); + } + + public function testBuildModule() + { + + Route::get('blog/:name', 'index/blog'); + Route::get('blog/:id', 'index/blog'); + Config::set('pathinfo_depr', '/'); + Config::set('url_html_suffix', ''); + + $this->assertEquals('/blog/thinkphp', Url::build('index/blog?name=thinkphp')); + $this->assertEquals('/blog/thinkphp.html', Url::build('index/blog', 'name=thinkphp', 'html')); + $this->assertEquals('/blog/10', Url::build('index/blog?id=10')); + $this->assertEquals('/blog/10.html', Url::build('index/blog', 'id=10', 'html')); + + Route::get('item-', 'blog/item', [], ['name' => '\w+', 'id' => '\d+']); + $this->assertEquals('/item-thinkphp', Url::build('blog/item?name=thinkphp')); + $this->assertEquals('/item-thinkphp2016', Url::build('blog/item?name=thinkphp&id=2016')); + } + + public function testBuildController() + { + Config::set('url_html_suffix', ''); + Route::get('blog/:id', '@index/blog/read'); + $this->assertEquals('/blog/10.html', Url::build('@index/blog/read', 'id=10', 'html')); + + Route::get('foo/bar', '@foo/bar/index'); + $this->assertEquals('/foo/bar', Url::build('@foo/bar/index')); + + Route::get('foo/bar/baz', '@foo/bar.BAZ/index'); + $this->assertEquals('/foo/bar/baz', Url::build('@foo/bar.BAZ/index')); + } + + public function testBuildMethod() + { + Route::get('blog/:id', '\app\index\controller\blog@read'); + $this->assertEquals('/blog/10.html', Url::build('\app\index\controller\blog@read', 'id=10', 'html')); + } + + public function testBuildRoute() + { + Route::get('blog/:id', 'index/blog'); + Config::set('url_html_suffix', 'shtml'); + $this->assertNotEquals('/blog/10.html', Url::build('/blog/10')); + $this->assertEquals('/blog/10.shtml', Url::build('/blog/10')); + } + + public function testBuildNameRoute() + { + Route::get(['name', 'blog/:id'], 'index/blog'); + $this->assertEquals([['blog/:id', ['id' => 1], null, null]], Route::name('name')); + Config::set('url_html_suffix', 'shtml'); + $this->assertEquals('/blog/10.shtml', Url::build('name?id=10')); + } + + public function testBuildAnchor() + { + Route::get('blog/:id', 'index/blog'); + Config::set('url_html_suffix', 'shtml'); + $this->assertEquals('/blog/10.shtml#detail', Url::build('index/blog#detail', 'id=10')); + + Config::set('url_common_param', true); + $this->assertEquals('/blog/10.shtml?foo=bar#detail', Url::build('index/blog#detail', "id=10&foo=bar")); + } + + public function testBuildDomain() + { + Config::set('url_domain_deploy', true); + Route::domain('subdomain.thinkphp.cn', 'admin'); + $this->assertEquals('http://subdomain.thinkphp.cn/blog/10.html', Url::build('/blog/10')); + Route::domain('subdomain.thinkphp.cn', [ + 'hello/:name' => 'index/hello', + ]); + $this->assertEquals('http://subdomain.thinkphp.cn/hello/thinkphp.html', Url::build('index/hello?name=thinkphp')); + } + + public function testRoot() + { + Config::set('url_domain_deploy', false); + Config::set('url_common_param', false); + Url::root('/index.php'); + Route::get('blog/:id', 'index/blog/read'); + Config::set('url_html_suffix', 'shtml'); + $this->assertEquals('/index.php/blog/10/name/thinkphp.shtml', Url::build('index/blog/read?id=10&name=thinkphp')); + + } +} diff --git a/thinkphp/tests/thinkphp/library/think/validateTest.php b/thinkphp/tests/thinkphp/library/think/validateTest.php new file mode 100644 index 0000000..b5f4333 --- /dev/null +++ b/thinkphp/tests/thinkphp/library/think/validateTest.php @@ -0,0 +1,200 @@ + +// +---------------------------------------------------------------------- + +/** + * Validate类测试 + */ + +namespace tests\thinkphp\library\think; + +use think\File; +use think\Validate; + +class validateTest extends \PHPUnit_Framework_TestCase +{ + + public function testCheck() + { + $rule = [ + 'name' => 'require|max:25', + 'age' => 'number|between:1,120', + 'email' => 'email', + ]; + $msg = [ + 'name.require' => '名称必须', + 'name.max' => '名称最多不能超过25个字符', + 'age.number' => '年龄必须是数字', + 'age.between' => '年龄只能在1-120之间', + 'email' => '邮箱格式错误', + ]; + $data = [ + 'name' => 'thinkphp', + 'age' => 10, + 'email' => 'thinkphp@qq.com', + ]; + $validate = new Validate($rule, $msg); + $result = $validate->check($data); + $this->assertEquals(true, $result); + } + + public function testRule() + { + $rule = [ + 'name' => 'require|method:get|alphaNum|max:25|expire:2016-1-1,2026-1-1', + 'account' => 'requireIf:name,thinkphp|alphaDash|min:4|length:4,30', + 'age' => 'number|between:1,120', + 'email' => 'requireWith:name|email', + 'host' => 'activeUrl|activeUrl:A', + 'url' => 'url', + 'ip' => 'ip|ip:ipv4', + 'score' => 'float|gt:60|notBetween:90,100|notIn:70,80|lt:100|elt:100|egt:60', + 'status' => 'integer|in:0,1,2', + 'begin_time' => 'after:2016-3-18|beforeWith:end_time', + 'end_time' => 'before:2016-10-01|afterWith:begin_time', + 'info' => 'require|array|length:4|max:5|min:2', + 'info.name' => 'require|length:8|alpha|same:thinkphp', + 'value' => 'same:100|different:status', + 'bool' => 'boolean', + 'title' => 'chsAlpha', + 'city' => 'chs', + 'nickname' => 'chsDash', + 'aliasname' => 'chsAlphaNum', + 'file' => 'file|fileSize:20480', + 'image' => 'image|fileMime:image/png|image:80,80,png', + 'test' => 'test', + ]; + $data = [ + 'name' => 'thinkphp', + 'account' => 'liuchen', + 'age' => 10, + 'email' => 'thinkphp@qq.com', + 'host' => 'thinkphp.cn', + 'url' => 'http://thinkphp.cn/topic', + 'ip' => '114.34.54.5', + 'score' => '89.15', + 'status' => 1, + 'begin_time' => '2016-3-20', + 'end_time' => '2016-5-1', + 'info' => [1, 2, 3, 'name' => 'thinkphp'], + 'zip' => '200000', + 'date' => '16-3-8', + 'ok' => 'yes', + 'value' => 100, + 'bool' => true, + 'title' => '流年ThinkPHP', + 'city' => '上海', + 'nickname' => '流年ThinkPHP_2016', + 'aliasname' => '流年Think2016', + 'file' => new File(THINK_PATH . 'base.php'), + 'image' => new File(THINK_PATH . 'logo.png'), + 'test' => 'test', + ]; + $validate = new Validate($rule); + $validate->extend('test', function ($value) {return 'test' == $value ? true : false;}); + $validate->rule('zip', '/^\d{6}$/'); + $validate->rule([ + 'ok' => 'require|accepted', + 'date' => 'date|dateFormat:y-m-d', + ]); + $result = $validate->batch()->check($data); + $this->assertEquals(true, $result); + } + + public function testMsg() + { + $validate = new Validate(); + $validate->message('name.require', '名称必须'); + $validate->message([ + 'name.require' => '名称必须', + 'name.max' => '名称最多不能超过25个字符', + 'age.number' => '年龄必须是数字', + 'age.between' => '年龄只能在1-120之间', + 'email' => '邮箱格式错误', + ]); + } + + public function testMake() + { + $rule = [ + 'name' => 'require|max:25', + 'age' => 'number|between:1,120', + 'email' => 'email', + ]; + $msg = [ + 'name.require' => '名称必须', + 'name.max' => '名称最多不能超过25个字符', + 'age.number' => '年龄必须是数字', + 'age.between' => '年龄只能在1-120之间', + 'email' => '邮箱格式错误', + ]; + $validate = Validate::make($rule, $msg); + } + + public function testExtend() + { + $validate = new Validate(['name' => 'check:1']); + $validate->extend('check', function ($value, $rule) {return $rule == $value ? true : false;}); + $validate->extend(['check' => function ($value, $rule) {return $rule == $value ? true : false;}]); + $data = ['name' => 1]; + $result = $validate->check($data); + $this->assertEquals(true, $result); + } + + public function testScene() + { + $rule = [ + 'name' => 'require|max:25', + 'age' => 'number|between:1,120', + 'email' => 'email', + ]; + $msg = [ + 'name.require' => '名称必须', + 'name.max' => '名称最多不能超过25个字符', + 'age.number' => '年龄必须是数字', + 'age.between' => '年龄只能在1-120之间', + 'email' => '邮箱格式错误', + ]; + $data = [ + 'name' => 'thinkphp', + 'age' => 10, + 'email' => 'thinkphp@qq.com', + ]; + $validate = new Validate($rule); + $validate->scene(['edit' => ['name', 'age']]); + $validate->scene('edit', ['name', 'age']); + $validate->scene('edit'); + $result = $validate->check($data); + $this->assertEquals(true, $result); + } + + public function testSetTypeMsg() + { + $rule = [ + 'name|名称' => 'require|max:25', + 'age' => 'number|between:1,120', + 'email' => 'email', + ['sex', 'in:1,2', '性别错误'], + ]; + $data = [ + 'name' => '', + 'age' => 10, + 'email' => 'thinkphp@qq.com', + 'sex' => '3', + ]; + $validate = new Validate($rule); + $validate->setTypeMsg('require', ':attribute必须'); + $validate->setTypeMsg(['require' => ':attribute必须']); + $result = $validate->batch()->check($data); + $this->assertFalse($result); + $this->assertEquals(['name' => '名称必须', 'sex' => '性别错误'], $validate->getError()); + } + +} diff --git a/thinkphp/tests/thinkphp/library/think/view/driver/.gitignore b/thinkphp/tests/thinkphp/library/think/view/driver/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/thinkphp/tests/thinkphp/library/think/view/driver/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/thinkphp/tests/thinkphp/library/think/view/theme/index/template.html b/thinkphp/tests/thinkphp/library/think/view/theme/index/template.html new file mode 100644 index 0000000..5f4548b --- /dev/null +++ b/thinkphp/tests/thinkphp/library/think/view/theme/index/template.html @@ -0,0 +1,14 @@ + + + + + + + + + Document + + + + + diff --git a/thinkphp/tests/thinkphp/library/think/viewTest.php b/thinkphp/tests/thinkphp/library/think/viewTest.php new file mode 100644 index 0000000..5bb7de1 --- /dev/null +++ b/thinkphp/tests/thinkphp/library/think/viewTest.php @@ -0,0 +1,76 @@ + +// +---------------------------------------------------------------------- + +/** + * view测试 + * @author mahuan + */ + +namespace tests\thinkphp\library\think; + +class viewTest extends \PHPUnit_Framework_TestCase +{ + + /** + * 句柄测试 + * @return mixed + * @access public + */ + public function testGetInstance() + { + \think\Cookie::get('a'); + $view_instance = \think\View::instance(); + $this->assertInstanceOf('\think\view', $view_instance, 'instance方法返回错误'); + } + + /** + * 测试变量赋值 + * @return mixed + * @access public + */ + public function testAssign() + { + $view_instance = \think\View::instance(); + $view_instance->key = 'value'; + $this->assertTrue(isset($view_instance->key)); + $this->assertEquals('value', $view_instance->key); + $data = $view_instance->assign(array('key' => 'value')); + $data = $view_instance->assign('key2', 'value2'); + //测试私有属性 + $expect_data = array('key' => 'value', 'key2' => 'value2'); + $this->assertAttributeEquals($expect_data, 'data', $view_instance); + } + + /** + * 测试引擎设置 + * @return mixed + * @access public + */ + public function testEngine() + { + $view_instance = \think\View::instance(); + $data = $view_instance->engine('php'); + $data = $view_instance->engine(['type' => 'php', 'view_path' => '', 'view_suffix' => '.php', 'view_depr' => DS]); + $php_engine = new \think\view\driver\Php(['view_path' => '', 'view_suffix' => '.php', 'view_depr' => DS]); + $this->assertAttributeEquals($php_engine, 'engine', $view_instance); + //测试模板引擎驱动 + $data = $view_instance->engine(['type' => 'think', 'view_path' => '', 'view_suffix' => '.html', 'view_depr' => DS]); + $think_engine = new \think\view\driver\Think(['view_path' => '', 'view_suffix' => '.html', 'view_depr' => DS]); + $this->assertAttributeEquals($think_engine, 'engine', $view_instance); + } + + public function testReplace() + { + $view_instance = \think\View::instance(); + $view_instance->replace('string', 'replace')->display('string'); + } + +} diff --git a/thinkphp/tests/thinkphp/library/traits/controller/jumpTest.php b/thinkphp/tests/thinkphp/library/traits/controller/jumpTest.php new file mode 100644 index 0000000..47e9dbf --- /dev/null +++ b/thinkphp/tests/thinkphp/library/traits/controller/jumpTest.php @@ -0,0 +1,339 @@ +testClass = new testClassWithJump(); + $this->request = Request::create(''); + + $this->originServerData = Request::instance()->server(); + } + + public function tearDown() + { + Request::instance()->server($this->originServerData); + } + + /** + * @dataProvider provideTestSuccess + */ + public function testSuccess($arguments, $expected, array $extra) + { + if (isset($extra['server'])) { + $this->request->server($extra['server']); + } + + $mock = $this->getMockBuilder(get_class($this->testClass))->setMethods(['getResponseType'])->getMock(); + $mock->expects($this->any())->method('getResponseType')->willReturn($extra['return']); + + try { + call_user_func_array([$mock, 'success'], $arguments); + $this->setExpectedException('\think\exception\HttpResponseException'); + } catch (\Exception $e) { + $this->assertInstanceOf('\think\exception\HttpResponseException', $e); + + /** @var Response $response */ + $response = $e->getResponse(); + + $this->assertInstanceOf('\Think\Response', $response); + $this->assertEquals($expected['header'], $response->getHeader()); + $this->assertEquals($expected['data'], $response->getData()); + } + } + + /** + * @dataProvider provideTestError + */ + public function testError($arguments, $expected, array $extra) + { + if (isset($extra['server'])) { + $this->request->server($extra['server']); + } + + $mock = $this->getMockBuilder(get_class($this->testClass))->setMethods(['getResponseType'])->getMock(); + $mock->expects($this->any())->method('getResponseType')->willReturn($extra['return']); + + try { + call_user_func_array([$mock, 'error'], $arguments); + $this->setExpectedException('\think\exception\HttpResponseException'); + } catch (\Exception $e) { + $this->assertInstanceOf('\think\exception\HttpResponseException', $e); + + /** @var Response $response */ + $response = $e->getResponse(); + + $this->assertInstanceOf('\Think\Response', $response); + $this->assertEquals($expected['header'], $response->getHeader()); + $this->assertEquals($expected['data'], $response->getData()); + } + } + + /** + * @dataProvider provideTestResult + */ + public function testResult($arguments, $expected, array $extra) + { + if (isset($extra['server'])) { + $this->request->server($extra['server']); + } + + $mock = $this->getMockBuilder(get_class($this->testClass))->setMethods(['getResponseType'])->getMock(); + $mock->expects($this->any())->method('getResponseType')->willReturn($extra['return']); + + try { + call_user_func_array([$mock, 'result'], $arguments); + $this->setExpectedException('\think\exception\HttpResponseException'); + } catch (\Exception $e) { + $this->assertInstanceOf('\think\exception\HttpResponseException', $e); + + /** @var Response $response */ + $response = $e->getResponse(); + + $this->assertInstanceOf('\Think\Response', $response); + $this->assertEquals($expected['header'], $response->getHeader()); + $this->assertEquals($expected['data'], $response->getData()); + } + } + + /** + * @dataProvider provideTestRedirect + */ + public function testRedirect($arguments, $expected) + { + try { + call_user_func_array([$this->testClass, 'redirect'], $arguments); + $this->setExpectedException('\think\exception\HttpResponseException'); + } catch (\Exception $e) { + $this->assertInstanceOf('\think\exception\HttpResponseException', $e); + + /** @var Redirect $response */ + $response = $e->getResponse(); + + $this->assertInstanceOf('\think\response\Redirect', $response); + $this->assertEquals($expected['url'], $response->getTargetUrl()); + $this->assertEquals($expected['code'], $response->getCode()); + } + } + + public function testGetResponseType() + { + Request::instance()->server(['HTTP_X_REQUESTED_WITH' => null]); + $this->assertEquals('html', $this->testClass->getResponseType()); + + Request::instance()->server(['HTTP_X_REQUESTED_WITH' => true]); + $this->assertEquals('html', $this->testClass->getResponseType()); + + Request::instance()->server(['HTTP_X_REQUESTED_WITH' => 'xmlhttprequest']); + $this->assertEquals('json', $this->testClass->getResponseType()); + } + + public function provideTestSuccess() + { + $provideData = []; + + $arguments = ['', null, '', 3, []]; + $expected = [ + 'header' => [ + 'Content-Type' => 'text/html; charset=utf-8' + ], + 'data' => View::instance(Config::get('template'), Config::get('view_replace_str')) + ->fetch(Config::get('dispatch_error_tmpl'), [ + 'code' => 1, + 'msg' => '', + 'data' => '', + 'url' => '/index.php/', + 'wait' => 3, + ]) + ]; + $provideData[] = [$arguments, $expected, ['server' => ['HTTP_REFERER' => null], 'return' => 'html']]; + + $arguments = ['thinkphp', null, ['foo'], 4, ['Power-By' => 'thinkphp', 'Content-Type' => 'text/html; charset=gbk']]; + $expected = [ + 'header' => [ + 'Content-Type' => 'text/html; charset=gbk', + 'Power-By' => 'thinkphp' + ], + 'data' => View::instance(Config::get('template'), Config::get('view_replace_str')) + ->fetch(Config::get('dispatch_error_tmpl'), [ + 'code' => 1, + 'msg' => 'thinkphp', + 'data' => ['foo'], + 'url' => 'http://www.thinkphp.cn', + 'wait' => 4, + ]) + ]; + $provideData[] = [$arguments, $expected, ['server' => ['HTTP_REFERER' => 'http://www.thinkphp.cn'], 'return' => 'html']]; + + $arguments = ['thinkphp', 'index', ['foo'], 5, []]; + $expected = [ + 'header' => [ + 'Content-Type' => 'application/json; charset=utf-8' + ], + 'data' => [ + 'code' => 1, + 'msg' => 'thinkphp', + 'data' => ['foo'], + 'url' => '/index.php/index.html', + 'wait' => 5, + ] + ]; + $provideData[] = [$arguments, $expected, ['server' => ['HTTP_REFERER' => null], 'return' => 'json']]; + + return $provideData; + } + + public function provideTestError() + { + $provideData = []; + + $arguments = ['', null, '', 3, []]; + $expected = [ + 'header' => [ + 'Content-Type' => 'text/html; charset=utf-8' + ], + 'data' => View::instance(Config::get('template'), Config::get('view_replace_str')) + ->fetch(Config::get('dispatch_error_tmpl'), [ + 'code' => 0, + 'msg' => '', + 'data' => '', + 'url' => 'javascript:history.back(-1);', + 'wait' => 3, + ]) + ]; + $provideData[] = [$arguments, $expected, ['return' => 'html']]; + + $arguments = ['thinkphp', 'http://www.thinkphp.cn', ['foo'], 4, ['Power-By' => 'thinkphp', 'Content-Type' => 'text/html; charset=gbk']]; + $expected = [ + 'header' => [ + 'Content-Type' => 'text/html; charset=gbk', + 'Power-By' => 'thinkphp' + ], + 'data' => View::instance(Config::get('template'), Config::get('view_replace_str')) + ->fetch(Config::get('dispatch_error_tmpl'), [ + 'code' => 0, + 'msg' => 'thinkphp', + 'data' => ['foo'], + 'url' => 'http://www.thinkphp.cn', + 'wait' => 4, + ]) + ]; + $provideData[] = [$arguments, $expected, ['return' => 'html']]; + + $arguments = ['thinkphp', '', ['foo'], 5, []]; + $expected = [ + 'header' => [ + 'Content-Type' => 'application/json; charset=utf-8' + ], + 'data' => [ + 'code' => 0, + 'msg' => 'thinkphp', + 'data' => ['foo'], + 'url' => '', + 'wait' => 5, + ] + ]; + $provideData[] = [$arguments, $expected, ['return' => 'json']]; + + return $provideData; + } + + public function provideTestResult() + { + $provideData = []; + + $arguments = [null, 0, '', '', []]; + $expected = [ + 'header' => [ + 'Content-Type' => 'text/html; charset=utf-8' + ], + 'data' => [ + 'code' => 0, + 'msg' => '', + 'time' => Request::create('')->server('REQUEST_TIME'), + 'data' => null, + ] + ]; + $provideData[] = [$arguments, $expected, ['return' => 'html']]; + + $arguments = [['foo'], 200, 'thinkphp', 'json', ['Power-By' => 'thinkphp']]; + $expected = [ + 'header' => [ + 'Power-By' => 'thinkphp', + 'Content-Type' => 'application/json; charset=utf-8' + ], + 'data' => [ + 'code' => 200, + 'msg' => 'thinkphp', + 'time' => 1000, + 'data' => ['foo'], + ] + ]; + + $provideData[] = [$arguments, $expected, ['server' => ['REQUEST_TIME' => 1000], 'return' => 'json']]; + + return $provideData; + } + + public function provideTestRedirect() + { + $provideData = []; + + $arguments = ['', [], 302, []]; + $expected = [ + 'code'=> 302, + 'url' => '/index.php/' + ]; + $provideData[] = [$arguments, $expected, []]; + + $arguments = ['index', 302, null, []]; + $expected = [ + 'code'=> 302, + 'url' => '/index.php/index.html' + ]; + $provideData[] = [$arguments, $expected, []]; + + $arguments = ['http://www.thinkphp.cn', 301, 302, []]; + $expected = [ + 'code'=> 301, + 'url' => 'http://www.thinkphp.cn' + ]; + $provideData[] = [$arguments, $expected, []]; + + return $provideData; + } +} + +class testClassWithJump +{ + use Jump { + success as public; + error as public; + result as public; + redirect as public; + getResponseType as public; + } +} diff --git a/thinkphp/tests/thinkphp/library/traits/model/softDeleteTest.php b/thinkphp/tests/thinkphp/library/traits/model/softDeleteTest.php new file mode 100644 index 0000000..8410efa --- /dev/null +++ b/thinkphp/tests/thinkphp/library/traits/model/softDeleteTest.php @@ -0,0 +1,179 @@ +connection; + + $sql[] = <<execute($one); + } + } + + public function testTrashed() + { + /** @var testClassWithSoftDelete[] $selections */ + $selections = testClassWithSoftDelete::withTrashed()->select(); + + $this->assertFalse($selections[0]->trashed()); + $this->assertTrue($selections[1]->trashed()); + $this->assertTrue($selections[2]->trashed()); + } + + public function testDefaultTrashed() + { + $this->assertCount(3, testClassWithSoftDelete::all()); + } + + public function testWithTrashed() + { + $this->assertCount(5, testClassWithSoftDelete::withTrashed()->select()); + } + + public function testOnlyTrashed() + { + $this->assertCount(2, testClassWithSoftDelete::onlyTrashed()->select()); + } + + public function testSoftDelete() + { + $this->assertEquals(1, testClassWithSoftDelete::get(1)->delete()); + $this->assertNotNull(testClassWithSoftDelete::withTrashed()->find(1)->getData('delete_time')); + } + + public function testForceDelete() + { + $this->assertEquals(1, testClassWithSoftDelete::get(1)->delete(true)); + $this->assertNull(testClassWithSoftDelete::get(1)); + } + + public function testSoftDestroy() + { + $this->assertEquals(5, testClassWithSoftDelete::destroy([1, 2, 3, 4, 5, 6])); + $this->assertNotNull(testClassWithSoftDelete::withTrashed()->find(2)->getData('delete_time')); + $this->assertNotEquals(self::TEST_TIME, testClassWithSoftDelete::withTrashed()->find(2)->getData('delete_time')); + $this->assertNotEquals(self::TEST_TIME, testClassWithSoftDelete::withTrashed()->find(3)->getData('delete_time')); + $this->assertNotNull(testClassWithSoftDelete::withTrashed()->find(4)->getData('delete_time')); + $this->assertNotNull(testClassWithSoftDelete::withTrashed()->find(5)->getData('delete_time')); + } + + public function testForceDestroy() + { + $this->assertEquals(5, testClassWithSoftDelete::destroy([1, 2, 3, 4, 5, 6], true)); + $this->assertNull(testClassWithSoftDelete::withTrashed()->find(1)); + $this->assertNull(testClassWithSoftDelete::withTrashed()->find(2)); + $this->assertNull(testClassWithSoftDelete::withTrashed()->find(3)); + $this->assertNull(testClassWithSoftDelete::withTrashed()->find(4)); + $this->assertNull(testClassWithSoftDelete::withTrashed()->find(5)); + } + + public function testRestore() + { + /** @var testClassWithSoftDelete[] $selections */ + $selections = testClassWithSoftDelete::withTrashed()->select(); + + $this->assertEquals(0, $selections[0]->restore()); + $this->assertEquals(1, $selections[1]->restore()); + $this->assertEquals(1, $selections[2]->restore()); + $this->assertEquals(0, $selections[3]->restore()); + $this->assertEquals(0, $selections[4]->restore()); + + $this->assertNull(testClassWithSoftDelete::withTrashed()->find(1)->getData('delete_time')); + $this->assertNull(testClassWithSoftDelete::withTrashed()->find(2)->getData('delete_time')); + } + + public function testGetDeleteTimeField() + { + $testClass = new testClassWithSoftDelete(); + + $this->assertEquals('delete_time', $testClass->getDeleteTimeField()); + + $testClass->deleteTime = 'create_time'; + $this->assertEquals('create_time', $testClass->getDeleteTimeField()); + + $testClass->deleteTime = 'test.create_time'; + $this->assertEquals('create_time', $testClass->getDeleteTimeField()); + + $testClass->deleteTime = 'create_time'; + $this->assertEquals('__TABLE__.create_time', $testClass->getDeleteTimeField(true)); + } +} + +class testClassWithSoftDelete extends Model +{ + public $table = 'tp_soft_delete'; + + public $deleteTime = 'delete_time'; + + public $connection = [ + // 数据库类型 + 'type' => 'mysql', + // 服务器地址 + 'hostname' => '127.0.0.1', + // 数据库名 + 'database' => 'test', + // 用户名 + 'username' => 'root', + // 密码 + 'password' => '', + // 端口 + 'hostport' => '', + // 连接dsn + 'dsn' => '', + // 数据库连接参数 + 'params' => [], + // 数据库编码默认采用utf8 + 'charset' => 'utf8', + // 数据库表前缀 + 'prefix' => '', + // 数据库调试模式 + 'debug' => true, + // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) + 'deploy' => 0, + // 数据库读写是否分离 主从式有效 + 'rw_separate' => false, + // 读写分离后 主服务器数量 + 'master_num' => 1, + // 指定从服务器序号 + 'slave_no' => '', + // 是否严格检查字段是否存在 + 'fields_strict' => true, + // 数据集返回类型 array 数组 collection Collection对象 + 'resultset_type' => 'array', + // 是否自动写入时间戳字段 + 'auto_timestamp' => false, + // 是否需要进行SQL性能分析 + 'sql_explain' => false, + ]; + + use SoftDelete { + getDeleteTimeField as public; + } +} diff --git a/thinkphp/tests/thinkphp/library/traits/think/instanceTest.php b/thinkphp/tests/thinkphp/library/traits/think/instanceTest.php new file mode 100644 index 0000000..f93f22b --- /dev/null +++ b/thinkphp/tests/thinkphp/library/traits/think/instanceTest.php @@ -0,0 +1,60 @@ +assertInstanceOf('\tests\thinkphp\library\traits\think\InstanceTestFather', $father); + $this->assertEquals([], $father->options); + + $son = InstanceTestFather::instance(['son']); + $this->assertSame($father, $son); + } + + public function testCallStatic() + { + $father = InstanceTestFather::instance(); + $this->assertEquals([], $father->options); + + $this->assertEquals($father::__protectedStaticFunc(['thinkphp']), 'protectedStaticFunc["thinkphp"]'); + + try { + $father::_protectedStaticFunc(); + $this->setExpectedException('\think\Exception'); + } catch (\Exception $e) { + $this->assertInstanceOf('\think\Exception', $e); + } + } + + protected function tearDown() + { + call_user_func(\Closure::bind(function () { + InstanceTestFather::$instance = null; + }, null, '\tests\thinkphp\library\traits\think\InstanceTestFather')); + } +} + +class InstanceTestFather +{ + use Instance; + + public $options = null; + + public function __construct($options) + { + $this->options = $options; + } + + protected static function _protectedStaticFunc($params) + { + return 'protectedStaticFunc' . json_encode($params); + } +} + +class InstanceTestSon extends InstanceTestFather +{ +} diff --git a/thinkphp/tpl/default_index.tpl b/thinkphp/tpl/default_index.tpl new file mode 100644 index 0000000..8538b4d --- /dev/null +++ b/thinkphp/tpl/default_index.tpl @@ -0,0 +1,10 @@ +*{ padding: 0; margin: 0; } div{ padding: 4px 48px;} a{color:#2E5CD5;cursor: pointer;text-decoration: none} a:hover{text-decoration:underline; } body{ background: #fff; font-family: "Century Gothic","Microsoft yahei"; color: #333;font-size:18px;} h1{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.6em; font-size: 42px }

    :)

    ThinkPHP V5
    十年磨一剑 - 为API开发设计的高性能框架

    [ V5.0 版本由 七牛云 独家赞助发布 ]
    '; + } +} diff --git a/thinkphp/tpl/dispatch_jump.tpl b/thinkphp/tpl/dispatch_jump.tpl new file mode 100644 index 0000000..583376b --- /dev/null +++ b/thinkphp/tpl/dispatch_jump.tpl @@ -0,0 +1,49 @@ +{__NOLAYOUT__} + + + + + 跳转提示 + + + +
    + + +

    :)

    +

    + + +

    :(

    +

    + + +

    +

    + 页面自动 跳转 等待时间: +

    +
    + + + diff --git a/thinkphp/tpl/page_trace.tpl b/thinkphp/tpl/page_trace.tpl new file mode 100644 index 0000000..7c5df6f --- /dev/null +++ b/thinkphp/tpl/page_trace.tpl @@ -0,0 +1,71 @@ +
    + + +
    +
    +
    + +
    + + diff --git a/thinkphp/tpl/think_exception.tpl b/thinkphp/tpl/think_exception.tpl new file mode 100644 index 0000000..21bbafc --- /dev/null +++ b/thinkphp/tpl/think_exception.tpl @@ -0,0 +1,537 @@ +'.end($names).''; + } + } + + if(!function_exists('parse_file')){ + function parse_file($file, $line) + { + return ''.basename($file)." line {$line}".''; + } + } + + if(!function_exists('parse_args')){ + function parse_args($args) + { + $result = []; + + foreach ($args as $key => $item) { + switch (true) { + case is_object($item): + $value = sprintf('object(%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( + '\'%s...\'', + 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 = 'null'; + break; + case is_bool($item): + $value = '' . ($item ? 'true' : 'false') . ''; + break; + case is_resource($item): + $value = 'resource'; + break; + default: + $value = htmlentities(str_replace("\n", '', var_export(strval($item), true))); + break; + } + + $result[] = is_int($key) ? $value : "'{$key}' => {$value}"; + } + + return implode(', ', $result); + } + } +?> + + + + + <?php echo \think\Lang::get('System Error'); ?> + + + + + +
    + +
    + +
    +
    + +
    +
    +

    [

    +
    +

    +
    + +
    + +
    +
      $value) { ?>
    +
    + +
    +

    Call Stack

    +
      +
    1. + +
    2. + +
    3. + +
    +
    +
    + +
    + +

    + +
    + + + +
    +

    Exception Datas

    + $value) { ?> + + + + + + + $val) { ?> + + + + + + + +
    empty
    + +
    + +
    + + + +
    +

    Environment Variables

    + $value) { ?> +
    + +
    +
    +
    empty
    +
    + +

    +
    + $val) { ?> +
    +
    +
    + +
    +
    + +
    + +
    + +
    + + + + + + + + diff --git a/vendor/autoload.php b/vendor/autoload.php new file mode 100644 index 0000000..7fca814 --- /dev/null +++ b/vendor/autoload.php @@ -0,0 +1,7 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see http://www.php-fig.org/psr/psr-0/ + * @see http://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + // PSR-4 + private $prefixLengthsPsr4 = array(); + private $prefixDirsPsr4 = array(); + private $fallbackDirsPsr4 = array(); + + // PSR-0 + private $prefixesPsr0 = array(); + private $fallbackDirsPsr0 = array(); + + private $useIncludePath = false; + private $classMap = array(); + private $classMapAuthoritative = false; + private $missingClasses = array(); + + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', $this->prefixesPsr0); + } + + return array(); + } + + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 base directories + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return bool|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + includeFile($file); + + return true; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731 + if ('\\' == $class[0]) { + $class = substr($class, 1); + } + + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) { + if (0 === strpos($class, $prefix)) { + foreach ($this->prefixDirsPsr4[$prefix] as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } +} + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + */ +function includeFile($file) +{ + include $file; +} diff --git a/vendor/composer/LICENSE b/vendor/composer/LICENSE new file mode 100644 index 0000000..1a28124 --- /dev/null +++ b/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) 2016 Nils Adermann, Jordi Boggiano + +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/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000..6afcbef --- /dev/null +++ b/vendor/composer/autoload_classmap.php @@ -0,0 +1,16 @@ + $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', + 'JsonException' => $vendorDir . '/symfony/polyfill-php73/Resources/stubs/JsonException.php', + 'Normalizer' => $vendorDir . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php', + 'PhpToken' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', + 'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', + 'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', + 'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', +); diff --git a/vendor/composer/autoload_files.php b/vendor/composer/autoload_files.php new file mode 100644 index 0000000..f037525 --- /dev/null +++ b/vendor/composer/autoload_files.php @@ -0,0 +1,83 @@ + $vendorDir . '/symfony/polyfill-php80/bootstrap.php', + '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', + '25072dd6e2470089de65ae7bf11d3109' => $vendorDir . '/symfony/polyfill-php72/bootstrap.php', + 'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php', + 'f598d06aa772fa33d905e87be6398fb1' => $vendorDir . '/symfony/polyfill-intl-idn/bootstrap.php', + '7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php', + 'a0edc8309cc5e1d60e3047b5df6b7052' => $vendorDir . '/guzzlehttp/psr7/src/functions_include.php', + 'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php', + '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php', + '0d59ee240a4cd96ddbb4ff164fccea4d' => $vendorDir . '/symfony/polyfill-php73/bootstrap.php', + '9b552a3cc426e3287cc811caefa3cf53' => $vendorDir . '/topthink/think-helper/src/helper.php', + '3af723442581d6c310bf44543f9f5c60' => $vendorDir . '/markbaker/matrix/classes/src/Functions/adjoint.php', + 'd803221834c8b57fec95debb5406a797' => $vendorDir . '/markbaker/matrix/classes/src/Functions/antidiagonal.php', + '4714cafbd3be4c72c274a25eae9396bb' => $vendorDir . '/markbaker/matrix/classes/src/Functions/cofactors.php', + '89719dc7c77436609d1c1c31f0797b8f' => $vendorDir . '/markbaker/matrix/classes/src/Functions/determinant.php', + 'c28af79ec7730859d83f2d4310b8dd0b' => $vendorDir . '/markbaker/matrix/classes/src/Functions/diagonal.php', + 'c5d82bf1ac485e445f911e55789ab4e6' => $vendorDir . '/markbaker/matrix/classes/src/Functions/identity.php', + '0d2d594de24a247f7a33499e933aa21e' => $vendorDir . '/markbaker/matrix/classes/src/Functions/inverse.php', + 'f37c25880804a014ef40c8bffbab1b10' => $vendorDir . '/markbaker/matrix/classes/src/Functions/minors.php', + 'd6e4e42171df0dbea253b3067fefda38' => $vendorDir . '/markbaker/matrix/classes/src/Functions/trace.php', + '2c9b19fa954fd3e6fcc7e7a1383caddd' => $vendorDir . '/markbaker/matrix/classes/src/Functions/transpose.php', + '0a538fc9b897450ec362480ebbebe94f' => $vendorDir . '/markbaker/matrix/classes/src/Operations/add.php', + 'f0843f7f4089ec2343c7445544356385' => $vendorDir . '/markbaker/matrix/classes/src/Operations/directsum.php', + 'ad3e8c29aa16d134661a414265677b61' => $vendorDir . '/markbaker/matrix/classes/src/Operations/subtract.php', + '8d37dad4703fab45bfec9dd0bbf3278e' => $vendorDir . '/markbaker/matrix/classes/src/Operations/multiply.php', + '4888a6f58c08148ebe17682f9ce9b2a8' => $vendorDir . '/markbaker/matrix/classes/src/Operations/divideby.php', + 'eef6fa3879d3efa347cd24d5eb348f85' => $vendorDir . '/markbaker/matrix/classes/src/Operations/divideinto.php', + 'abede361264e2ae69ec1eee813a101af' => $vendorDir . '/markbaker/complex/classes/src/functions/abs.php', + '21a5860fbef5be28db5ddfbc3cca67c4' => $vendorDir . '/markbaker/complex/classes/src/functions/acos.php', + '1546e3f9d127f2a9bb2d1b6c31c26ef1' => $vendorDir . '/markbaker/complex/classes/src/functions/acosh.php', + 'd2516f7f4fba5ea5905f494b4a8262e0' => $vendorDir . '/markbaker/complex/classes/src/functions/acot.php', + '4511163d560956219b96882c0980b65e' => $vendorDir . '/markbaker/complex/classes/src/functions/acoth.php', + 'c361f5616dc2a8da4fa3e137077cd4ea' => $vendorDir . '/markbaker/complex/classes/src/functions/acsc.php', + '02d68920fc98da71991ce569c91df0f6' => $vendorDir . '/markbaker/complex/classes/src/functions/acsch.php', + '88e19525eae308b4a6aa3419364875d3' => $vendorDir . '/markbaker/complex/classes/src/functions/argument.php', + '60e8e2d0827b58bfc904f13957e51849' => $vendorDir . '/markbaker/complex/classes/src/functions/asec.php', + '13d2f040713999eab66c359b4d79871d' => $vendorDir . '/markbaker/complex/classes/src/functions/asech.php', + '838ab38beb32c68a79d3cd2c007d5a04' => $vendorDir . '/markbaker/complex/classes/src/functions/asin.php', + 'bb28eccd0f8f008333a1b3c163d604ac' => $vendorDir . '/markbaker/complex/classes/src/functions/asinh.php', + '9e483de83558c98f7d3feaa402c78cb3' => $vendorDir . '/markbaker/complex/classes/src/functions/atan.php', + '36b74b5b765ded91ee58c8ee3c0e85e3' => $vendorDir . '/markbaker/complex/classes/src/functions/atanh.php', + '05c15ee9510da7fd6bf6136f436500c0' => $vendorDir . '/markbaker/complex/classes/src/functions/conjugate.php', + 'd3208dfbce2505e370788f9f22f6785f' => $vendorDir . '/markbaker/complex/classes/src/functions/cos.php', + '141cf1fb3a3046f8b64534b0ebab33ca' => $vendorDir . '/markbaker/complex/classes/src/functions/cosh.php', + 'be660df75fd0dbe7fa7c03b7434b3294' => $vendorDir . '/markbaker/complex/classes/src/functions/cot.php', + '01e31ea298a51bc9e91517e3ce6b9e76' => $vendorDir . '/markbaker/complex/classes/src/functions/coth.php', + '803ddd97f7b1da68982a7b087c3476f6' => $vendorDir . '/markbaker/complex/classes/src/functions/csc.php', + '3001cdfd101ec3c32da34ee43c2e149b' => $vendorDir . '/markbaker/complex/classes/src/functions/csch.php', + '77b2d7629ef2a93fabb8c56754a91051' => $vendorDir . '/markbaker/complex/classes/src/functions/exp.php', + '4a4471296dec796c21d4f4b6552396a9' => $vendorDir . '/markbaker/complex/classes/src/functions/inverse.php', + 'c3e9897e1744b88deb56fcdc39d34d85' => $vendorDir . '/markbaker/complex/classes/src/functions/ln.php', + 'a83cacf2de942cff288de15a83afd26d' => $vendorDir . '/markbaker/complex/classes/src/functions/log2.php', + '6a861dacc9ee2f3061241d4c7772fa21' => $vendorDir . '/markbaker/complex/classes/src/functions/log10.php', + '4d2522d968c8ba78d6c13548a1b4200e' => $vendorDir . '/markbaker/complex/classes/src/functions/negative.php', + 'fd587ca933fc0447fa5ab4843bdd97f7' => $vendorDir . '/markbaker/complex/classes/src/functions/pow.php', + '383ef01c62028fc78cd4388082fce3c2' => $vendorDir . '/markbaker/complex/classes/src/functions/rho.php', + '150fbd1b95029dc47292da97ecab9375' => $vendorDir . '/markbaker/complex/classes/src/functions/sec.php', + '549abd9bae174286d660bdaa07407c68' => $vendorDir . '/markbaker/complex/classes/src/functions/sech.php', + '6bfbf5eaea6b17a0ed85cb21ba80370c' => $vendorDir . '/markbaker/complex/classes/src/functions/sin.php', + '22efe13f1a497b8e199540ae2d9dc59c' => $vendorDir . '/markbaker/complex/classes/src/functions/sinh.php', + 'e90135ab8e787795a509ed7147de207d' => $vendorDir . '/markbaker/complex/classes/src/functions/sqrt.php', + 'bb0a7923ffc6a90919cd64ec54ff06bc' => $vendorDir . '/markbaker/complex/classes/src/functions/tan.php', + '2d302f32ce0fd4e433dd91c5bb404a28' => $vendorDir . '/markbaker/complex/classes/src/functions/tanh.php', + '24dd4658a952171a4ee79218c4f9fd06' => $vendorDir . '/markbaker/complex/classes/src/functions/theta.php', + 'e49b7876281d6f5bc39536dde96d1f4a' => $vendorDir . '/markbaker/complex/classes/src/operations/add.php', + '47596e02b43cd6da7700134fd08f88cf' => $vendorDir . '/markbaker/complex/classes/src/operations/subtract.php', + '883af48563631547925fa4c3b48ead07' => $vendorDir . '/markbaker/complex/classes/src/operations/multiply.php', + 'f190e3308e6ca23234a2875edc985c03' => $vendorDir . '/markbaker/complex/classes/src/operations/divideby.php', + 'ac9e33ce6841aa5bf5d16d465a2f03a7' => $vendorDir . '/markbaker/complex/classes/src/operations/divideinto.php', + 'cc56288302d9df745d97c934d6a6e5f0' => $vendorDir . '/topthink/think-queue/src/common.php', + 'f0e7e63bbb278a92db02393536748c5f' => $vendorDir . '/overtrue/wechat/src/Kernel/Support/Helpers.php', + '6747f579ad6817f318cc3a7e7a0abb93' => $vendorDir . '/overtrue/wechat/src/Kernel/Helpers.php', + '1cfd2761b63b0a29ed23657ea394cb2d' => $vendorDir . '/topthink/think-captcha/src/helper.php', + '488987c28e9b5e95a1ce6b6bcb94606c' => $vendorDir . '/karsonzhang/fastadmin-addons/src/common.php', +); diff --git a/vendor/composer/autoload_namespaces.php b/vendor/composer/autoload_namespaces.php new file mode 100644 index 0000000..c3cd022 --- /dev/null +++ b/vendor/composer/autoload_namespaces.php @@ -0,0 +1,10 @@ + array($vendorDir . '/pimple/pimple/src'), +); diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php new file mode 100644 index 0000000..7d31c6c --- /dev/null +++ b/vendor/composer/autoload_psr4.php @@ -0,0 +1,47 @@ + array($vendorDir . '/topthink/think-helper/src'), + 'think\\composer\\' => array($vendorDir . '/topthink/think-installer/src'), + 'think\\captcha\\' => array($vendorDir . '/topthink/think-captcha/src'), + 'think\\' => array($vendorDir . '/topthink/think-queue/src', $baseDir . '/thinkphp/library/think', $vendorDir . '/karsonzhang/fastadmin-addons/src'), + 'Tx\\' => array($vendorDir . '/txthinking/mailer/src'), + 'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'), + 'Symfony\\Polyfill\\Php73\\' => array($vendorDir . '/symfony/polyfill-php73'), + 'Symfony\\Polyfill\\Php72\\' => array($vendorDir . '/symfony/polyfill-php72'), + 'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'), + 'Symfony\\Polyfill\\Intl\\Normalizer\\' => array($vendorDir . '/symfony/polyfill-intl-normalizer'), + 'Symfony\\Polyfill\\Intl\\Idn\\' => array($vendorDir . '/symfony/polyfill-intl-idn'), + 'Symfony\\Contracts\\Service\\' => array($vendorDir . '/symfony/service-contracts'), + 'Symfony\\Contracts\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher-contracts'), + 'Symfony\\Contracts\\Cache\\' => array($vendorDir . '/symfony/cache-contracts'), + 'Symfony\\Component\\VarExporter\\' => array($vendorDir . '/symfony/var-exporter'), + 'Symfony\\Component\\Mime\\' => array($vendorDir . '/symfony/mime'), + 'Symfony\\Component\\HttpFoundation\\' => array($vendorDir . '/symfony/http-foundation'), + 'Symfony\\Component\\Finder\\' => array($vendorDir . '/symfony/finder'), + 'Symfony\\Component\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher'), + 'Symfony\\Component\\Cache\\' => array($vendorDir . '/symfony/cache'), + 'Symfony\\Bridge\\PsrHttpMessage\\' => array($vendorDir . '/symfony/psr-http-message-bridge'), + 'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'), + 'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'), + 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src'), + 'Psr\\Container\\' => array($vendorDir . '/psr/container/src'), + 'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'), + 'PhpZip\\' => array($vendorDir . '/nelexa/zip/src'), + 'PhpOffice\\PhpSpreadsheet\\' => array($vendorDir . '/phpoffice/phpspreadsheet/src/PhpSpreadsheet'), + 'Overtrue\\Socialite\\' => array($vendorDir . '/overtrue/socialite/src'), + 'Overtrue\\Pinyin\\' => array($vendorDir . '/overtrue/pinyin/src'), + 'Monolog\\' => array($vendorDir . '/monolog/monolog/src/Monolog'), + 'Matrix\\' => array($vendorDir . '/markbaker/matrix/classes/src'), + 'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'), + 'GuzzleHttp\\Promise\\' => array($vendorDir . '/guzzlehttp/promises/src'), + 'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'), + 'EasyWeChat\\' => array($vendorDir . '/overtrue/wechat/src'), + 'EasyWeChatComposer\\' => array($vendorDir . '/easywechat-composer/easywechat-composer/src'), + 'Complex\\' => array($vendorDir . '/markbaker/complex/classes/src'), +); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php new file mode 100644 index 0000000..5256752 --- /dev/null +++ b/vendor/composer/autoload_real.php @@ -0,0 +1,70 @@ += 50600 && !defined('HHVM_VERSION'); + if ($useStaticLoader) { + require_once __DIR__ . '/autoload_static.php'; + + call_user_func(\Composer\Autoload\ComposerStaticInit73f9e72fede2c36621e52f7b610bbb65::getInitializer($loader)); + } else { + $map = require __DIR__ . '/autoload_namespaces.php'; + foreach ($map as $namespace => $path) { + $loader->set($namespace, $path); + } + + $map = require __DIR__ . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + + $classMap = require __DIR__ . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + } + + $loader->register(true); + + if ($useStaticLoader) { + $includeFiles = Composer\Autoload\ComposerStaticInit73f9e72fede2c36621e52f7b610bbb65::$files; + } else { + $includeFiles = require __DIR__ . '/autoload_files.php'; + } + foreach ($includeFiles as $fileIdentifier => $file) { + composerRequire73f9e72fede2c36621e52f7b610bbb65($fileIdentifier, $file); + } + + return $loader; + } +} + +function composerRequire73f9e72fede2c36621e52f7b610bbb65($fileIdentifier, $file) +{ + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + require $file; + + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + } +} diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php new file mode 100644 index 0000000..3f685f6 --- /dev/null +++ b/vendor/composer/autoload_static.php @@ -0,0 +1,341 @@ + __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php', + '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', + '25072dd6e2470089de65ae7bf11d3109' => __DIR__ . '/..' . '/symfony/polyfill-php72/bootstrap.php', + 'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php', + 'f598d06aa772fa33d905e87be6398fb1' => __DIR__ . '/..' . '/symfony/polyfill-intl-idn/bootstrap.php', + '7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php', + 'a0edc8309cc5e1d60e3047b5df6b7052' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/functions_include.php', + 'c964ee0ededf28c96ebd9db5099ef910' => __DIR__ . '/..' . '/guzzlehttp/promises/src/functions_include.php', + '37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php', + '0d59ee240a4cd96ddbb4ff164fccea4d' => __DIR__ . '/..' . '/symfony/polyfill-php73/bootstrap.php', + '9b552a3cc426e3287cc811caefa3cf53' => __DIR__ . '/..' . '/topthink/think-helper/src/helper.php', + '3af723442581d6c310bf44543f9f5c60' => __DIR__ . '/..' . '/markbaker/matrix/classes/src/Functions/adjoint.php', + 'd803221834c8b57fec95debb5406a797' => __DIR__ . '/..' . '/markbaker/matrix/classes/src/Functions/antidiagonal.php', + '4714cafbd3be4c72c274a25eae9396bb' => __DIR__ . '/..' . '/markbaker/matrix/classes/src/Functions/cofactors.php', + '89719dc7c77436609d1c1c31f0797b8f' => __DIR__ . '/..' . '/markbaker/matrix/classes/src/Functions/determinant.php', + 'c28af79ec7730859d83f2d4310b8dd0b' => __DIR__ . '/..' . '/markbaker/matrix/classes/src/Functions/diagonal.php', + 'c5d82bf1ac485e445f911e55789ab4e6' => __DIR__ . '/..' . '/markbaker/matrix/classes/src/Functions/identity.php', + '0d2d594de24a247f7a33499e933aa21e' => __DIR__ . '/..' . '/markbaker/matrix/classes/src/Functions/inverse.php', + 'f37c25880804a014ef40c8bffbab1b10' => __DIR__ . '/..' . '/markbaker/matrix/classes/src/Functions/minors.php', + 'd6e4e42171df0dbea253b3067fefda38' => __DIR__ . '/..' . '/markbaker/matrix/classes/src/Functions/trace.php', + '2c9b19fa954fd3e6fcc7e7a1383caddd' => __DIR__ . '/..' . '/markbaker/matrix/classes/src/Functions/transpose.php', + '0a538fc9b897450ec362480ebbebe94f' => __DIR__ . '/..' . '/markbaker/matrix/classes/src/Operations/add.php', + 'f0843f7f4089ec2343c7445544356385' => __DIR__ . '/..' . '/markbaker/matrix/classes/src/Operations/directsum.php', + 'ad3e8c29aa16d134661a414265677b61' => __DIR__ . '/..' . '/markbaker/matrix/classes/src/Operations/subtract.php', + '8d37dad4703fab45bfec9dd0bbf3278e' => __DIR__ . '/..' . '/markbaker/matrix/classes/src/Operations/multiply.php', + '4888a6f58c08148ebe17682f9ce9b2a8' => __DIR__ . '/..' . '/markbaker/matrix/classes/src/Operations/divideby.php', + 'eef6fa3879d3efa347cd24d5eb348f85' => __DIR__ . '/..' . '/markbaker/matrix/classes/src/Operations/divideinto.php', + 'abede361264e2ae69ec1eee813a101af' => __DIR__ . '/..' . '/markbaker/complex/classes/src/functions/abs.php', + '21a5860fbef5be28db5ddfbc3cca67c4' => __DIR__ . '/..' . '/markbaker/complex/classes/src/functions/acos.php', + '1546e3f9d127f2a9bb2d1b6c31c26ef1' => __DIR__ . '/..' . '/markbaker/complex/classes/src/functions/acosh.php', + 'd2516f7f4fba5ea5905f494b4a8262e0' => __DIR__ . '/..' . '/markbaker/complex/classes/src/functions/acot.php', + '4511163d560956219b96882c0980b65e' => __DIR__ . '/..' . '/markbaker/complex/classes/src/functions/acoth.php', + 'c361f5616dc2a8da4fa3e137077cd4ea' => __DIR__ . '/..' . '/markbaker/complex/classes/src/functions/acsc.php', + '02d68920fc98da71991ce569c91df0f6' => __DIR__ . '/..' . '/markbaker/complex/classes/src/functions/acsch.php', + '88e19525eae308b4a6aa3419364875d3' => __DIR__ . '/..' . '/markbaker/complex/classes/src/functions/argument.php', + '60e8e2d0827b58bfc904f13957e51849' => __DIR__ . '/..' . '/markbaker/complex/classes/src/functions/asec.php', + '13d2f040713999eab66c359b4d79871d' => __DIR__ . '/..' . '/markbaker/complex/classes/src/functions/asech.php', + '838ab38beb32c68a79d3cd2c007d5a04' => __DIR__ . '/..' . '/markbaker/complex/classes/src/functions/asin.php', + 'bb28eccd0f8f008333a1b3c163d604ac' => __DIR__ . '/..' . '/markbaker/complex/classes/src/functions/asinh.php', + '9e483de83558c98f7d3feaa402c78cb3' => __DIR__ . '/..' . '/markbaker/complex/classes/src/functions/atan.php', + '36b74b5b765ded91ee58c8ee3c0e85e3' => __DIR__ . '/..' . '/markbaker/complex/classes/src/functions/atanh.php', + '05c15ee9510da7fd6bf6136f436500c0' => __DIR__ . '/..' . '/markbaker/complex/classes/src/functions/conjugate.php', + 'd3208dfbce2505e370788f9f22f6785f' => __DIR__ . '/..' . '/markbaker/complex/classes/src/functions/cos.php', + '141cf1fb3a3046f8b64534b0ebab33ca' => __DIR__ . '/..' . '/markbaker/complex/classes/src/functions/cosh.php', + 'be660df75fd0dbe7fa7c03b7434b3294' => __DIR__ . '/..' . '/markbaker/complex/classes/src/functions/cot.php', + '01e31ea298a51bc9e91517e3ce6b9e76' => __DIR__ . '/..' . '/markbaker/complex/classes/src/functions/coth.php', + '803ddd97f7b1da68982a7b087c3476f6' => __DIR__ . '/..' . '/markbaker/complex/classes/src/functions/csc.php', + '3001cdfd101ec3c32da34ee43c2e149b' => __DIR__ . '/..' . '/markbaker/complex/classes/src/functions/csch.php', + '77b2d7629ef2a93fabb8c56754a91051' => __DIR__ . '/..' . '/markbaker/complex/classes/src/functions/exp.php', + '4a4471296dec796c21d4f4b6552396a9' => __DIR__ . '/..' . '/markbaker/complex/classes/src/functions/inverse.php', + 'c3e9897e1744b88deb56fcdc39d34d85' => __DIR__ . '/..' . '/markbaker/complex/classes/src/functions/ln.php', + 'a83cacf2de942cff288de15a83afd26d' => __DIR__ . '/..' . '/markbaker/complex/classes/src/functions/log2.php', + '6a861dacc9ee2f3061241d4c7772fa21' => __DIR__ . '/..' . '/markbaker/complex/classes/src/functions/log10.php', + '4d2522d968c8ba78d6c13548a1b4200e' => __DIR__ . '/..' . '/markbaker/complex/classes/src/functions/negative.php', + 'fd587ca933fc0447fa5ab4843bdd97f7' => __DIR__ . '/..' . '/markbaker/complex/classes/src/functions/pow.php', + '383ef01c62028fc78cd4388082fce3c2' => __DIR__ . '/..' . '/markbaker/complex/classes/src/functions/rho.php', + '150fbd1b95029dc47292da97ecab9375' => __DIR__ . '/..' . '/markbaker/complex/classes/src/functions/sec.php', + '549abd9bae174286d660bdaa07407c68' => __DIR__ . '/..' . '/markbaker/complex/classes/src/functions/sech.php', + '6bfbf5eaea6b17a0ed85cb21ba80370c' => __DIR__ . '/..' . '/markbaker/complex/classes/src/functions/sin.php', + '22efe13f1a497b8e199540ae2d9dc59c' => __DIR__ . '/..' . '/markbaker/complex/classes/src/functions/sinh.php', + 'e90135ab8e787795a509ed7147de207d' => __DIR__ . '/..' . '/markbaker/complex/classes/src/functions/sqrt.php', + 'bb0a7923ffc6a90919cd64ec54ff06bc' => __DIR__ . '/..' . '/markbaker/complex/classes/src/functions/tan.php', + '2d302f32ce0fd4e433dd91c5bb404a28' => __DIR__ . '/..' . '/markbaker/complex/classes/src/functions/tanh.php', + '24dd4658a952171a4ee79218c4f9fd06' => __DIR__ . '/..' . '/markbaker/complex/classes/src/functions/theta.php', + 'e49b7876281d6f5bc39536dde96d1f4a' => __DIR__ . '/..' . '/markbaker/complex/classes/src/operations/add.php', + '47596e02b43cd6da7700134fd08f88cf' => __DIR__ . '/..' . '/markbaker/complex/classes/src/operations/subtract.php', + '883af48563631547925fa4c3b48ead07' => __DIR__ . '/..' . '/markbaker/complex/classes/src/operations/multiply.php', + 'f190e3308e6ca23234a2875edc985c03' => __DIR__ . '/..' . '/markbaker/complex/classes/src/operations/divideby.php', + 'ac9e33ce6841aa5bf5d16d465a2f03a7' => __DIR__ . '/..' . '/markbaker/complex/classes/src/operations/divideinto.php', + 'cc56288302d9df745d97c934d6a6e5f0' => __DIR__ . '/..' . '/topthink/think-queue/src/common.php', + 'f0e7e63bbb278a92db02393536748c5f' => __DIR__ . '/..' . '/overtrue/wechat/src/Kernel/Support/Helpers.php', + '6747f579ad6817f318cc3a7e7a0abb93' => __DIR__ . '/..' . '/overtrue/wechat/src/Kernel/Helpers.php', + '1cfd2761b63b0a29ed23657ea394cb2d' => __DIR__ . '/..' . '/topthink/think-captcha/src/helper.php', + '488987c28e9b5e95a1ce6b6bcb94606c' => __DIR__ . '/..' . '/karsonzhang/fastadmin-addons/src/common.php', + ); + + public static $prefixLengthsPsr4 = array ( + 't' => + array ( + 'think\\helper\\' => 13, + 'think\\composer\\' => 15, + 'think\\captcha\\' => 14, + 'think\\' => 6, + ), + 'T' => + array ( + 'Tx\\' => 3, + ), + 'S' => + array ( + 'Symfony\\Polyfill\\Php80\\' => 23, + 'Symfony\\Polyfill\\Php73\\' => 23, + 'Symfony\\Polyfill\\Php72\\' => 23, + 'Symfony\\Polyfill\\Mbstring\\' => 26, + 'Symfony\\Polyfill\\Intl\\Normalizer\\' => 33, + 'Symfony\\Polyfill\\Intl\\Idn\\' => 26, + 'Symfony\\Contracts\\Service\\' => 26, + 'Symfony\\Contracts\\EventDispatcher\\' => 34, + 'Symfony\\Contracts\\Cache\\' => 24, + 'Symfony\\Component\\VarExporter\\' => 30, + 'Symfony\\Component\\Mime\\' => 23, + 'Symfony\\Component\\HttpFoundation\\' => 33, + 'Symfony\\Component\\Finder\\' => 25, + 'Symfony\\Component\\EventDispatcher\\' => 34, + 'Symfony\\Component\\Cache\\' => 24, + 'Symfony\\Bridge\\PsrHttpMessage\\' => 30, + ), + 'P' => + array ( + 'Psr\\SimpleCache\\' => 16, + 'Psr\\Log\\' => 8, + 'Psr\\Http\\Message\\' => 17, + 'Psr\\Container\\' => 14, + 'Psr\\Cache\\' => 10, + 'PhpZip\\' => 7, + 'PhpOffice\\PhpSpreadsheet\\' => 25, + ), + 'O' => + array ( + 'Overtrue\\Socialite\\' => 19, + 'Overtrue\\Pinyin\\' => 16, + ), + 'M' => + array ( + 'Monolog\\' => 8, + 'Matrix\\' => 7, + ), + 'G' => + array ( + 'GuzzleHttp\\Psr7\\' => 16, + 'GuzzleHttp\\Promise\\' => 19, + 'GuzzleHttp\\' => 11, + ), + 'E' => + array ( + 'EasyWeChat\\' => 11, + 'EasyWeChatComposer\\' => 19, + ), + 'C' => + array ( + 'Complex\\' => 8, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'think\\helper\\' => + array ( + 0 => __DIR__ . '/..' . '/topthink/think-helper/src', + ), + 'think\\composer\\' => + array ( + 0 => __DIR__ . '/..' . '/topthink/think-installer/src', + ), + 'think\\captcha\\' => + array ( + 0 => __DIR__ . '/..' . '/topthink/think-captcha/src', + ), + 'think\\' => + array ( + 0 => __DIR__ . '/..' . '/topthink/think-queue/src', + 1 => __DIR__ . '/../..' . '/thinkphp/library/think', + 2 => __DIR__ . '/..' . '/karsonzhang/fastadmin-addons/src', + ), + 'Tx\\' => + array ( + 0 => __DIR__ . '/..' . '/txthinking/mailer/src', + ), + 'Symfony\\Polyfill\\Php80\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php80', + ), + 'Symfony\\Polyfill\\Php73\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php73', + ), + 'Symfony\\Polyfill\\Php72\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php72', + ), + 'Symfony\\Polyfill\\Mbstring\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring', + ), + 'Symfony\\Polyfill\\Intl\\Normalizer\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer', + ), + 'Symfony\\Polyfill\\Intl\\Idn\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-intl-idn', + ), + 'Symfony\\Contracts\\Service\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/service-contracts', + ), + 'Symfony\\Contracts\\EventDispatcher\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/event-dispatcher-contracts', + ), + 'Symfony\\Contracts\\Cache\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/cache-contracts', + ), + 'Symfony\\Component\\VarExporter\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/var-exporter', + ), + 'Symfony\\Component\\Mime\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/mime', + ), + 'Symfony\\Component\\HttpFoundation\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/http-foundation', + ), + 'Symfony\\Component\\Finder\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/finder', + ), + 'Symfony\\Component\\EventDispatcher\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/event-dispatcher', + ), + 'Symfony\\Component\\Cache\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/cache', + ), + 'Symfony\\Bridge\\PsrHttpMessage\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/psr-http-message-bridge', + ), + 'Psr\\SimpleCache\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/simple-cache/src', + ), + 'Psr\\Log\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/log/Psr/Log', + ), + 'Psr\\Http\\Message\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/http-message/src', + ), + 'Psr\\Container\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/container/src', + ), + 'Psr\\Cache\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/cache/src', + ), + 'PhpZip\\' => + array ( + 0 => __DIR__ . '/..' . '/nelexa/zip/src', + ), + 'PhpOffice\\PhpSpreadsheet\\' => + array ( + 0 => __DIR__ . '/..' . '/phpoffice/phpspreadsheet/src/PhpSpreadsheet', + ), + 'Overtrue\\Socialite\\' => + array ( + 0 => __DIR__ . '/..' . '/overtrue/socialite/src', + ), + 'Overtrue\\Pinyin\\' => + array ( + 0 => __DIR__ . '/..' . '/overtrue/pinyin/src', + ), + 'Monolog\\' => + array ( + 0 => __DIR__ . '/..' . '/monolog/monolog/src/Monolog', + ), + 'Matrix\\' => + array ( + 0 => __DIR__ . '/..' . '/markbaker/matrix/classes/src', + ), + 'GuzzleHttp\\Psr7\\' => + array ( + 0 => __DIR__ . '/..' . '/guzzlehttp/psr7/src', + ), + 'GuzzleHttp\\Promise\\' => + array ( + 0 => __DIR__ . '/..' . '/guzzlehttp/promises/src', + ), + 'GuzzleHttp\\' => + array ( + 0 => __DIR__ . '/..' . '/guzzlehttp/guzzle/src', + ), + 'EasyWeChat\\' => + array ( + 0 => __DIR__ . '/..' . '/overtrue/wechat/src', + ), + 'EasyWeChatComposer\\' => + array ( + 0 => __DIR__ . '/..' . '/easywechat-composer/easywechat-composer/src', + ), + 'Complex\\' => + array ( + 0 => __DIR__ . '/..' . '/markbaker/complex/classes/src', + ), + ); + + public static $prefixesPsr0 = array ( + 'P' => + array ( + 'Pimple' => + array ( + 0 => __DIR__ . '/..' . '/pimple/pimple/src', + ), + ), + ); + + public static $classMap = array ( + 'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', + 'JsonException' => __DIR__ . '/..' . '/symfony/polyfill-php73/Resources/stubs/JsonException.php', + 'Normalizer' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php', + 'PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', + 'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', + 'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', + 'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInit73f9e72fede2c36621e52f7b610bbb65::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit73f9e72fede2c36621e52f7b610bbb65::$prefixDirsPsr4; + $loader->prefixesPsr0 = ComposerStaticInit73f9e72fede2c36621e52f7b610bbb65::$prefixesPsr0; + $loader->classMap = ComposerStaticInit73f9e72fede2c36621e52f7b610bbb65::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json new file mode 100644 index 0000000..6ed1633 --- /dev/null +++ b/vendor/composer/installed.json @@ -0,0 +1,2744 @@ +[ + { + "name": "topthink/think-installer", + "version": "v1.0.14", + "version_normalized": "1.0.14.0", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-installer.git", + "reference": "eae1740ac264a55c06134b6685dfb9f837d004d1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-installer/zipball/eae1740ac264a55c06134b6685dfb9f837d004d1", + "reference": "eae1740ac264a55c06134b6685dfb9f837d004d1", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "composer-plugin-api": "^1.0||^2.0" + }, + "require-dev": { + "composer/composer": "^1.0||^2.0" + }, + "time": "2021-03-25 08:34:02", + "type": "composer-plugin", + "extra": { + "class": "think\\composer\\Plugin" + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "think\\composer\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ] + }, + { + "name": "topthink/think-helper", + "version": "v1.0.7", + "version_normalized": "1.0.7.0", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-helper.git", + "reference": "5f92178606c8ce131d36b37a57c58eb71e55f019" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-helper/zipball/5f92178606c8ce131d36b37a57c58eb71e55f019", + "reference": "5f92178606c8ce131d36b37a57c58eb71e55f019", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "time": "2018-10-05 00:43:21", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "think\\helper\\": "src" + }, + "files": [ + "src/helper.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "description": "The ThinkPHP5 Helper Package" + }, + { + "name": "topthink/think-queue", + "version": "v1.1.6", + "version_normalized": "1.1.6.0", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-queue.git", + "reference": "250650eb0e8ea5af4cfdc7ae46f3f4e0a24ac245" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-queue/zipball/250650eb0e8ea5af4cfdc7ae46f3f4e0a24ac245", + "reference": "250650eb0e8ea5af4cfdc7ae46f3f4e0a24ac245", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "topthink/think-helper": ">=1.0.4", + "topthink/think-installer": ">=1.0.10" + }, + "require-dev": { + "topthink/framework": "~5.0.0" + }, + "time": "2018-10-15 10:16:55", + "type": "think-extend", + "extra": { + "think-config": { + "queue": "src/config.php" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "think\\": "src" + }, + "files": [ + "src/common.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "description": "The ThinkPHP5 Queue Package" + }, + { + "name": "psr/simple-cache", + "version": "1.0.1", + "version_normalized": "1.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2017-10-23 01:57:42", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ] + }, + { + "name": "markbaker/matrix", + "version": "1.2.3", + "version_normalized": "1.2.3.0", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPMatrix.git", + "reference": "44bb1ab01811116f01fe216ab37d921dccc6c10d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/44bb1ab01811116f01fe216ab37d921dccc6c10d", + "reference": "44bb1ab01811116f01fe216ab37d921dccc6c10d", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^5.6.0|^7.0.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-master", + "phpcompatibility/php-compatibility": "dev-master", + "phploc/phploc": "^4", + "phpmd/phpmd": "dev-master", + "phpunit/phpunit": "^5.7|^6.0|7.0", + "sebastian/phpcpd": "^3.0", + "squizlabs/php_codesniffer": "^3.0@dev" + }, + "time": "2021-01-26 14:36:01", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Matrix\\": "classes/src/" + }, + "files": [ + "classes/src/Functions/adjoint.php", + "classes/src/Functions/antidiagonal.php", + "classes/src/Functions/cofactors.php", + "classes/src/Functions/determinant.php", + "classes/src/Functions/diagonal.php", + "classes/src/Functions/identity.php", + "classes/src/Functions/inverse.php", + "classes/src/Functions/minors.php", + "classes/src/Functions/trace.php", + "classes/src/Functions/transpose.php", + "classes/src/Operations/add.php", + "classes/src/Operations/directsum.php", + "classes/src/Operations/subtract.php", + "classes/src/Operations/multiply.php", + "classes/src/Operations/divideby.php", + "classes/src/Operations/divideinto.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@lange.demon.co.uk" + } + ], + "description": "PHP Class for working with matrices", + "homepage": "https://github.com/MarkBaker/PHPMatrix", + "keywords": [ + "mathematics", + "matrix", + "vector" + ] + }, + { + "name": "markbaker/complex", + "version": "1.5.0", + "version_normalized": "1.5.0.0", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPComplex.git", + "reference": "c3131244e29c08d44fefb49e0dd35021e9e39dd2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/c3131244e29c08d44fefb49e0dd35021e9e39dd2", + "reference": "c3131244e29c08d44fefb49e0dd35021e9e39dd2", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^5.6.0|^7.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.5.0", + "phpcompatibility/php-compatibility": "^9.0", + "phpdocumentor/phpdocumentor": "2.*", + "phploc/phploc": "^4.0|^5.0|^6.0|^7.0", + "phpmd/phpmd": "2.*", + "phpunit/phpunit": "^4.8.35|^5.0|^6.0|^7.0", + "sebastian/phpcpd": "2.*", + "squizlabs/php_codesniffer": "^3.4.0" + }, + "time": "2020-08-26 19:47:57", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Complex\\": "classes/src/" + }, + "files": [ + "classes/src/functions/abs.php", + "classes/src/functions/acos.php", + "classes/src/functions/acosh.php", + "classes/src/functions/acot.php", + "classes/src/functions/acoth.php", + "classes/src/functions/acsc.php", + "classes/src/functions/acsch.php", + "classes/src/functions/argument.php", + "classes/src/functions/asec.php", + "classes/src/functions/asech.php", + "classes/src/functions/asin.php", + "classes/src/functions/asinh.php", + "classes/src/functions/atan.php", + "classes/src/functions/atanh.php", + "classes/src/functions/conjugate.php", + "classes/src/functions/cos.php", + "classes/src/functions/cosh.php", + "classes/src/functions/cot.php", + "classes/src/functions/coth.php", + "classes/src/functions/csc.php", + "classes/src/functions/csch.php", + "classes/src/functions/exp.php", + "classes/src/functions/inverse.php", + "classes/src/functions/ln.php", + "classes/src/functions/log2.php", + "classes/src/functions/log10.php", + "classes/src/functions/negative.php", + "classes/src/functions/pow.php", + "classes/src/functions/rho.php", + "classes/src/functions/sec.php", + "classes/src/functions/sech.php", + "classes/src/functions/sin.php", + "classes/src/functions/sinh.php", + "classes/src/functions/sqrt.php", + "classes/src/functions/tan.php", + "classes/src/functions/tanh.php", + "classes/src/functions/theta.php", + "classes/src/operations/add.php", + "classes/src/operations/subtract.php", + "classes/src/operations/multiply.php", + "classes/src/operations/divideby.php", + "classes/src/operations/divideinto.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@lange.demon.co.uk" + } + ], + "description": "PHP Class for working with complex numbers", + "homepage": "https://github.com/MarkBaker/PHPComplex", + "keywords": [ + "complex", + "mathematics" + ] + }, + { + "name": "phpoffice/phpspreadsheet", + "version": "1.12.0", + "version_normalized": "1.12.0.0", + "source": { + "type": "git", + "url": "https://github.com/PHPOffice/PhpSpreadsheet.git", + "reference": "f79611d6dc1f6b7e8e30b738fc371b392001dbfd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/f79611d6dc1f6b7e8e30b738fc371b392001dbfd", + "reference": "f79611d6dc1f6b7e8e30b738fc371b392001dbfd", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-ctype": "*", + "ext-dom": "*", + "ext-fileinfo": "*", + "ext-gd": "*", + "ext-iconv": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "ext-xml": "*", + "ext-xmlreader": "*", + "ext-xmlwriter": "*", + "ext-zip": "*", + "ext-zlib": "*", + "markbaker/complex": "^1.4", + "markbaker/matrix": "^1.2", + "php": "^7.1", + "psr/simple-cache": "^1.0" + }, + "require-dev": { + "dompdf/dompdf": "^0.8.3", + "friendsofphp/php-cs-fixer": "^2.16", + "jpgraph/jpgraph": "^4.0", + "mpdf/mpdf": "^8.0", + "phpcompatibility/php-compatibility": "^9.3", + "phpunit/phpunit": "^7.5", + "squizlabs/php_codesniffer": "^3.5", + "tecnickcom/tcpdf": "^6.3" + }, + "suggest": { + "dompdf/dompdf": "Option for rendering PDF with PDF Writer", + "jpgraph/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers", + "mpdf/mpdf": "Option for rendering PDF with PDF Writer", + "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer" + }, + "time": "2020-04-27 08:12:48", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Maarten Balliauw", + "homepage": "https://blog.maartenballiauw.be" + }, + { + "name": "Mark Baker", + "homepage": "https://markbakeruk.net" + }, + { + "name": "Franck Lefevre", + "homepage": "https://rootslabs.net" + }, + { + "name": "Erik Tilt" + }, + { + "name": "Adrien Crivelli" + } + ], + "description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine", + "homepage": "https://github.com/PHPOffice/PhpSpreadsheet", + "keywords": [ + "OpenXML", + "excel", + "gnumeric", + "ods", + "php", + "spreadsheet", + "xls", + "xlsx" + ] + }, + { + "name": "paragonie/random_compat", + "version": "v9.99.100", + "version_normalized": "9.99.100.0", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">= 7" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "time": "2020-10-15 08:29:30", + "type": "library", + "installation-source": "dist", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "polyfill", + "pseudorandom", + "random" + ] + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "version_normalized": "1.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2016-08-06 14:39:51", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ] + }, + { + "name": "symfony/psr-http-message-bridge", + "version": "v1.3.0", + "version_normalized": "1.3.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/psr-http-message-bridge.git", + "reference": "9d3e80d54d9ae747ad573cad796e8e247df7b796" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/9d3e80d54d9ae747ad573cad796e8e247df7b796", + "reference": "9d3e80d54d9ae747ad573cad796e8e247df7b796", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.1", + "psr/http-message": "^1.0", + "symfony/http-foundation": "^4.4 || ^5.0" + }, + "require-dev": { + "nyholm/psr7": "^1.1", + "symfony/phpunit-bridge": "^4.4 || ^5.0", + "zendframework/zend-diactoros": "^1.4.1 || ^2.0" + }, + "suggest": { + "nyholm/psr7": "For a super lightweight PSR-7/17 implementation" + }, + "time": "2019-11-25 19:33:50", + "type": "symfony-bridge", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Bridge\\PsrHttpMessage\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "PSR HTTP message bridge", + "homepage": "http://symfony.com", + "keywords": [ + "http", + "http-message", + "psr-17", + "psr-7" + ] + }, + { + "name": "psr/container", + "version": "1.0.0", + "version_normalized": "1.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2017-02-14 16:28:37", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ] + }, + { + "name": "psr/cache", + "version": "1.0.1", + "version_normalized": "1.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2016-08-06 20:24:11", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ] + }, + { + "name": "pimple/pimple", + "version": "v3.2.3", + "version_normalized": "3.2.3.0", + "source": { + "type": "git", + "url": "https://github.com/silexphp/Pimple.git", + "reference": "9e403941ef9d65d20cba7d54e29fe906db42cf32" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/silexphp/Pimple/zipball/9e403941ef9d65d20cba7d54e29fe906db42cf32", + "reference": "9e403941ef9d65d20cba7d54e29fe906db42cf32", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.3.0", + "psr/container": "^1.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^3.2" + }, + "time": "2018-01-21 07:42:36", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Pimple": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Pimple, a simple Dependency Injection Container", + "homepage": "http://pimple.sensiolabs.org", + "keywords": [ + "container", + "dependency injection" + ] + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "version_normalized": "3.0.3.0", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "time": "2019-03-08 08:55:37", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders." + }, + { + "name": "overtrue/wechat", + "version": "4.2.11", + "version_normalized": "4.2.11.0", + "source": { + "type": "git", + "url": "https://github.com/w7corp/easywechat.git", + "reference": "853e0772e6aa53a71edf1b5d251c7ff1e6b2a2bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/w7corp/easywechat/zipball/853e0772e6aa53a71edf1b5d251c7ff1e6b2a2bf", + "reference": "853e0772e6aa53a71edf1b5d251c7ff1e6b2a2bf", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "easywechat-composer/easywechat-composer": "^1.1", + "ext-fileinfo": "*", + "ext-openssl": "*", + "ext-simplexml": "*", + "guzzlehttp/guzzle": "^6.2", + "monolog/monolog": "^1.22 || ^2.0", + "overtrue/socialite": "~2.0", + "php": ">=7.1", + "pimple/pimple": "^3.0", + "psr/simple-cache": "^1.0", + "symfony/cache": "^3.3 || ^4.3", + "symfony/event-dispatcher": "^4.3", + "symfony/http-foundation": "^2.7 || ^3.0 || ^4.0", + "symfony/psr-http-message-bridge": "^0.3 || ^1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.15", + "mikey179/vfsstream": "^1.6", + "mockery/mockery": "^1.2.3", + "phpstan/phpstan": "^0.11.12", + "phpunit/phpunit": "^7.5" + }, + "time": "2019-11-27 16:38:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "EasyWeChat\\": "src/" + }, + "files": [ + "src/Kernel/Support/Helpers.php", + "src/Kernel/Helpers.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "overtrue", + "email": "anzhengchao@gmail.com" + } + ], + "description": "微信SDK", + "keywords": [ + "sdk", + "wechat", + "weixin", + "weixin-sdk" + ], + "abandoned": "w7corp/easywechat" + }, + { + "name": "topthink/think-captcha", + "version": "v1.0.7", + "version_normalized": "1.0.7.0", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-captcha.git", + "reference": "0c55455df26a1626a60d0dc35d2d89002b741d44" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-captcha/zipball/0c55455df26a1626a60d0dc35d2d89002b741d44", + "reference": "0c55455df26a1626a60d0dc35d2d89002b741d44", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "time": "2016-07-06 01:47:11", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "think\\captcha\\": "src/" + }, + "files": [ + "src/helper.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "description": "captcha package for thinkphp5" + }, + { + "name": "nelexa/zip", + "version": "3.3.3", + "version_normalized": "3.3.3.0", + "source": { + "type": "git", + "url": "https://github.com/Ne-Lexa/php-zip.git", + "reference": "501b52f6fc393a599b44ff348a42740e1eaac7c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Ne-Lexa/php-zip/zipball/501b52f6fc393a599b44ff348a42740e1eaac7c6", + "reference": "501b52f6fc393a599b44ff348a42740e1eaac7c6", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-zlib": "*", + "paragonie/random_compat": "*", + "php": "^5.5.9 || ^7.0", + "psr/http-message": "^1.0", + "symfony/finder": "^3.0|^4.0|^5.0" + }, + "require-dev": { + "ext-bz2": "*", + "ext-fileinfo": "*", + "ext-openssl": "*", + "ext-xml": "*", + "guzzlehttp/psr7": "^1.6", + "phpunit/phpunit": "^4.8|^5.7", + "symfony/var-dumper": "^3.0|^4.0|^5.0" + }, + "suggest": { + "ext-bz2": "Needed to support BZIP2 compression", + "ext-fileinfo": "Needed to get mime-type file", + "ext-mcrypt": "Needed to support encrypt zip entries or use ext-openssl", + "ext-openssl": "Needed to support encrypt zip entries or use ext-mcrypt" + }, + "time": "2020-07-11 21:01:42", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "PhpZip\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ne-Lexa", + "email": "alexey@nelexa.ru", + "role": "Developer" + } + ], + "description": "PhpZip is a php-library for extended work with ZIP-archives. Open, create, update, delete, extract and get info tool. Supports appending to existing ZIP files, WinZip AES encryption, Traditional PKWARE Encryption, ZipAlign tool, BZIP2 compression, external file attributes and ZIP64 extensions. Alternative ZipArchive. It does not require php-zip extension.", + "homepage": "https://github.com/Ne-Lexa/php-zip", + "keywords": [ + "archive", + "extract", + "unzip", + "winzip", + "zip", + "zipalign", + "ziparchive" + ] + }, + { + "name": "overtrue/pinyin", + "version": "3.0.6", + "version_normalized": "3.0.6.0", + "source": { + "type": "git", + "url": "https://github.com/overtrue/pinyin.git", + "reference": "3b781d267197b74752daa32814d3a2cf5d140779" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/overtrue/pinyin/zipball/3b781d267197b74752daa32814d3a2cf5d140779", + "reference": "3b781d267197b74752daa32814d3a2cf5d140779", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "time": "2017-07-10 07:20:01", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Overtrue\\Pinyin\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Carlos", + "homepage": "http://github.com/overtrue" + } + ], + "description": "Chinese to pinyin translator.", + "homepage": "https://github.com/overtrue/pinyin", + "keywords": [ + "Chinese", + "Pinyin", + "cn2pinyin" + ] + }, + { + "name": "txthinking/mailer", + "version": "v2.0.1", + "version_normalized": "2.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/txthinking/Mailer.git", + "reference": "09013cf9dad3aac195f66ae5309e8c3343c018e9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/txthinking/Mailer/zipball/09013cf9dad3aac195f66ae5309e8c3343c018e9", + "reference": "09013cf9dad3aac195f66ae5309e8c3343c018e9", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.3.2", + "psr/log": "~1.0" + }, + "require-dev": { + "monolog/monolog": "~1.13", + "phpunit/phpunit": "~4.0" + }, + "time": "2018-10-09 10:47:23", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Tx\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Cloud", + "email": "cloud@txthinking.com", + "homepage": "http://www.txthinking.com", + "role": "Thinker" + }, + { + "name": "Matt Sowers", + "email": "msowers@erblearn.org" + } + ], + "description": "A very lightweight PHP SMTP mail sender", + "homepage": "http://github.com/txthinking/Mailer", + "keywords": [ + "mail", + "smtp" + ] + }, + { + "name": "easywechat-composer/easywechat-composer", + "version": "1.4.1", + "version_normalized": "1.4.1.0", + "source": { + "type": "git", + "url": "https://github.com/mingyoung/easywechat-composer.git", + "reference": "3fc6a7ab6d3853c0f4e2922539b56cc37ef361cd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mingyoung/easywechat-composer/zipball/3fc6a7ab6d3853c0f4e2922539b56cc37ef361cd", + "reference": "3fc6a7ab6d3853c0f4e2922539b56cc37ef361cd", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=7.0" + }, + "require-dev": { + "composer/composer": "^1.0 || ^2.0", + "phpunit/phpunit": "^6.5 || ^7.0" + }, + "time": "2021-07-05 04:03:22", + "type": "composer-plugin", + "extra": { + "class": "EasyWeChatComposer\\Plugin" + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "EasyWeChatComposer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "张铭阳", + "email": "mingyoungcheung@gmail.com" + } + ], + "description": "The composer plugin for EasyWeChat" + }, + { + "name": "psr/log", + "version": "1.1.4", + "version_normalized": "1.1.4.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2021-05-03 11:20:27", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ] + }, + { + "name": "guzzlehttp/promises", + "version": "1.5.1", + "version_normalized": "1.5.1.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/fe752aedc9fd8fcca3fe7ad05d419d32998a06da", + "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "symfony/phpunit-bridge": "^4.4 || ^5.1" + }, + "time": "2021-10-22 20:56:57", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.5-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ] + }, + { + "name": "overtrue/socialite", + "version": "2.0.24", + "version_normalized": "2.0.24.0", + "source": { + "type": "git", + "url": "https://github.com/overtrue/socialite.git", + "reference": "ee7e7b000ec7d64f2b8aba1f6a2eec5cdf3f8bec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/overtrue/socialite/zipball/ee7e7b000ec7d64f2b8aba1f6a2eec5cdf3f8bec", + "reference": "ee7e7b000ec7d64f2b8aba1f6a2eec5cdf3f8bec", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-json": "*", + "guzzlehttp/guzzle": "^5.0|^6.0|^7.0", + "php": ">=5.6", + "symfony/http-foundation": "^2.7|^3.0|^4.0|^5.0" + }, + "require-dev": { + "mockery/mockery": "~1.2", + "phpunit/phpunit": "^6.0|^7.0|^8.0|^9.0" + }, + "time": "2021-05-13 16:04:48", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Overtrue\\Socialite\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "overtrue", + "email": "anzhengchao@gmail.com" + } + ], + "description": "A collection of OAuth 2 packages that extracts from laravel/socialite.", + "keywords": [ + "login", + "oauth", + "qq", + "social", + "wechat", + "weibo" + ] + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.25.0", + "version_normalized": "1.25.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/4407588e0d3f1f52efb65fbe92babe41f37fe50c", + "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "time": "2022-03-04 08:16:47", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ] + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "version_normalized": "1.25.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "time": "2021-11-30 18:21:41", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ] + }, + { + "name": "symfony/polyfill-php72", + "version": "v1.25.0", + "version_normalized": "1.25.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "9a142215a36a3888e30d0a9eeea9766764e96976" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/9a142215a36a3888e30d0a9eeea9766764e96976", + "reference": "9a142215a36a3888e30d0a9eeea9766764e96976", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "time": "2021-05-27 09:17:38", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php72\\": "" + } + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ] + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.25.0", + "version_normalized": "1.25.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8590a5f561694770bdcd3f9b5c69dde6945028e8", + "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "time": "2021-02-19 12:13:01", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ] + }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.25.0", + "version_normalized": "1.25.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "749045c69efb97c70d25d7463abba812e91f3a44" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/749045c69efb97c70d25d7463abba812e91f3a44", + "reference": "749045c69efb97c70d25d7463abba812e91f3a44", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "symfony/polyfill-intl-normalizer": "^1.10", + "symfony/polyfill-php72": "^1.10" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "time": "2021-09-14 14:02:44", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + } + }, + "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" + } + ], + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ] + }, + { + "name": "symfony/mime", + "version": "v4.4.42", + "version_normalized": "4.4.42.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/mime.git", + "reference": "f4f3e994024f16c1d6ca8338c62a10e0767314fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mime/zipball/f4f3e994024f16c1d6ca8338c62a10e0767314fc", + "reference": "f4f3e994024f16c1d6ca8338c62a10e0767314fc", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0", + "symfony/polyfill-php80": "^1.16" + }, + "conflict": { + "egulias/email-validator": "~3.0.0", + "symfony/mailer": "<4.4" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10|^3.1", + "symfony/dependency-injection": "^3.4|^4.1|^5.0" + }, + "time": "2022-05-21 10:10:45", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mime\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows manipulating MIME messages", + "homepage": "https://symfony.com", + "keywords": [ + "mime", + "mime-type" + ] + }, + { + "name": "guzzlehttp/psr7", + "version": "1.8.5", + "version_normalized": "1.8.5.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "337e3ad8e5716c15f9657bd214d16cc5e69df268" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/337e3ad8e5716c15f9657bd214d16cc5e69df268", + "reference": "337e3ad8e5716c15f9657bd214d16cc5e69df268", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0", + "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-zlib": "*", + "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.10" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "time": "2022-03-20 21:51:18", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ] + }, + { + "name": "guzzlehttp/guzzle", + "version": "6.5.6", + "version_normalized": "6.5.6.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "f092dd734083473658de3ee4bef093ed77d2689c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/f092dd734083473658de3ee4bef093ed77d2689c", + "reference": "f092dd734083473658de3ee4bef093ed77d2689c", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.0", + "guzzlehttp/psr7": "^1.6.1", + "php": ">=5.5", + "symfony/polyfill-intl-idn": "^1.17.0" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0", + "psr/log": "^1.1" + }, + "suggest": { + "psr/log": "Required for using the Log middleware" + }, + "time": "2022-05-25 13:19:12", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.5-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ] + }, + { + "name": "monolog/monolog", + "version": "1.27.0", + "version_normalized": "1.27.0.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "52ebd235c1f7e0d5e1b16464b695a28335f8e44a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/52ebd235c1f7e0d5e1b16464b695a28335f8e44a", + "reference": "52ebd235c1f7e0d5e1b16464b695a28335f8e44a", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "psr/log": "~1.0" + }, + "provide": { + "psr/log-implementation": "1.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "doctrine/couchdb": "~1.0@dev", + "graylog2/gelf-php": "~1.0", + "php-amqplib/php-amqplib": "~2.4", + "php-console/php-console": "^3.1.3", + "phpstan/phpstan": "^0.12.59", + "phpunit/phpunit": "~4.5", + "ruflin/elastica": ">=0.90 <3.0", + "sentry/sentry": "^0.13", + "swiftmailer/swiftmailer": "^5.3|^6.0" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-mongo": "Allow sending log messages to a MongoDB server", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "php-console/php-console": "Allow sending log messages to Google Chrome", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server", + "sentry/sentry": "Allow sending log messages to a Sentry server" + }, + "time": "2022-03-13 20:29:46", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "http://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ] + }, + { + "name": "symfony/var-exporter", + "version": "v4.4.41", + "version_normalized": "4.4.41.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "bc5f57ae61b5e492b3f6f21be6e503dcc7b898b7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/bc5f57ae61b5e492b3f6f21be6e503dcc7b898b7", + "reference": "bc5f57ae61b5e492b3f6f21be6e503dcc7b898b7", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "symfony/var-dumper": "^4.4.9|^5.0.9" + }, + "time": "2022-04-25 17:40:48", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows exporting any serializable PHP data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "serialize" + ] + }, + { + "name": "symfony/service-contracts", + "version": "v1.1.12", + "version_normalized": "1.1.12.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "eedb374f02031714a48848758a27812f3eca317a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/eedb374f02031714a48848758a27812f3eca317a", + "reference": "eedb374f02031714a48848758a27812f3eca317a", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "psr/container": "^1.0" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "time": "2022-03-09 13:39:03", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.1-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ] + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.25.0", + "version_normalized": "1.25.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "cc5db0e22b3cb4111010e48785a97f670b350ca5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/cc5db0e22b3cb4111010e48785a97f670b350ca5", + "reference": "cc5db0e22b3cb4111010e48785a97f670b350ca5", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "time": "2021-06-05 21:20:04", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ] + }, + { + "name": "symfony/cache-contracts", + "version": "v1.1.12", + "version_normalized": "1.1.12.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache-contracts.git", + "reference": "a872a66e0bf7bac179c89bc96c7098bef1949f81" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/a872a66e0bf7bac179c89bc96c7098bef1949f81", + "reference": "a872a66e0bf7bac179c89bc96c7098bef1949f81", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "psr/cache": "^1.0|^2.0|^3.0" + }, + "suggest": { + "symfony/cache-implementation": "" + }, + "time": "2022-01-02 09:41:36", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.1-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Cache\\": "" + } + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to caching", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ] + }, + { + "name": "symfony/cache", + "version": "v4.4.41", + "version_normalized": "4.4.41.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache.git", + "reference": "27121284fe32a7cefc225268761ec7ce1741b9ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache/zipball/27121284fe32a7cefc225268761ec7ce1741b9ac", + "reference": "27121284fe32a7cefc225268761ec7ce1741b9ac", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "psr/cache": "^1.0|^2.0", + "psr/log": "^1|^2|^3", + "symfony/cache-contracts": "^1.1.7|^2", + "symfony/polyfill-php73": "^1.9", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.1|^2", + "symfony/var-exporter": "^4.2|^5.0" + }, + "conflict": { + "doctrine/dbal": "<2.7", + "symfony/dependency-injection": "<3.4", + "symfony/http-kernel": "<4.4|>=5.0", + "symfony/var-dumper": "<4.4" + }, + "provide": { + "psr/cache-implementation": "1.0|2.0", + "psr/simple-cache-implementation": "1.0|2.0", + "symfony/cache-implementation": "1.0|2.0" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "doctrine/cache": "^1.6|^2.0", + "doctrine/dbal": "^2.7|^3.0", + "predis/predis": "^1.1", + "psr/simple-cache": "^1.0|^2.0", + "symfony/config": "^4.2|^5.0", + "symfony/dependency-injection": "^3.4|^4.1|^5.0", + "symfony/filesystem": "^4.4|^5.0", + "symfony/http-kernel": "^4.4", + "symfony/var-dumper": "^4.4|^5.0" + }, + "time": "2022-04-25 17:25:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Cache\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an extended PSR-6, PSR-16 (and tags) implementation", + "homepage": "https://symfony.com", + "keywords": [ + "caching", + "psr6" + ] + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v1.1.12", + "version_normalized": "1.1.12.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "1d5cd762abaa6b2a4169d3e77610193a7157129e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/1d5cd762abaa6b2a4169d3e77610193a7157129e", + "reference": "1d5cd762abaa6b2a4169d3e77610193a7157129e", + "shasum": "" + }, + "require": { + "php": ">=7.1.3" + }, + "suggest": { + "psr/event-dispatcher": "", + "symfony/event-dispatcher-implementation": "" + }, + "time": "2022-01-02 09:41:36", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.1-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ] + }, + { + "name": "symfony/event-dispatcher", + "version": "v4.4.37", + "version_normalized": "4.4.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "3ccfcfb96ecce1217d7b0875a0736976bc6e63dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/3ccfcfb96ecce1217d7b0875a0736976bc6e63dc", + "reference": "3ccfcfb96ecce1217d7b0875a0736976bc6e63dc", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/event-dispatcher-contracts": "^1.1", + "symfony/polyfill-php80": "^1.16" + }, + "conflict": { + "symfony/dependency-injection": "<3.4" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "1.1" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^3.4|^4.0|^5.0", + "symfony/dependency-injection": "^3.4|^4.0|^5.0", + "symfony/error-handler": "~3.4|~4.4", + "symfony/expression-language": "^3.4|^4.0|^5.0", + "symfony/http-foundation": "^3.4|^4.0|^5.0", + "symfony/service-contracts": "^1.1|^2", + "symfony/stopwatch": "^3.4|^4.0|^5.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "time": "2022-01-02 09:41:36", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com" + }, + { + "name": "symfony/finder", + "version": "v4.4.41", + "version_normalized": "4.4.41.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "40790bdf293b462798882ef6da72bb49a4a6633a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/40790bdf293b462798882ef6da72bb49a4a6633a", + "reference": "40790bdf293b462798882ef6da72bb49a4a6633a", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/polyfill-php80": "^1.16" + }, + "time": "2022-04-14 15:36:10", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com" + }, + { + "name": "topthink/framework", + "version": "dev-master", + "version_normalized": "9999999-dev", + "source": { + "type": "git", + "url": "https://gitee.com/fastadminnet/framework.git", + "reference": "0999d4733451e6c219c3e99090fa2cccb19115a6" + }, + "require": { + "php": ">=7.1.0", + "topthink/think-installer": "~1.0" + }, + "require-dev": { + "johnkary/phpunit-speedtrap": "^1.0", + "mikey179/vfsstream": "~1.6", + "phpdocumentor/reflection-docblock": "^2.0", + "phploc/phploc": "2.*", + "phpunit/phpunit": "4.8.*", + "sebastian/phpcpd": "2.*" + }, + "time": "2022-05-07 15:02:42", + "type": "think-framework", + "installation-source": "source", + "autoload": { + "psr-4": { + "think\\": "library/think" + } + }, + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "the new thinkphp framework", + "homepage": "http://thinkphp.cn/", + "keywords": [ + "ORM", + "framework", + "thinkphp" + ] + }, + { + "name": "symfony/http-foundation", + "version": "v4.4.42", + "version_normalized": "4.4.42.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "8e87b3ec23ebbcf7440d91dec8f7ca70dd591eb3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/8e87b3ec23ebbcf7440d91dec8f7ca70dd591eb3", + "reference": "8e87b3ec23ebbcf7440d91dec8f7ca70dd591eb3", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/mime": "^4.3|^5.0", + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "predis/predis": "~1.0", + "symfony/expression-language": "^3.4|^4.0|^5.0" + }, + "time": "2022-05-17 11:15:18", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Defines an object-oriented layer for the HTTP specification", + "homepage": "https://symfony.com" + }, + { + "name": "karsonzhang/fastadmin-addons", + "version": "1.3.3", + "version_normalized": "1.3.3.0", + "source": { + "type": "git", + "url": "https://github.com/karsonzhang/fastadmin-addons.git", + "reference": "f5bb4dabda55271b08371a8ae9355a205be48c56" + }, + "dist": { + "type": "zip", + "url": "https://mirrors.huaweicloud.com/repository/php/karsonzhang/fastadmin-addons/1.3.3/karsonzhang-fastadmin-addons-1.3.3.zip", + "reference": "f5bb4dabda55271b08371a8ae9355a205be48c56", + "shasum": "" + }, + "require": { + "nelexa/zip": "^3.3", + "php": ">=7.0.0", + "symfony/var-exporter": "^4.4.13" + }, + "time": "2022-05-27 09:30:12", + "type": "library", + "extra": { + "think-config": { + "addons": "src/config.php" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "src/common.php" + ], + "psr-4": { + "think\\": "src/" + } + }, + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Karson", + "email": "karson@fastadmin.net" + }, + { + "name": "xiaobo.sun", + "email": "xiaobo.sun@qq.com" + } + ], + "description": "addons package for fastadmin", + "homepage": "https://github.com/karsonzhang/fastadmin-addons" + } +] diff --git a/vendor/easywechat-composer/easywechat-composer/.gitignore b/vendor/easywechat-composer/easywechat-composer/.gitignore new file mode 100644 index 0000000..c7a0a65 --- /dev/null +++ b/vendor/easywechat-composer/easywechat-composer/.gitignore @@ -0,0 +1,5 @@ +.idea/ +/vendor +composer.lock +extensions.php +.php_cs.cache diff --git a/vendor/easywechat-composer/easywechat-composer/.php_cs b/vendor/easywechat-composer/easywechat-composer/.php_cs new file mode 100644 index 0000000..d256932 --- /dev/null +++ b/vendor/easywechat-composer/easywechat-composer/.php_cs @@ -0,0 +1,29 @@ + + +This source file is subject to the MIT license that is bundled +with this source code in the file LICENSE. +EOF; + +return PhpCsFixer\Config::create() + ->setRiskyAllowed(true) + ->setRules([ + '@Symfony' => true, + 'header_comment' => ['header' => $header], + 'declare_strict_types' => true, + 'ordered_imports' => true, + 'strict_comparison' => true, + 'no_empty_comment' => false, + 'yoda_style' => false, + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->exclude('vendor') + ->notPath('src/Laravel/config.php', 'src/Laravel/routes.php') + ->in(__DIR__) + ) +; diff --git a/vendor/easywechat-composer/easywechat-composer/.travis.yml b/vendor/easywechat-composer/easywechat-composer/.travis.yml new file mode 100644 index 0000000..e819807 --- /dev/null +++ b/vendor/easywechat-composer/easywechat-composer/.travis.yml @@ -0,0 +1,12 @@ +language: php + +php: + - 7.0 + - 7.1 + - 7.2 + - 7.3 + +install: + - travis_retry composer install --no-interaction --no-suggest + +script: ./vendor/bin/phpunit diff --git a/vendor/easywechat-composer/easywechat-composer/LICENSE b/vendor/easywechat-composer/easywechat-composer/LICENSE new file mode 100644 index 0000000..3559904 --- /dev/null +++ b/vendor/easywechat-composer/easywechat-composer/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 张铭阳 + +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/easywechat-composer/easywechat-composer/README.md b/vendor/easywechat-composer/easywechat-composer/README.md new file mode 100644 index 0000000..a08c1be --- /dev/null +++ b/vendor/easywechat-composer/easywechat-composer/README.md @@ -0,0 +1,55 @@ +

    +

    EasyWeChat Composer Plugin

    +

    + +

    + Build Status + Scrutinizer Code Quality + Latest Stable Version + Total Downloads + License +

    + +Usage +--- + +Set the `type` to be `easywechat-extension` in your package composer.json file: + +```json +{ + "name": "your/package", + "type": "easywechat-extension" +} +``` + +Specify server observer classes in the extra section: + +```json +{ + "name": "your/package", + "type": "easywechat-extension", + "extra": { + "observers": [ + "Acme\\Observers\\Handler" + ] + } +} +``` + +Examples +--- +* [easywechat-composer/open-platform-testcase](https://github.com/mingyoung/open-platform-testcase) + +Server Delegation +--- + +> 目前仅支持 Laravel + +1. 在 `config/app.php` 中添加 `EasyWeChatComposer\Laravel\ServiceProvider::class` + +2. 在**本地项目**的 `.env` 文件中添加如下配置: + +``` +EASYWECHAT_DELEGATION=true # false 则不启用 +EASYWECHAT_DELEGATION_HOST=https://example.com # 线上域名 +``` diff --git a/vendor/easywechat-composer/easywechat-composer/composer.json b/vendor/easywechat-composer/easywechat-composer/composer.json new file mode 100644 index 0000000..32d7c94 --- /dev/null +++ b/vendor/easywechat-composer/easywechat-composer/composer.json @@ -0,0 +1,35 @@ +{ + "name": "easywechat-composer/easywechat-composer", + "description": "The composer plugin for EasyWeChat", + "type": "composer-plugin", + "license": "MIT", + "authors": [ + { + "name": "张铭阳", + "email": "mingyoungcheung@gmail.com" + } + ], + "require": { + "php": ">=7.0", + "composer-plugin-api": "^1.0 || ^2.0" + }, + "require-dev": { + "composer/composer": "^1.0 || ^2.0", + "phpunit/phpunit": "^6.5 || ^7.0" + }, + "autoload": { + "psr-4": { + "EasyWeChatComposer\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "EasyWeChatComposer\\Tests\\": "tests/" + } + }, + "extra": { + "class": "EasyWeChatComposer\\Plugin" + }, + "minimum-stability": "dev", + "prefer-stable": true +} diff --git a/vendor/easywechat-composer/easywechat-composer/extensions.php b/vendor/easywechat-composer/easywechat-composer/extensions.php new file mode 100644 index 0000000..a359f03 --- /dev/null +++ b/vendor/easywechat-composer/easywechat-composer/extensions.php @@ -0,0 +1,2 @@ + + + + tests + + + + + src + + + diff --git a/vendor/easywechat-composer/easywechat-composer/src/Commands/ExtensionsCommand.php b/vendor/easywechat-composer/easywechat-composer/src/Commands/ExtensionsCommand.php new file mode 100644 index 0000000..bc0155e --- /dev/null +++ b/vendor/easywechat-composer/easywechat-composer/src/Commands/ExtensionsCommand.php @@ -0,0 +1,63 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChatComposer\Commands; + +use Composer\Command\BaseCommand; +use Symfony\Component\Console\Helper\Table; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class ExtensionsCommand extends BaseCommand +{ + /** + * Configures the current command. + */ + protected function configure() + { + $this->setName('easywechat:extensions') + ->setDescription('Lists all installed extensions.'); + } + + /** + * Executes the current command. + * + * @param InputInterface $input + * @param OutputInterface $output + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $extensions = require __DIR__.'/../../extensions.php'; + + if (empty($extensions) || !is_array($extensions)) { + return $output->writeln('No extension installed.'); + } + + $table = new Table($output); + $table->setHeaders(['Name', 'Observers']) + ->setRows( + array_map([$this, 'getRows'], array_keys($extensions), $extensions) + )->render(); + } + + /** + * @param string $name + * @param array $extension + * + * @return array + */ + protected function getRows($name, $extension) + { + return [$name, implode("\n", $extension['observers'] ?? [])]; + } +} diff --git a/vendor/easywechat-composer/easywechat-composer/src/Commands/Provider.php b/vendor/easywechat-composer/easywechat-composer/src/Commands/Provider.php new file mode 100644 index 0000000..928f096 --- /dev/null +++ b/vendor/easywechat-composer/easywechat-composer/src/Commands/Provider.php @@ -0,0 +1,31 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChatComposer\Commands; + +use Composer\Plugin\Capability\CommandProvider; + +class Provider implements CommandProvider +{ + /** + * Retrieves an array of commands. + * + * @return \Composer\Command\BaseCommand[] + */ + public function getCommands() + { + return [ + new ExtensionsCommand(), + ]; + } +} diff --git a/vendor/easywechat-composer/easywechat-composer/src/Contracts/Encrypter.php b/vendor/easywechat-composer/easywechat-composer/src/Contracts/Encrypter.php new file mode 100644 index 0000000..af8b8d1 --- /dev/null +++ b/vendor/easywechat-composer/easywechat-composer/src/Contracts/Encrypter.php @@ -0,0 +1,35 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChatComposer\Contracts; + +interface Encrypter +{ + /** + * Encrypt the given value. + * + * @param string $value + * + * @return string + */ + public function encrypt($value); + + /** + * Decrypt the given value. + * + * @param string $payload + * + * @return string + */ + public function decrypt($payload); +} diff --git a/vendor/easywechat-composer/easywechat-composer/src/Delegation/DelegationOptions.php b/vendor/easywechat-composer/easywechat-composer/src/Delegation/DelegationOptions.php new file mode 100644 index 0000000..a333261 --- /dev/null +++ b/vendor/easywechat-composer/easywechat-composer/src/Delegation/DelegationOptions.php @@ -0,0 +1,80 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChatComposer\Delegation; + +use EasyWeChatComposer\EasyWeChat; + +class DelegationOptions +{ + /** + * @var array + */ + protected $config = [ + 'enabled' => false, + ]; + + /** + * @return $this + */ + public function enable() + { + $this->config['enabled'] = true; + + return $this; + } + + /** + * @return $this + */ + public function disable() + { + $this->config['enabled'] = false; + + return $this; + } + + /** + * @param bool $ability + * + * @return $this + */ + public function ability($ability) + { + $this->config['enabled'] = (bool) $ability; + + return $this; + } + + /** + * @param string $host + * + * @return $this + */ + public function toHost($host) + { + $this->config['host'] = $host; + + return $this; + } + + /** + * Destructor. + */ + public function __destruct() + { + EasyWeChat::mergeConfig([ + 'delegation' => $this->config, + ]); + } +} diff --git a/vendor/easywechat-composer/easywechat-composer/src/Delegation/DelegationTo.php b/vendor/easywechat-composer/easywechat-composer/src/Delegation/DelegationTo.php new file mode 100644 index 0000000..2e9e6db --- /dev/null +++ b/vendor/easywechat-composer/easywechat-composer/src/Delegation/DelegationTo.php @@ -0,0 +1,83 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChatComposer\Delegation; + +use EasyWeChatComposer\Traits\MakesHttpRequests; + +class DelegationTo +{ + use MakesHttpRequests; + + /** + * @var \EasyWeChat\Kernel\ServiceContainer + */ + protected $app; + + /** + * @var array + */ + protected $identifiers = []; + + /** + * @param \EasyWeChat\Kernel\ServiceContainer $app + * @param string $identifier + */ + public function __construct($app, $identifier) + { + $this->app = $app; + + $this->push($identifier); + } + + /** + * @param string $identifier + */ + public function push($identifier) + { + $this->identifiers[] = $identifier; + } + + /** + * @param string $identifier + * + * @return $this + */ + public function __get($identifier) + { + $this->push($identifier); + + return $this; + } + + /** + * @param string $method + * @param array $arguments + * + * @return mixed + */ + public function __call($method, $arguments) + { + $config = array_intersect_key($this->app->getConfig(), array_flip(['app_id', 'secret', 'token', 'aes_key', 'response_type', 'component_app_id', 'refresh_token'])); + + $data = [ + 'config' => $config, + 'application' => get_class($this->app), + 'identifiers' => $this->identifiers, + 'method' => $method, + 'arguments' => $arguments, + ]; + + return $this->request('easywechat-composer/delegate', $data); + } +} diff --git a/vendor/easywechat-composer/easywechat-composer/src/Delegation/Hydrate.php b/vendor/easywechat-composer/easywechat-composer/src/Delegation/Hydrate.php new file mode 100644 index 0000000..b83bbe9 --- /dev/null +++ b/vendor/easywechat-composer/easywechat-composer/src/Delegation/Hydrate.php @@ -0,0 +1,83 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChatComposer\Delegation; + +use EasyWeChat; +use EasyWeChatComposer\Http\DelegationResponse; + +class Hydrate +{ + /** + * @var array + */ + protected $attributes; + + /** + * @param array $attributes + */ + public function __construct(array $attributes) + { + $this->attributes = $attributes; + } + + /** + * @return array + */ + public function handle() + { + $app = $this->createsApplication()->shouldntDelegate(); + + foreach ($this->attributes['identifiers'] as $identifier) { + $app = $app->$identifier; + } + + return call_user_func_array([$app, $this->attributes['method']], $this->attributes['arguments']); + } + + /** + * @return \EasyWeChat\Kernel\ServiceContainer + */ + protected function createsApplication() + { + $application = $this->attributes['application']; + + if ($application === EasyWeChat\OpenPlatform\Authorizer\OfficialAccount\Application::class) { + return $this->createsOpenPlatformApplication('officialAccount'); + } + + if ($application === EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Application::class) { + return $this->createsOpenPlatformApplication('miniProgram'); + } + + return new $application($this->buildConfig($this->attributes['config'])); + } + + protected function createsOpenPlatformApplication($type) + { + $config = $this->attributes['config']; + + $authorizerAppId = $config['app_id']; + + $config['app_id'] = $config['component_app_id']; + + return EasyWeChat\Factory::openPlatform($this->buildConfig($config))->$type($authorizerAppId, $config['refresh_token']); + } + + protected function buildConfig(array $config) + { + $config['response_type'] = DelegationResponse::class; + + return $config; + } +} diff --git a/vendor/easywechat-composer/easywechat-composer/src/EasyWeChat.php b/vendor/easywechat-composer/easywechat-composer/src/EasyWeChat.php new file mode 100644 index 0000000..4ff3d9b --- /dev/null +++ b/vendor/easywechat-composer/easywechat-composer/src/EasyWeChat.php @@ -0,0 +1,79 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChatComposer; + +use EasyWeChatComposer\Delegation\DelegationOptions; + +class EasyWeChat +{ + /** + * @var array + */ + protected static $config = []; + + /** + * Encryption key. + * + * @var string + */ + protected static $encryptionKey; + + /** + * @param array $config + */ + public static function mergeConfig(array $config) + { + static::$config = array_merge(static::$config, $config); + } + + /** + * @return array + */ + public static function config() + { + return static::$config; + } + + /** + * Set encryption key. + * + * @param string $key + * + * @return static + */ + public static function setEncryptionKey(string $key) + { + static::$encryptionKey = $key; + + return new static(); + } + + /** + * Get encryption key. + * + * @return string + */ + public static function getEncryptionKey(): string + { + return static::$encryptionKey; + } + + /** + * @return \EasyWeChatComposer\Delegation\DelegationOptions + */ + public static function withDelegation() + { + return new DelegationOptions(); + } +} diff --git a/vendor/easywechat-composer/easywechat-composer/src/Encryption/DefaultEncrypter.php b/vendor/easywechat-composer/easywechat-composer/src/Encryption/DefaultEncrypter.php new file mode 100644 index 0000000..2c4cd53 --- /dev/null +++ b/vendor/easywechat-composer/easywechat-composer/src/Encryption/DefaultEncrypter.php @@ -0,0 +1,89 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChatComposer\Encryption; + +use EasyWeChatComposer\Contracts\Encrypter; +use EasyWeChatComposer\Exceptions\DecryptException; +use EasyWeChatComposer\Exceptions\EncryptException; + +class DefaultEncrypter implements Encrypter +{ + /** + * @var string + */ + protected $key; + + /** + * @var string + */ + protected $cipher; + + /** + * @param string $key + * @param string $cipher + */ + public function __construct($key, $cipher = 'AES-256-CBC') + { + $this->key = $key; + $this->cipher = $cipher; + } + + /** + * Encrypt the given value. + * + * @param string $value + * + * @return string + * + * @throws \EasyWeChatComposer\Exceptions\EncryptException + */ + public function encrypt($value) + { + $iv = random_bytes(openssl_cipher_iv_length($this->cipher)); + + $value = openssl_encrypt($value, $this->cipher, $this->key, 0, $iv); + + if ($value === false) { + throw new EncryptException('Could not encrypt the data.'); + } + + $iv = base64_encode($iv); + + return base64_encode(json_encode(compact('iv', 'value'))); + } + + /** + * Decrypt the given value. + * + * @param string $payload + * + * @return string + * + * @throws \EasyWeChatComposer\Exceptions\DecryptException + */ + public function decrypt($payload) + { + $payload = json_decode(base64_decode($payload), true); + + $iv = base64_decode($payload['iv']); + + $decrypted = openssl_decrypt($payload['value'], $this->cipher, $this->key, 0, $iv); + + if ($decrypted === false) { + throw new DecryptException('Could not decrypt the data.'); + } + + return $decrypted; + } +} diff --git a/vendor/easywechat-composer/easywechat-composer/src/Exceptions/DecryptException.php b/vendor/easywechat-composer/easywechat-composer/src/Exceptions/DecryptException.php new file mode 100644 index 0000000..e210d1f --- /dev/null +++ b/vendor/easywechat-composer/easywechat-composer/src/Exceptions/DecryptException.php @@ -0,0 +1,21 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChatComposer\Exceptions; + +use Exception; + +class DecryptException extends Exception +{ + // +} diff --git a/vendor/easywechat-composer/easywechat-composer/src/Exceptions/DelegationException.php b/vendor/easywechat-composer/easywechat-composer/src/Exceptions/DelegationException.php new file mode 100644 index 0000000..0af9c2d --- /dev/null +++ b/vendor/easywechat-composer/easywechat-composer/src/Exceptions/DelegationException.php @@ -0,0 +1,42 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChatComposer\Exceptions; + +use Exception; + +class DelegationException extends Exception +{ + /** + * @var string + */ + protected $exception; + + /** + * @param string $exception + */ + public function setException($exception) + { + $this->exception = $exception; + + return $this; + } + + /** + * @return string + */ + public function getException() + { + return $this->exception; + } +} diff --git a/vendor/easywechat-composer/easywechat-composer/src/Exceptions/EncryptException.php b/vendor/easywechat-composer/easywechat-composer/src/Exceptions/EncryptException.php new file mode 100644 index 0000000..f88f8f4 --- /dev/null +++ b/vendor/easywechat-composer/easywechat-composer/src/Exceptions/EncryptException.php @@ -0,0 +1,21 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChatComposer\Exceptions; + +use Exception; + +class EncryptException extends Exception +{ + // +} diff --git a/vendor/easywechat-composer/easywechat-composer/src/Extension.php b/vendor/easywechat-composer/easywechat-composer/src/Extension.php new file mode 100644 index 0000000..6a521d1 --- /dev/null +++ b/vendor/easywechat-composer/easywechat-composer/src/Extension.php @@ -0,0 +1,143 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChatComposer; + +use EasyWeChat\Kernel\Contracts\EventHandlerInterface; +use Pimple\Container; +use ReflectionClass; + +class Extension +{ + /** + * @var \Pimple\Container + */ + protected $app; + + /** + * @var string + */ + protected $manifestPath; + + /** + * @var array|null + */ + protected $manifest; + + /** + * @param \Pimple\Container $app + */ + public function __construct(Container $app) + { + $this->app = $app; + $this->manifestPath = __DIR__.'/../extensions.php'; + } + + /** + * Get observers. + * + * @return array + */ + public function observers(): array + { + if ($this->shouldIgnore()) { + return []; + } + + $observers = []; + + foreach ($this->getManifest() as $name => $extra) { + $observers = array_merge($observers, $extra['observers'] ?? []); + } + + return array_map([$this, 'listObserver'], array_filter($observers, [$this, 'validateObserver'])); + } + + /** + * @param mixed $observer + * + * @return bool + */ + protected function isDisable($observer): bool + { + return in_array($observer, $this->app->config->get('disable_observers', [])); + } + + /** + * Get the observers should be ignore. + * + * @return bool + */ + protected function shouldIgnore(): bool + { + return !file_exists($this->manifestPath) || $this->isDisable('*'); + } + + /** + * Validate the given observer. + * + * @param mixed $observer + * + * @return bool + * + * @throws \ReflectionException + */ + protected function validateObserver($observer): bool + { + return !$this->isDisable($observer) + && (new ReflectionClass($observer))->implementsInterface(EventHandlerInterface::class) + && $this->accessible($observer); + } + + /** + * Determine whether the given observer is accessible. + * + * @param string $observer + * + * @return bool + */ + protected function accessible($observer): bool + { + if (!method_exists($observer, 'getAccessor')) { + return true; + } + + return in_array(get_class($this->app), (array) $observer::getAccessor()); + } + + /** + * @param mixed $observer + * + * @return array + */ + protected function listObserver($observer): array + { + $condition = method_exists($observer, 'onCondition') ? $observer::onCondition() : '*'; + + return [$observer, $condition]; + } + + /** + * Get the easywechat manifest. + * + * @return array + */ + protected function getManifest(): array + { + if (!is_null($this->manifest)) { + return $this->manifest; + } + + return $this->manifest = file_exists($this->manifestPath) ? require $this->manifestPath : []; + } +} diff --git a/vendor/easywechat-composer/easywechat-composer/src/Http/DelegationResponse.php b/vendor/easywechat-composer/easywechat-composer/src/Http/DelegationResponse.php new file mode 100644 index 0000000..329eb54 --- /dev/null +++ b/vendor/easywechat-composer/easywechat-composer/src/Http/DelegationResponse.php @@ -0,0 +1,25 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChatComposer\Http; + +class DelegationResponse extends Response +{ + /** + * @return string + */ + public function getBodyContents() + { + return $this->response->getBodyContents(); + } +} diff --git a/vendor/easywechat-composer/easywechat-composer/src/Http/Response.php b/vendor/easywechat-composer/easywechat-composer/src/Http/Response.php new file mode 100644 index 0000000..534bf54 --- /dev/null +++ b/vendor/easywechat-composer/easywechat-composer/src/Http/Response.php @@ -0,0 +1,104 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChatComposer\Http; + +use EasyWeChat\Kernel\Contracts\Arrayable; +use EasyWeChat\Kernel\Http\Response as HttpResponse; +use JsonSerializable; + +class Response implements Arrayable, JsonSerializable +{ + /** + * @var \EasyWeChat\Kernel\Http\Response + */ + protected $response; + + /** + * @var array + */ + protected $array; + + /** + * @param \EasyWeChat\Kernel\Http\Response $response + */ + public function __construct(HttpResponse $response) + { + $this->response = $response; + } + + /** + * @see \ArrayAccess::offsetExists + * + * @param string $offset + * + * @return bool + */ + public function offsetExists($offset) + { + return isset($this->toArray()[$offset]); + } + + /** + * @see \ArrayAccess::offsetGet + * + * @param string $offset + * + * @return mixed + */ + public function offsetGet($offset) + { + return $this->toArray()[$offset] ?? null; + } + + /** + * @see \ArrayAccess::offsetSet + * + * @param string $offset + * @param mixed $value + */ + public function offsetSet($offset, $value) + { + // + } + + /** + * @see \ArrayAccess::offsetUnset + * + * @param string $offset + */ + public function offsetUnset($offset) + { + // + } + + /** + * Get the instance as an array. + * + * @return array + */ + public function toArray() + { + return $this->array ?: $this->array = $this->response->toArray(); + } + + /** + * Convert the object into something JSON serializable. + * + * @return array + */ + public function jsonSerialize() + { + return $this->toArray(); + } +} diff --git a/vendor/easywechat-composer/easywechat-composer/src/Laravel/Http/Controllers/DelegatesController.php b/vendor/easywechat-composer/easywechat-composer/src/Laravel/Http/Controllers/DelegatesController.php new file mode 100644 index 0000000..ad014d8 --- /dev/null +++ b/vendor/easywechat-composer/easywechat-composer/src/Laravel/Http/Controllers/DelegatesController.php @@ -0,0 +1,49 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChatComposer\Laravel\Http\Controllers; + +use EasyWeChatComposer\Delegation\Hydrate; +use EasyWeChatComposer\Encryption\DefaultEncrypter; +use Illuminate\Http\Request; +use Throwable; + +class DelegatesController +{ + /** + * @param \Illuminate\Http\Request $request + * @param \EasyWeChatComposer\Encryption\DefaultEncrypter $encrypter + * + * @return \Illuminate\Http\Response + */ + public function __invoke(Request $request, DefaultEncrypter $encrypter) + { + try { + $data = json_decode($encrypter->decrypt($request->get('encrypted')), true); + + $hydrate = new Hydrate($data); + + $response = $hydrate->handle(); + + return response()->json([ + 'response_type' => get_class($response), + 'response' => $encrypter->encrypt($response->getBodyContents()), + ]); + } catch (Throwable $t) { + return [ + 'exception' => get_class($t), + 'message' => $t->getMessage(), + ]; + } + } +} diff --git a/vendor/easywechat-composer/easywechat-composer/src/Laravel/ServiceProvider.php b/vendor/easywechat-composer/easywechat-composer/src/Laravel/ServiceProvider.php new file mode 100644 index 0000000..4c43b04 --- /dev/null +++ b/vendor/easywechat-composer/easywechat-composer/src/Laravel/ServiceProvider.php @@ -0,0 +1,116 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChatComposer\Laravel; + +use EasyWeChatComposer\EasyWeChat; +use EasyWeChatComposer\Encryption\DefaultEncrypter; +use Illuminate\Foundation\Application; +use Illuminate\Support\Arr; +use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Facades\Route; +use Illuminate\Support\ServiceProvider as LaravelServiceProvider; +use RuntimeException; + +class ServiceProvider extends LaravelServiceProvider +{ + /** + * Bootstrap any application services. + */ + public function boot() + { + $this->registerRoutes(); + $this->publishes([ + __DIR__.'/config.php' => config_path('easywechat-composer.php'), + ]); + + EasyWeChat::setEncryptionKey( + $defaultKey = $this->getKey() + ); + + EasyWeChat::withDelegation() + ->toHost($this->config('delegation.host')) + ->ability($this->config('delegation.enabled')); + + $this->app->when(DefaultEncrypter::class)->needs('$key')->give($defaultKey); + } + + /** + * Register routes. + */ + protected function registerRoutes() + { + Route::prefix('easywechat-composer')->namespace('EasyWeChatComposer\Laravel\Http\Controllers')->group(function () { + $this->loadRoutesFrom(__DIR__.'/routes.php'); + }); + } + + /** + * Register any application services. + */ + public function register() + { + $this->configure(); + } + + /** + * Register config. + */ + protected function configure() + { + $this->mergeConfigFrom( + __DIR__.'/config.php', 'easywechat-composer' + ); + } + + /** + * Get the specified configuration value. + * + * @param string|null $key + * @param mixed $default + * + * @return mixed + */ + protected function config($key = null, $default = null) + { + $config = $this->app['config']->get('easywechat-composer'); + + if (is_null($key)) { + return $config; + } + + return Arr::get($config, $key, $default); + } + + /** + * @return string + */ + protected function getKey() + { + return $this->config('encryption.key') ?: $this->getMd5Key(); + } + + /** + * @return string + */ + protected function getMd5Key() + { + $ttl = (version_compare(Application::VERSION, '5.8') === -1) ? 30 : 1800; + + return Cache::remember('easywechat-composer.encryption_key', $ttl, function () { + throw_unless(file_exists($path = base_path('composer.lock')), RuntimeException::class, 'No encryption key provided.'); + + return md5_file($path); + }); + } +} diff --git a/vendor/easywechat-composer/easywechat-composer/src/Laravel/config.php b/vendor/easywechat-composer/easywechat-composer/src/Laravel/config.php new file mode 100644 index 0000000..5deabef --- /dev/null +++ b/vendor/easywechat-composer/easywechat-composer/src/Laravel/config.php @@ -0,0 +1,29 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +return [ + + 'encryption' => [ + + 'key' => env('EASYWECHAT_KEY'), + + ], + + 'delegation' => [ + + 'enabled' => env('EASYWECHAT_DELEGATION', false), + + 'host' => env('EASYWECHAT_DELEGATION_HOST'), + ], + +]; diff --git a/vendor/easywechat-composer/easywechat-composer/src/Laravel/routes.php b/vendor/easywechat-composer/easywechat-composer/src/Laravel/routes.php new file mode 100644 index 0000000..6a3041a --- /dev/null +++ b/vendor/easywechat-composer/easywechat-composer/src/Laravel/routes.php @@ -0,0 +1,16 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +use Illuminate\Support\Facades\Route; + +Route::post('delegate', 'DelegatesController'); diff --git a/vendor/easywechat-composer/easywechat-composer/src/ManifestManager.php b/vendor/easywechat-composer/easywechat-composer/src/ManifestManager.php new file mode 100644 index 0000000..e1af69c --- /dev/null +++ b/vendor/easywechat-composer/easywechat-composer/src/ManifestManager.php @@ -0,0 +1,127 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChatComposer; + +use Composer\Plugin\PluginInterface; + +class ManifestManager +{ + const PACKAGE_TYPE = 'easywechat-extension'; + + const EXTRA_OBSERVER = 'observers'; + + /** + * The vendor path. + * + * @var string + */ + protected $vendorPath; + + /** + * The manifest path. + * + * @var string + */ + protected $manifestPath; + + /** + * @param string $vendorPath + * @param string|null $manifestPath + */ + public function __construct(string $vendorPath, string $manifestPath = null) + { + $this->vendorPath = $vendorPath; + $this->manifestPath = $manifestPath ?: $vendorPath.'/easywechat-composer/easywechat-composer/extensions.php'; + } + + /** + * Remove manifest file. + * + * @return $this + */ + public function unlink() + { + if (file_exists($this->manifestPath)) { + @unlink($this->manifestPath); + } + + return $this; + } + + /** + * Build the manifest file. + */ + public function build() + { + $packages = []; + + if (file_exists($installed = $this->vendorPath.'/composer/installed.json')) { + $packages = json_decode(file_get_contents($installed), true); + if (version_compare(PluginInterface::PLUGIN_API_VERSION, '2.0.0', 'ge')) { + $packages = $packages['packages']; + } + } + + $this->write($this->map($packages)); + } + + /** + * @param array $packages + * + * @return array + */ + protected function map(array $packages): array + { + $manifest = []; + + $packages = array_filter($packages, function ($package) { + if(isset($package['type'])){ + return $package['type'] === self::PACKAGE_TYPE; + } + }); + + foreach ($packages as $package) { + $manifest[$package['name']] = [self::EXTRA_OBSERVER => $package['extra'][self::EXTRA_OBSERVER] ?? []]; + } + + return $manifest; + } + + /** + * Write the manifest array to a file. + * + * @param array $manifest + */ + protected function write(array $manifest) + { + file_put_contents( + $this->manifestPath, + 'invalidate($this->manifestPath); + } + + /** + * Invalidate the given file. + * + * @param string $file + */ + protected function invalidate($file) + { + if (function_exists('opcache_invalidate')) { + @opcache_invalidate($file, true); + } + } +} diff --git a/vendor/easywechat-composer/easywechat-composer/src/Plugin.php b/vendor/easywechat-composer/easywechat-composer/src/Plugin.php new file mode 100644 index 0000000..cd9e504 --- /dev/null +++ b/vendor/easywechat-composer/easywechat-composer/src/Plugin.php @@ -0,0 +1,107 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChatComposer; + +use Composer\Composer; +use Composer\EventDispatcher\EventSubscriberInterface; +use Composer\Installer\PackageEvent; +use Composer\Installer\PackageEvents; +use Composer\IO\IOInterface; +use Composer\Plugin\Capable; +use Composer\Plugin\PluginInterface; +use Composer\Script\Event; +use Composer\Script\ScriptEvents; + +class Plugin implements PluginInterface, EventSubscriberInterface, Capable +{ + /** + * @var bool + */ + protected $activated = true; + + /** + * Apply plugin modifications to Composer. + */ + public function activate(Composer $composer, IOInterface $io) + { + // + } + + /** + * Remove any hooks from Composer. + * + * This will be called when a plugin is deactivated before being + * uninstalled, but also before it gets upgraded to a new version + * so the old one can be deactivated and the new one activated. + */ + public function deactivate(Composer $composer, IOInterface $io) + { + // + } + + /** + * Prepare the plugin to be uninstalled. + * + * This will be called after deactivate. + */ + public function uninstall(Composer $composer, IOInterface $io) + { + } + + /** + * @return array + */ + public function getCapabilities() + { + return [ + 'Composer\Plugin\Capability\CommandProvider' => 'EasyWeChatComposer\Commands\Provider', + ]; + } + + /** + * Listen events. + * + * @return array + */ + public static function getSubscribedEvents() + { + return [ + PackageEvents::PRE_PACKAGE_UNINSTALL => 'prePackageUninstall', + ScriptEvents::POST_AUTOLOAD_DUMP => 'postAutoloadDump', + ]; + } + + /** + * @param \Composer\Installer\PackageEvent + */ + public function prePackageUninstall(PackageEvent $event) + { + if ($event->getOperation()->getPackage()->getName() === 'overtrue/wechat') { + $this->activated = false; + } + } + + public function postAutoloadDump(Event $event) + { + if (!$this->activated) { + return; + } + + $manifest = new ManifestManager( + rtrim($event->getComposer()->getConfig()->get('vendor-dir'), '/') + ); + + $manifest->unlink()->build(); + } +} diff --git a/vendor/easywechat-composer/easywechat-composer/src/Traits/MakesHttpRequests.php b/vendor/easywechat-composer/easywechat-composer/src/Traits/MakesHttpRequests.php new file mode 100644 index 0000000..091a75c --- /dev/null +++ b/vendor/easywechat-composer/easywechat-composer/src/Traits/MakesHttpRequests.php @@ -0,0 +1,110 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChatComposer\Traits; + +use EasyWeChat\Kernel\Http\StreamResponse; +use EasyWeChat\Kernel\Traits\ResponseCastable; +use EasyWeChatComposer\Contracts\Encrypter; +use EasyWeChatComposer\EasyWeChat; +use EasyWeChatComposer\Encryption\DefaultEncrypter; +use EasyWeChatComposer\Exceptions\DelegationException; +use GuzzleHttp\Client; +use GuzzleHttp\ClientInterface; + +trait MakesHttpRequests +{ + use ResponseCastable; + + /** + * @var \GuzzleHttp\ClientInterface + */ + protected $httpClient; + + /** + * @var \EasyWeChatComposer\Contracts\Encrypter + */ + protected $encrypter; + + /** + * @param string $endpoint + * @param array $payload + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function request($endpoint, array $payload) + { + $response = $this->getHttpClient()->request('POST', $endpoint, [ + 'form_params' => $this->buildFormParams($payload), + ]); + + $parsed = $this->parseResponse($response); + + return $this->detectAndCastResponseToType( + $this->getEncrypter()->decrypt($parsed['response']), + ($parsed['response_type'] === StreamResponse::class) ? 'raw' : $this->app['config']['response_type'] + ); + } + + /** + * @param array $payload + * + * @return array + */ + protected function buildFormParams($payload) + { + return [ + 'encrypted' => $this->getEncrypter()->encrypt(json_encode($payload)), + ]; + } + + /** + * @param \Psr\Http\Message\ResponseInterface $response + * + * @return array + */ + protected function parseResponse($response) + { + $result = json_decode((string) $response->getBody(), true); + + if (isset($result['exception'])) { + throw (new DelegationException($result['message']))->setException($result['exception']); + } + + return $result; + } + + /** + * @return \GuzzleHttp\ClientInterface + */ + protected function getHttpClient(): ClientInterface + { + return $this->httpClient ?: $this->httpClient = new Client([ + 'base_uri' => $this->app['config']['delegation']['host'], + ]); + } + + /** + * @return \EasyWeChatComposer\Contracts\Encrypter + */ + protected function getEncrypter(): Encrypter + { + return $this->encrypter ?: $this->encrypter = new DefaultEncrypter( + EasyWeChat::getEncryptionKey() + ); + } +} diff --git a/vendor/easywechat-composer/easywechat-composer/src/Traits/WithAggregator.php b/vendor/easywechat-composer/easywechat-composer/src/Traits/WithAggregator.php new file mode 100644 index 0000000..a3eb16e --- /dev/null +++ b/vendor/easywechat-composer/easywechat-composer/src/Traits/WithAggregator.php @@ -0,0 +1,60 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChatComposer\Traits; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChatComposer\Delegation\DelegationTo; +use EasyWeChatComposer\EasyWeChat; + +trait WithAggregator +{ + /** + * Aggregate. + */ + protected function aggregate() + { + foreach (EasyWeChat::config() as $key => $value) { + $this['config']->set($key, $value); + } + } + + /** + * @return bool + */ + public function shouldDelegate($id) + { + return $this['config']->get('delegation.enabled') + && $this->offsetGet($id) instanceof BaseClient; + } + + /** + * @return $this + */ + public function shouldntDelegate() + { + $this['config']->set('delegation.enabled', false); + + return $this; + } + + /** + * @param string $id + * + * @return \EasyWeChatComposer\Delegation + */ + public function delegateTo($id) + { + return new DelegationTo($this, $id); + } +} diff --git a/vendor/easywechat-composer/easywechat-composer/tests/ManifestManagerTest.php b/vendor/easywechat-composer/easywechat-composer/tests/ManifestManagerTest.php new file mode 100644 index 0000000..23b8e2c --- /dev/null +++ b/vendor/easywechat-composer/easywechat-composer/tests/ManifestManagerTest.php @@ -0,0 +1,37 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChatComposer\Tests; + +use EasyWeChatComposer\ManifestManager; +use PHPUnit\Framework\TestCase; + +class ManifestManagerTest extends TestCase +{ + private $vendorPath; + private $manifestPath; + + protected function getManifestManager() + { + return new ManifestManager( + $this->vendorPath = __DIR__.'/__fixtures__/vendor/', + $this->manifestPath = __DIR__.'/__fixtures__/extensions.php' + ); + } + + public function testUnlink() + { + $this->assertInstanceOf(ManifestManager::class, $this->getManifestManager()->unlink()); + $this->assertFalse(file_exists($this->manifestPath)); + } +} diff --git a/vendor/guzzlehttp/guzzle/.php_cs b/vendor/guzzlehttp/guzzle/.php_cs new file mode 100644 index 0000000..2dd5036 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/.php_cs @@ -0,0 +1,23 @@ +setRiskyAllowed(true) + ->setRules([ + '@PSR2' => true, + 'array_syntax' => ['syntax' => 'short'], + 'declare_strict_types' => false, + 'concat_space' => ['spacing'=>'one'], + 'php_unit_test_case_static_method_calls' => ['call_type' => 'self'], + 'ordered_imports' => true, + // 'phpdoc_align' => ['align'=>'vertical'], + // 'native_function_invocation' => true, + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in(__DIR__.'/src') + ->in(__DIR__.'/tests') + ->name('*.php') + ) +; + +return $config; diff --git a/vendor/guzzlehttp/guzzle/CHANGELOG.md b/vendor/guzzlehttp/guzzle/CHANGELOG.md new file mode 100644 index 0000000..95d26df --- /dev/null +++ b/vendor/guzzlehttp/guzzle/CHANGELOG.md @@ -0,0 +1,1342 @@ +# Change Log + +## 6.5.6 - 2022-05-25 + +* Fix cross-domain cookie leakage + +## 6.5.5 - 2020-06-16 + +* Unpin version constraint for `symfony/polyfill-intl-idn` [#2678](https://github.com/guzzle/guzzle/pull/2678) + +## 6.5.4 - 2020-05-25 + +* Fix various intl icu issues [#2626](https://github.com/guzzle/guzzle/pull/2626) + +## 6.5.3 - 2020-04-18 + +* Use Symfony intl-idn polyfill [#2550](https://github.com/guzzle/guzzle/pull/2550) +* Remove use of internal functions [#2548](https://github.com/guzzle/guzzle/pull/2548) + +## 6.5.2 - 2019-12-23 + +* idn_to_ascii() fix for old PHP versions [#2489](https://github.com/guzzle/guzzle/pull/2489) + +## 6.5.1 - 2019-12-21 + +* Better defaults for PHP installations with old ICU lib [#2454](https://github.com/guzzle/guzzle/pull/2454) +* IDN support for redirects [#2424](https://github.com/guzzle/guzzle/pull/2424) + +## 6.5.0 - 2019-12-07 + +* Improvement: Added support for reset internal queue in MockHandler. [#2143](https://github.com/guzzle/guzzle/pull/2143) +* Improvement: Added support to pass arbitrary options to `curl_multi_init`. [#2287](https://github.com/guzzle/guzzle/pull/2287) +* Fix: Gracefully handle passing `null` to the `header` option. [#2132](https://github.com/guzzle/guzzle/pull/2132) +* Fix: `RetryMiddleware` did not do exponential delay between retries due unit mismatch. [#2132](https://github.com/guzzle/guzzle/pull/2132) + Previously, `RetryMiddleware` would sleep for 1 millisecond, then 2 milliseconds, then 4 milliseconds. + **After this change, `RetryMiddleware` will sleep for 1 second, then 2 seconds, then 4 seconds.** + `Middleware::retry()` accepts a second callback parameter to override the default timeouts if needed. +* Fix: Prevent undefined offset when using array for ssl_key options. [#2348](https://github.com/guzzle/guzzle/pull/2348) +* Deprecated `ClientInterface::VERSION` + +## 6.4.1 - 2019-10-23 + +* No `guzzle.phar` was created in 6.4.0 due expired API token. This release will fix that +* Added `parent::__construct()` to `FileCookieJar` and `SessionCookieJar` + +## 6.4.0 - 2019-10-23 + +* Improvement: Improved error messages when using curl < 7.21.2 [#2108](https://github.com/guzzle/guzzle/pull/2108) +* Fix: Test if response is readable before returning a summary in `RequestException::getResponseBodySummary()` [#2081](https://github.com/guzzle/guzzle/pull/2081) +* Fix: Add support for GUZZLE_CURL_SELECT_TIMEOUT environment variable [#2161](https://github.com/guzzle/guzzle/pull/2161) +* Improvement: Added `GuzzleHttp\Exception\InvalidArgumentException` [#2163](https://github.com/guzzle/guzzle/pull/2163) +* Improvement: Added `GuzzleHttp\_current_time()` to use `hrtime()` if that function exists. [#2242](https://github.com/guzzle/guzzle/pull/2242) +* Improvement: Added curl's `appconnect_time` in `TransferStats` [#2284](https://github.com/guzzle/guzzle/pull/2284) +* Improvement: Make GuzzleException extend Throwable wherever it's available [#2273](https://github.com/guzzle/guzzle/pull/2273) +* Fix: Prevent concurrent writes to file when saving `CookieJar` [#2335](https://github.com/guzzle/guzzle/pull/2335) +* Improvement: Update `MockHandler` so we can test transfer time [#2362](https://github.com/guzzle/guzzle/pull/2362) + +## 6.3.3 - 2018-04-22 + +* Fix: Default headers when decode_content is specified + + +## 6.3.2 - 2018-03-26 + +* Fix: Release process + + +## 6.3.1 - 2018-03-26 + +* Bug fix: Parsing 0 epoch expiry times in cookies [#2014](https://github.com/guzzle/guzzle/pull/2014) +* Improvement: Better ConnectException detection [#2012](https://github.com/guzzle/guzzle/pull/2012) +* Bug fix: Malformed domain that contains a "/" [#1999](https://github.com/guzzle/guzzle/pull/1999) +* Bug fix: Undefined offset when a cookie has no first key-value pair [#1998](https://github.com/guzzle/guzzle/pull/1998) +* Improvement: Support PHPUnit 6 [#1953](https://github.com/guzzle/guzzle/pull/1953) +* Bug fix: Support empty headers [#1915](https://github.com/guzzle/guzzle/pull/1915) +* Bug fix: Ignore case during header modifications [#1916](https://github.com/guzzle/guzzle/pull/1916) + ++ Minor code cleanups, documentation fixes and clarifications. + + +## 6.3.0 - 2017-06-22 + +* Feature: force IP resolution (ipv4 or ipv6) [#1608](https://github.com/guzzle/guzzle/pull/1608), [#1659](https://github.com/guzzle/guzzle/pull/1659) +* Improvement: Don't include summary in exception message when body is empty [#1621](https://github.com/guzzle/guzzle/pull/1621) +* Improvement: Handle `on_headers` option in MockHandler [#1580](https://github.com/guzzle/guzzle/pull/1580) +* Improvement: Added SUSE Linux CA path [#1609](https://github.com/guzzle/guzzle/issues/1609) +* Improvement: Use class reference for getting the name of the class instead of using hardcoded strings [#1641](https://github.com/guzzle/guzzle/pull/1641) +* Feature: Added `read_timeout` option [#1611](https://github.com/guzzle/guzzle/pull/1611) +* Bug fix: PHP 7.x fixes [#1685](https://github.com/guzzle/guzzle/pull/1685), [#1686](https://github.com/guzzle/guzzle/pull/1686), [#1811](https://github.com/guzzle/guzzle/pull/1811) +* Deprecation: BadResponseException instantiation without a response [#1642](https://github.com/guzzle/guzzle/pull/1642) +* Feature: Added NTLM auth [#1569](https://github.com/guzzle/guzzle/pull/1569) +* Feature: Track redirect HTTP status codes [#1711](https://github.com/guzzle/guzzle/pull/1711) +* Improvement: Check handler type during construction [#1745](https://github.com/guzzle/guzzle/pull/1745) +* Improvement: Always include the Content-Length if there's a body [#1721](https://github.com/guzzle/guzzle/pull/1721) +* Feature: Added convenience method to access a cookie by name [#1318](https://github.com/guzzle/guzzle/pull/1318) +* Bug fix: Fill `CURLOPT_CAPATH` and `CURLOPT_CAINFO` properly [#1684](https://github.com/guzzle/guzzle/pull/1684) +* Improvement: Use `\GuzzleHttp\Promise\rejection_for` function instead of object init [#1827](https://github.com/guzzle/guzzle/pull/1827) + + ++ Minor code cleanups, documentation fixes and clarifications. + +## 6.2.3 - 2017-02-28 + +* Fix deprecations with guzzle/psr7 version 1.4 + +## 6.2.2 - 2016-10-08 + +* Allow to pass nullable Response to delay callable +* Only add scheme when host is present +* Fix drain case where content-length is the literal string zero +* Obfuscate in-URL credentials in exceptions + +## 6.2.1 - 2016-07-18 + +* Address HTTP_PROXY security vulnerability, CVE-2016-5385: + https://httpoxy.org/ +* Fixing timeout bug with StreamHandler: + https://github.com/guzzle/guzzle/pull/1488 +* Only read up to `Content-Length` in PHP StreamHandler to avoid timeouts when + a server does not honor `Connection: close`. +* Ignore URI fragment when sending requests. + +## 6.2.0 - 2016-03-21 + +* Feature: added `GuzzleHttp\json_encode` and `GuzzleHttp\json_decode`. + https://github.com/guzzle/guzzle/pull/1389 +* Bug fix: Fix sleep calculation when waiting for delayed requests. + https://github.com/guzzle/guzzle/pull/1324 +* Feature: More flexible history containers. + https://github.com/guzzle/guzzle/pull/1373 +* Bug fix: defer sink stream opening in StreamHandler. + https://github.com/guzzle/guzzle/pull/1377 +* Bug fix: do not attempt to escape cookie values. + https://github.com/guzzle/guzzle/pull/1406 +* Feature: report original content encoding and length on decoded responses. + https://github.com/guzzle/guzzle/pull/1409 +* Bug fix: rewind seekable request bodies before dispatching to cURL. + https://github.com/guzzle/guzzle/pull/1422 +* Bug fix: provide an empty string to `http_build_query` for HHVM workaround. + https://github.com/guzzle/guzzle/pull/1367 + +## 6.1.1 - 2015-11-22 + +* Bug fix: Proxy::wrapSync() now correctly proxies to the appropriate handler + https://github.com/guzzle/guzzle/commit/911bcbc8b434adce64e223a6d1d14e9a8f63e4e4 +* Feature: HandlerStack is now more generic. + https://github.com/guzzle/guzzle/commit/f2102941331cda544745eedd97fc8fd46e1ee33e +* Bug fix: setting verify to false in the StreamHandler now disables peer + verification. https://github.com/guzzle/guzzle/issues/1256 +* Feature: Middleware now uses an exception factory, including more error + context. https://github.com/guzzle/guzzle/pull/1282 +* Feature: better support for disabled functions. + https://github.com/guzzle/guzzle/pull/1287 +* Bug fix: fixed regression where MockHandler was not using `sink`. + https://github.com/guzzle/guzzle/pull/1292 + +## 6.1.0 - 2015-09-08 + +* Feature: Added the `on_stats` request option to provide access to transfer + statistics for requests. https://github.com/guzzle/guzzle/pull/1202 +* Feature: Added the ability to persist session cookies in CookieJars. + https://github.com/guzzle/guzzle/pull/1195 +* Feature: Some compatibility updates for Google APP Engine + https://github.com/guzzle/guzzle/pull/1216 +* Feature: Added support for NO_PROXY to prevent the use of a proxy based on + a simple set of rules. https://github.com/guzzle/guzzle/pull/1197 +* Feature: Cookies can now contain square brackets. + https://github.com/guzzle/guzzle/pull/1237 +* Bug fix: Now correctly parsing `=` inside of quotes in Cookies. + https://github.com/guzzle/guzzle/pull/1232 +* Bug fix: Cusotm cURL options now correctly override curl options of the + same name. https://github.com/guzzle/guzzle/pull/1221 +* Bug fix: Content-Type header is now added when using an explicitly provided + multipart body. https://github.com/guzzle/guzzle/pull/1218 +* Bug fix: Now ignoring Set-Cookie headers that have no name. +* Bug fix: Reason phrase is no longer cast to an int in some cases in the + cURL handler. https://github.com/guzzle/guzzle/pull/1187 +* Bug fix: Remove the Authorization header when redirecting if the Host + header changes. https://github.com/guzzle/guzzle/pull/1207 +* Bug fix: Cookie path matching fixes + https://github.com/guzzle/guzzle/issues/1129 +* Bug fix: Fixing the cURL `body_as_string` setting + https://github.com/guzzle/guzzle/pull/1201 +* Bug fix: quotes are no longer stripped when parsing cookies. + https://github.com/guzzle/guzzle/issues/1172 +* Bug fix: `form_params` and `query` now always uses the `&` separator. + https://github.com/guzzle/guzzle/pull/1163 +* Bug fix: Adding a Content-Length to PHP stream wrapper requests if not set. + https://github.com/guzzle/guzzle/pull/1189 + +## 6.0.2 - 2015-07-04 + +* Fixed a memory leak in the curl handlers in which references to callbacks + were not being removed by `curl_reset`. +* Cookies are now extracted properly before redirects. +* Cookies now allow more character ranges. +* Decoded Content-Encoding responses are now modified to correctly reflect + their state if the encoding was automatically removed by a handler. This + means that the `Content-Encoding` header may be removed an the + `Content-Length` modified to reflect the message size after removing the + encoding. +* Added a more explicit error message when trying to use `form_params` and + `multipart` in the same request. +* Several fixes for HHVM support. +* Functions are now conditionally required using an additional level of + indirection to help with global Composer installations. + +## 6.0.1 - 2015-05-27 + +* Fixed a bug with serializing the `query` request option where the `&` + separator was missing. +* Added a better error message for when `body` is provided as an array. Please + use `form_params` or `multipart` instead. +* Various doc fixes. + +## 6.0.0 - 2015-05-26 + +* See the UPGRADING.md document for more information. +* Added `multipart` and `form_params` request options. +* Added `synchronous` request option. +* Added the `on_headers` request option. +* Fixed `expect` handling. +* No longer adding default middlewares in the client ctor. These need to be + present on the provided handler in order to work. +* Requests are no longer initiated when sending async requests with the + CurlMultiHandler. This prevents unexpected recursion from requests completing + while ticking the cURL loop. +* Removed the semantics of setting `default` to `true`. This is no longer + required now that the cURL loop is not ticked for async requests. +* Added request and response logging middleware. +* No longer allowing self signed certificates when using the StreamHandler. +* Ensuring that `sink` is valid if saving to a file. +* Request exceptions now include a "handler context" which provides handler + specific contextual information. +* Added `GuzzleHttp\RequestOptions` to allow request options to be applied + using constants. +* `$maxHandles` has been removed from CurlMultiHandler. +* `MultipartPostBody` is now part of the `guzzlehttp/psr7` package. + +## 5.3.0 - 2015-05-19 + +* Mock now supports `save_to` +* Marked `AbstractRequestEvent::getTransaction()` as public. +* Fixed a bug in which multiple headers using different casing would overwrite + previous headers in the associative array. +* Added `Utils::getDefaultHandler()` +* Marked `GuzzleHttp\Client::getDefaultUserAgent` as deprecated. +* URL scheme is now always lowercased. + +## 6.0.0-beta.1 + +* Requires PHP >= 5.5 +* Updated to use PSR-7 + * Requires immutable messages, which basically means an event based system + owned by a request instance is no longer possible. + * Utilizing the [Guzzle PSR-7 package](https://github.com/guzzle/psr7). + * Removed the dependency on `guzzlehttp/streams`. These stream abstractions + are available in the `guzzlehttp/psr7` package under the `GuzzleHttp\Psr7` + namespace. +* Added middleware and handler system + * Replaced the Guzzle event and subscriber system with a middleware system. + * No longer depends on RingPHP, but rather places the HTTP handlers directly + in Guzzle, operating on PSR-7 messages. + * Retry logic is now encapsulated in `GuzzleHttp\Middleware::retry`, which + means the `guzzlehttp/retry-subscriber` is now obsolete. + * Mocking responses is now handled using `GuzzleHttp\Handler\MockHandler`. +* Asynchronous responses + * No longer supports the `future` request option to send an async request. + Instead, use one of the `*Async` methods of a client (e.g., `requestAsync`, + `getAsync`, etc.). + * Utilizing `GuzzleHttp\Promise` instead of React's promise library to avoid + recursion required by chaining and forwarding react promises. See + https://github.com/guzzle/promises + * Added `requestAsync` and `sendAsync` to send request asynchronously. + * Added magic methods for `getAsync()`, `postAsync()`, etc. to send requests + asynchronously. +* Request options + * POST and form updates + * Added the `form_fields` and `form_files` request options. + * Removed the `GuzzleHttp\Post` namespace. + * The `body` request option no longer accepts an array for POST requests. + * The `exceptions` request option has been deprecated in favor of the + `http_errors` request options. + * The `save_to` request option has been deprecated in favor of `sink` request + option. +* Clients no longer accept an array of URI template string and variables for + URI variables. You will need to expand URI templates before passing them + into a client constructor or request method. +* Client methods `get()`, `post()`, `put()`, `patch()`, `options()`, etc. are + now magic methods that will send synchronous requests. +* Replaced `Utils.php` with plain functions in `functions.php`. +* Removed `GuzzleHttp\Collection`. +* Removed `GuzzleHttp\BatchResults`. Batched pool results are now returned as + an array. +* Removed `GuzzleHttp\Query`. Query string handling is now handled using an + associative array passed into the `query` request option. The query string + is serialized using PHP's `http_build_query`. If you need more control, you + can pass the query string in as a string. +* `GuzzleHttp\QueryParser` has been replaced with the + `GuzzleHttp\Psr7\parse_query`. + +## 5.2.0 - 2015-01-27 + +* Added `AppliesHeadersInterface` to make applying headers to a request based + on the body more generic and not specific to `PostBodyInterface`. +* Reduced the number of stack frames needed to send requests. +* Nested futures are now resolved in the client rather than the RequestFsm +* Finishing state transitions is now handled in the RequestFsm rather than the + RingBridge. +* Added a guard in the Pool class to not use recursion for request retries. + +## 5.1.0 - 2014-12-19 + +* Pool class no longer uses recursion when a request is intercepted. +* The size of a Pool can now be dynamically adjusted using a callback. + See https://github.com/guzzle/guzzle/pull/943. +* Setting a request option to `null` when creating a request with a client will + ensure that the option is not set. This allows you to overwrite default + request options on a per-request basis. + See https://github.com/guzzle/guzzle/pull/937. +* Added the ability to limit which protocols are allowed for redirects by + specifying a `protocols` array in the `allow_redirects` request option. +* Nested futures due to retries are now resolved when waiting for synchronous + responses. See https://github.com/guzzle/guzzle/pull/947. +* `"0"` is now an allowed URI path. See + https://github.com/guzzle/guzzle/pull/935. +* `Query` no longer typehints on the `$query` argument in the constructor, + allowing for strings and arrays. +* Exceptions thrown in the `end` event are now correctly wrapped with Guzzle + specific exceptions if necessary. + +## 5.0.3 - 2014-11-03 + +This change updates query strings so that they are treated as un-encoded values +by default where the value represents an un-encoded value to send over the +wire. A Query object then encodes the value before sending over the wire. This +means that even value query string values (e.g., ":") are url encoded. This +makes the Query class match PHP's http_build_query function. However, if you +want to send requests over the wire using valid query string characters that do +not need to be encoded, then you can provide a string to Url::setQuery() and +pass true as the second argument to specify that the query string is a raw +string that should not be parsed or encoded (unless a call to getQuery() is +subsequently made, forcing the query-string to be converted into a Query +object). + +## 5.0.2 - 2014-10-30 + +* Added a trailing `\r\n` to multipart/form-data payloads. See + https://github.com/guzzle/guzzle/pull/871 +* Added a `GuzzleHttp\Pool::send()` convenience method to match the docs. +* Status codes are now returned as integers. See + https://github.com/guzzle/guzzle/issues/881 +* No longer overwriting an existing `application/x-www-form-urlencoded` header + when sending POST requests, allowing for customized headers. See + https://github.com/guzzle/guzzle/issues/877 +* Improved path URL serialization. + + * No longer double percent-encoding characters in the path or query string if + they are already encoded. + * Now properly encoding the supplied path to a URL object, instead of only + encoding ' ' and '?'. + * Note: This has been changed in 5.0.3 to now encode query string values by + default unless the `rawString` argument is provided when setting the query + string on a URL: Now allowing many more characters to be present in the + query string without being percent encoded. See http://tools.ietf.org/html/rfc3986#appendix-A + +## 5.0.1 - 2014-10-16 + +Bugfix release. + +* Fixed an issue where connection errors still returned response object in + error and end events event though the response is unusable. This has been + corrected so that a response is not returned in the `getResponse` method of + these events if the response did not complete. https://github.com/guzzle/guzzle/issues/867 +* Fixed an issue where transfer statistics were not being populated in the + RingBridge. https://github.com/guzzle/guzzle/issues/866 + +## 5.0.0 - 2014-10-12 + +Adding support for non-blocking responses and some minor API cleanup. + +### New Features + +* Added support for non-blocking responses based on `guzzlehttp/guzzle-ring`. +* Added a public API for creating a default HTTP adapter. +* Updated the redirect plugin to be non-blocking so that redirects are sent + concurrently. Other plugins like this can now be updated to be non-blocking. +* Added a "progress" event so that you can get upload and download progress + events. +* Added `GuzzleHttp\Pool` which implements FutureInterface and transfers + requests concurrently using a capped pool size as efficiently as possible. +* Added `hasListeners()` to EmitterInterface. +* Removed `GuzzleHttp\ClientInterface::sendAll` and marked + `GuzzleHttp\Client::sendAll` as deprecated (it's still there, just not the + recommended way). + +### Breaking changes + +The breaking changes in this release are relatively minor. The biggest thing to +look out for is that request and response objects no longer implement fluent +interfaces. + +* Removed the fluent interfaces (i.e., `return $this`) from requests, + responses, `GuzzleHttp\Collection`, `GuzzleHttp\Url`, + `GuzzleHttp\Query`, `GuzzleHttp\Post\PostBody`, and + `GuzzleHttp\Cookie\SetCookie`. This blog post provides a good outline of + why I did this: http://ocramius.github.io/blog/fluent-interfaces-are-evil/. + This also makes the Guzzle message interfaces compatible with the current + PSR-7 message proposal. +* Removed "functions.php", so that Guzzle is truly PSR-4 compliant. Except + for the HTTP request functions from function.php, these functions are now + implemented in `GuzzleHttp\Utils` using camelCase. `GuzzleHttp\json_decode` + moved to `GuzzleHttp\Utils::jsonDecode`. `GuzzleHttp\get_path` moved to + `GuzzleHttp\Utils::getPath`. `GuzzleHttp\set_path` moved to + `GuzzleHttp\Utils::setPath`. `GuzzleHttp\batch` should now be + `GuzzleHttp\Pool::batch`, which returns an `objectStorage`. Using functions.php + caused problems for many users: they aren't PSR-4 compliant, require an + explicit include, and needed an if-guard to ensure that the functions are not + declared multiple times. +* Rewrote adapter layer. + * Removing all classes from `GuzzleHttp\Adapter`, these are now + implemented as callables that are stored in `GuzzleHttp\Ring\Client`. + * Removed the concept of "parallel adapters". Sending requests serially or + concurrently is now handled using a single adapter. + * Moved `GuzzleHttp\Adapter\Transaction` to `GuzzleHttp\Transaction`. The + Transaction object now exposes the request, response, and client as public + properties. The getters and setters have been removed. +* Removed the "headers" event. This event was only useful for changing the + body a response once the headers of the response were known. You can implement + a similar behavior in a number of ways. One example might be to use a + FnStream that has access to the transaction being sent. For example, when the + first byte is written, you could check if the response headers match your + expectations, and if so, change the actual stream body that is being + written to. +* Removed the `asArray` parameter from + `GuzzleHttp\Message\MessageInterface::getHeader`. If you want to get a header + value as an array, then use the newly added `getHeaderAsArray()` method of + `MessageInterface`. This change makes the Guzzle interfaces compatible with + the PSR-7 interfaces. +* `GuzzleHttp\Message\MessageFactory` no longer allows subclasses to add + custom request options using double-dispatch (this was an implementation + detail). Instead, you should now provide an associative array to the + constructor which is a mapping of the request option name mapping to a + function that applies the option value to a request. +* Removed the concept of "throwImmediately" from exceptions and error events. + This control mechanism was used to stop a transfer of concurrent requests + from completing. This can now be handled by throwing the exception or by + cancelling a pool of requests or each outstanding future request individually. +* Updated to "GuzzleHttp\Streams" 3.0. + * `GuzzleHttp\Stream\StreamInterface::getContents()` no longer accepts a + `maxLen` parameter. This update makes the Guzzle streams project + compatible with the current PSR-7 proposal. + * `GuzzleHttp\Stream\Stream::__construct`, + `GuzzleHttp\Stream\Stream::factory`, and + `GuzzleHttp\Stream\Utils::create` no longer accept a size in the second + argument. They now accept an associative array of options, including the + "size" key and "metadata" key which can be used to provide custom metadata. + +## 4.2.2 - 2014-09-08 + +* Fixed a memory leak in the CurlAdapter when reusing cURL handles. +* No longer using `request_fulluri` in stream adapter proxies. +* Relative redirects are now based on the last response, not the first response. + +## 4.2.1 - 2014-08-19 + +* Ensuring that the StreamAdapter does not always add a Content-Type header +* Adding automated github releases with a phar and zip + +## 4.2.0 - 2014-08-17 + +* Now merging in default options using a case-insensitive comparison. + Closes https://github.com/guzzle/guzzle/issues/767 +* Added the ability to automatically decode `Content-Encoding` response bodies + using the `decode_content` request option. This is set to `true` by default + to decode the response body if it comes over the wire with a + `Content-Encoding`. Set this value to `false` to disable decoding the + response content, and pass a string to provide a request `Accept-Encoding` + header and turn on automatic response decoding. This feature now allows you + to pass an `Accept-Encoding` header in the headers of a request but still + disable automatic response decoding. + Closes https://github.com/guzzle/guzzle/issues/764 +* Added the ability to throw an exception immediately when transferring + requests in parallel. Closes https://github.com/guzzle/guzzle/issues/760 +* Updating guzzlehttp/streams dependency to ~2.1 +* No longer utilizing the now deprecated namespaced methods from the stream + package. + +## 4.1.8 - 2014-08-14 + +* Fixed an issue in the CurlFactory that caused setting the `stream=false` + request option to throw an exception. + See: https://github.com/guzzle/guzzle/issues/769 +* TransactionIterator now calls rewind on the inner iterator. + See: https://github.com/guzzle/guzzle/pull/765 +* You can now set the `Content-Type` header to `multipart/form-data` + when creating POST requests to force multipart bodies. + See https://github.com/guzzle/guzzle/issues/768 + +## 4.1.7 - 2014-08-07 + +* Fixed an error in the HistoryPlugin that caused the same request and response + to be logged multiple times when an HTTP protocol error occurs. +* Ensuring that cURL does not add a default Content-Type when no Content-Type + has been supplied by the user. This prevents the adapter layer from modifying + the request that is sent over the wire after any listeners may have already + put the request in a desired state (e.g., signed the request). +* Throwing an exception when you attempt to send requests that have the + "stream" set to true in parallel using the MultiAdapter. +* Only calling curl_multi_select when there are active cURL handles. This was + previously changed and caused performance problems on some systems due to PHP + always selecting until the maximum select timeout. +* Fixed a bug where multipart/form-data POST fields were not correctly + aggregated (e.g., values with "&"). + +## 4.1.6 - 2014-08-03 + +* Added helper methods to make it easier to represent messages as strings, + including getting the start line and getting headers as a string. + +## 4.1.5 - 2014-08-02 + +* Automatically retrying cURL "Connection died, retrying a fresh connect" + errors when possible. +* cURL implementation cleanup +* Allowing multiple event subscriber listeners to be registered per event by + passing an array of arrays of listener configuration. + +## 4.1.4 - 2014-07-22 + +* Fixed a bug that caused multi-part POST requests with more than one field to + serialize incorrectly. +* Paths can now be set to "0" +* `ResponseInterface::xml` now accepts a `libxml_options` option and added a + missing default argument that was required when parsing XML response bodies. +* A `save_to` stream is now created lazily, which means that files are not + created on disk unless a request succeeds. + +## 4.1.3 - 2014-07-15 + +* Various fixes to multipart/form-data POST uploads +* Wrapping function.php in an if-statement to ensure Guzzle can be used + globally and in a Composer install +* Fixed an issue with generating and merging in events to an event array +* POST headers are only applied before sending a request to allow you to change + the query aggregator used before uploading +* Added much more robust query string parsing +* Fixed various parsing and normalization issues with URLs +* Fixing an issue where multi-valued headers were not being utilized correctly + in the StreamAdapter + +## 4.1.2 - 2014-06-18 + +* Added support for sending payloads with GET requests + +## 4.1.1 - 2014-06-08 + +* Fixed an issue related to using custom message factory options in subclasses +* Fixed an issue with nested form fields in a multi-part POST +* Fixed an issue with using the `json` request option for POST requests +* Added `ToArrayInterface` to `GuzzleHttp\Cookie\CookieJar` + +## 4.1.0 - 2014-05-27 + +* Added a `json` request option to easily serialize JSON payloads. +* Added a `GuzzleHttp\json_decode()` wrapper to safely parse JSON. +* Added `setPort()` and `getPort()` to `GuzzleHttp\Message\RequestInterface`. +* Added the ability to provide an emitter to a client in the client constructor. +* Added the ability to persist a cookie session using $_SESSION. +* Added a trait that can be used to add event listeners to an iterator. +* Removed request method constants from RequestInterface. +* Fixed warning when invalid request start-lines are received. +* Updated MessageFactory to work with custom request option methods. +* Updated cacert bundle to latest build. + +4.0.2 (2014-04-16) +------------------ + +* Proxy requests using the StreamAdapter now properly use request_fulluri (#632) +* Added the ability to set scalars as POST fields (#628) + +## 4.0.1 - 2014-04-04 + +* The HTTP status code of a response is now set as the exception code of + RequestException objects. +* 303 redirects will now correctly switch from POST to GET requests. +* The default parallel adapter of a client now correctly uses the MultiAdapter. +* HasDataTrait now initializes the internal data array as an empty array so + that the toArray() method always returns an array. + +## 4.0.0 - 2014-03-29 + +* For more information on the 4.0 transition, see: + http://mtdowling.com/blog/2014/03/15/guzzle-4-rc/ +* For information on changes and upgrading, see: + https://github.com/guzzle/guzzle/blob/master/UPGRADING.md#3x-to-40 +* Added `GuzzleHttp\batch()` as a convenience function for sending requests in + parallel without needing to write asynchronous code. +* Restructured how events are added to `GuzzleHttp\ClientInterface::sendAll()`. + You can now pass a callable or an array of associative arrays where each + associative array contains the "fn", "priority", and "once" keys. + +## 4.0.0.rc-2 - 2014-03-25 + +* Removed `getConfig()` and `setConfig()` from clients to avoid confusion + around whether things like base_url, message_factory, etc. should be able to + be retrieved or modified. +* Added `getDefaultOption()` and `setDefaultOption()` to ClientInterface +* functions.php functions were renamed using snake_case to match PHP idioms +* Added support for `HTTP_PROXY`, `HTTPS_PROXY`, and + `GUZZLE_CURL_SELECT_TIMEOUT` environment variables +* Added the ability to specify custom `sendAll()` event priorities +* Added the ability to specify custom stream context options to the stream + adapter. +* Added a functions.php function for `get_path()` and `set_path()` +* CurlAdapter and MultiAdapter now use a callable to generate curl resources +* MockAdapter now properly reads a body and emits a `headers` event +* Updated Url class to check if a scheme and host are set before adding ":" + and "//". This allows empty Url (e.g., "") to be serialized as "". +* Parsing invalid XML no longer emits warnings +* Curl classes now properly throw AdapterExceptions +* Various performance optimizations +* Streams are created with the faster `Stream\create()` function +* Marked deprecation_proxy() as internal +* Test server is now a collection of static methods on a class + +## 4.0.0-rc.1 - 2014-03-15 + +* See https://github.com/guzzle/guzzle/blob/master/UPGRADING.md#3x-to-40 + +## 3.8.1 - 2014-01-28 + +* Bug: Always using GET requests when redirecting from a 303 response +* Bug: CURLOPT_SSL_VERIFYHOST is now correctly set to false when setting `$certificateAuthority` to false in + `Guzzle\Http\ClientInterface::setSslVerification()` +* Bug: RedirectPlugin now uses strict RFC 3986 compliance when combining a base URL with a relative URL +* Bug: The body of a request can now be set to `"0"` +* Sending PHP stream requests no longer forces `HTTP/1.0` +* Adding more information to ExceptionCollection exceptions so that users have more context, including a stack trace of + each sub-exception +* Updated the `$ref` attribute in service descriptions to merge over any existing parameters of a schema (rather than + clobbering everything). +* Merging URLs will now use the query string object from the relative URL (thus allowing custom query aggregators) +* Query strings are now parsed in a way that they do no convert empty keys with no value to have a dangling `=`. + For example `foo&bar=baz` is now correctly parsed and recognized as `foo&bar=baz` rather than `foo=&bar=baz`. +* Now properly escaping the regular expression delimiter when matching Cookie domains. +* Network access is now disabled when loading XML documents + +## 3.8.0 - 2013-12-05 + +* Added the ability to define a POST name for a file +* JSON response parsing now properly walks additionalProperties +* cURL error code 18 is now retried automatically in the BackoffPlugin +* Fixed a cURL error when URLs contain fragments +* Fixed an issue in the BackoffPlugin retry event where it was trying to access all exceptions as if they were + CurlExceptions +* CURLOPT_PROGRESS function fix for PHP 5.5 (69fcc1e) +* Added the ability for Guzzle to work with older versions of cURL that do not support `CURLOPT_TIMEOUT_MS` +* Fixed a bug that was encountered when parsing empty header parameters +* UriTemplate now has a `setRegex()` method to match the docs +* The `debug` request parameter now checks if it is truthy rather than if it exists +* Setting the `debug` request parameter to true shows verbose cURL output instead of using the LogPlugin +* Added the ability to combine URLs using strict RFC 3986 compliance +* Command objects can now return the validation errors encountered by the command +* Various fixes to cache revalidation (#437 and 29797e5) +* Various fixes to the AsyncPlugin +* Cleaned up build scripts + +## 3.7.4 - 2013-10-02 + +* Bug fix: 0 is now an allowed value in a description parameter that has a default value (#430) +* Bug fix: SchemaFormatter now returns an integer when formatting to a Unix timestamp + (see https://github.com/aws/aws-sdk-php/issues/147) +* Bug fix: Cleaned up and fixed URL dot segment removal to properly resolve internal dots +* Minimum PHP version is now properly specified as 5.3.3 (up from 5.3.2) (#420) +* Updated the bundled cacert.pem (#419) +* OauthPlugin now supports adding authentication to headers or query string (#425) + +## 3.7.3 - 2013-09-08 + +* Added the ability to get the exception associated with a request/command when using `MultiTransferException` and + `CommandTransferException`. +* Setting `additionalParameters` of a response to false is now honored when parsing responses with a service description +* Schemas are only injected into response models when explicitly configured. +* No longer guessing Content-Type based on the path of a request. Content-Type is now only guessed based on the path of + an EntityBody. +* Bug fix: ChunkedIterator can now properly chunk a \Traversable as well as an \Iterator. +* Bug fix: FilterIterator now relies on `\Iterator` instead of `\Traversable`. +* Bug fix: Gracefully handling malformed responses in RequestMediator::writeResponseBody() +* Bug fix: Replaced call to canCache with canCacheRequest in the CallbackCanCacheStrategy of the CachePlugin +* Bug fix: Visiting XML attributes first before visiting XML children when serializing requests +* Bug fix: Properly parsing headers that contain commas contained in quotes +* Bug fix: mimetype guessing based on a filename is now case-insensitive + +## 3.7.2 - 2013-08-02 + +* Bug fix: Properly URL encoding paths when using the PHP-only version of the UriTemplate expander + See https://github.com/guzzle/guzzle/issues/371 +* Bug fix: Cookie domains are now matched correctly according to RFC 6265 + See https://github.com/guzzle/guzzle/issues/377 +* Bug fix: GET parameters are now used when calculating an OAuth signature +* Bug fix: Fixed an issue with cache revalidation where the If-None-Match header was being double quoted +* `Guzzle\Common\AbstractHasDispatcher::dispatch()` now returns the event that was dispatched +* `Guzzle\Http\QueryString::factory()` now guesses the most appropriate query aggregator to used based on the input. + See https://github.com/guzzle/guzzle/issues/379 +* Added a way to add custom domain objects to service description parsing using the `operation.parse_class` event. See + https://github.com/guzzle/guzzle/pull/380 +* cURL multi cleanup and optimizations + +## 3.7.1 - 2013-07-05 + +* Bug fix: Setting default options on a client now works +* Bug fix: Setting options on HEAD requests now works. See #352 +* Bug fix: Moving stream factory before send event to before building the stream. See #353 +* Bug fix: Cookies no longer match on IP addresses per RFC 6265 +* Bug fix: Correctly parsing header parameters that are in `<>` and quotes +* Added `cert` and `ssl_key` as request options +* `Host` header can now diverge from the host part of a URL if the header is set manually +* `Guzzle\Service\Command\LocationVisitor\Request\XmlVisitor` was rewritten to change from using SimpleXML to XMLWriter +* OAuth parameters are only added via the plugin if they aren't already set +* Exceptions are now thrown when a URL cannot be parsed +* Returning `false` if `Guzzle\Http\EntityBody::getContentMd5()` fails +* Not setting a `Content-MD5` on a command if calculating the Content-MD5 fails via the CommandContentMd5Plugin + +## 3.7.0 - 2013-06-10 + +* See UPGRADING.md for more information on how to upgrade. +* Requests now support the ability to specify an array of $options when creating a request to more easily modify a + request. You can pass a 'request.options' configuration setting to a client to apply default request options to + every request created by a client (e.g. default query string variables, headers, curl options, etc.). +* Added a static facade class that allows you to use Guzzle with static methods and mount the class to `\Guzzle`. + See `Guzzle\Http\StaticClient::mount`. +* Added `command.request_options` to `Guzzle\Service\Command\AbstractCommand` to pass request options to requests + created by a command (e.g. custom headers, query string variables, timeout settings, etc.). +* Stream size in `Guzzle\Stream\PhpStreamRequestFactory` will now be set if Content-Length is returned in the + headers of a response +* Added `Guzzle\Common\Collection::setPath($path, $value)` to set a value into an array using a nested key + (e.g. `$collection->setPath('foo/baz/bar', 'test'); echo $collection['foo']['bar']['bar'];`) +* ServiceBuilders now support storing and retrieving arbitrary data +* CachePlugin can now purge all resources for a given URI +* CachePlugin can automatically purge matching cached items when a non-idempotent request is sent to a resource +* CachePlugin now uses the Vary header to determine if a resource is a cache hit +* `Guzzle\Http\Message\Response` now implements `\Serializable` +* Added `Guzzle\Cache\CacheAdapterFactory::fromCache()` to more easily create cache adapters +* `Guzzle\Service\ClientInterface::execute()` now accepts an array, single command, or Traversable +* Fixed a bug in `Guzzle\Http\Message\Header\Link::addLink()` +* Better handling of calculating the size of a stream in `Guzzle\Stream\Stream` using fstat() and caching the size +* `Guzzle\Common\Exception\ExceptionCollection` now creates a more readable exception message +* Fixing BC break: Added back the MonologLogAdapter implementation rather than extending from PsrLog so that older + Symfony users can still use the old version of Monolog. +* Fixing BC break: Added the implementation back in for `Guzzle\Http\Message\AbstractMessage::getTokenizedHeader()`. + Now triggering an E_USER_DEPRECATED warning when used. Use `$message->getHeader()->parseParams()`. +* Several performance improvements to `Guzzle\Common\Collection` +* Added an `$options` argument to the end of the following methods of `Guzzle\Http\ClientInterface`: + createRequest, head, delete, put, patch, post, options, prepareRequest +* Added an `$options` argument to the end of `Guzzle\Http\Message\Request\RequestFactoryInterface::createRequest()` +* Added an `applyOptions()` method to `Guzzle\Http\Message\Request\RequestFactoryInterface` +* Changed `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)` to + `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $options = array())`. You can still pass in a + resource, string, or EntityBody into the $options parameter to specify the download location of the response. +* Changed `Guzzle\Common\Collection::__construct($data)` to no longer accepts a null value for `$data` but a + default `array()` +* Added `Guzzle\Stream\StreamInterface::isRepeatable` +* Removed `Guzzle\Http\ClientInterface::setDefaultHeaders(). Use + $client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. or + $client->getConfig()->setPath('request.options/headers', array('header_name' => 'value'))`. +* Removed `Guzzle\Http\ClientInterface::getDefaultHeaders(). Use $client->getConfig()->getPath('request.options/headers')`. +* Removed `Guzzle\Http\ClientInterface::expandTemplate()` +* Removed `Guzzle\Http\ClientInterface::setRequestFactory()` +* Removed `Guzzle\Http\ClientInterface::getCurlMulti()` +* Removed `Guzzle\Http\Message\RequestInterface::canCache` +* Removed `Guzzle\Http\Message\RequestInterface::setIsRedirect` +* Removed `Guzzle\Http\Message\RequestInterface::isRedirect` +* Made `Guzzle\Http\Client::expandTemplate` and `getUriTemplate` protected methods. +* You can now enable E_USER_DEPRECATED warnings to see if you are using a deprecated method by setting + `Guzzle\Common\Version::$emitWarnings` to true. +* Marked `Guzzle\Http\Message\Request::isResponseBodyRepeatable()` as deprecated. Use + `$request->getResponseBody()->isRepeatable()` instead. +* Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use + `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead. +* Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use + `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead. +* Marked `Guzzle\Http\Message\Request::setIsRedirect()` as deprecated. Use the HistoryPlugin instead. +* Marked `Guzzle\Http\Message\Request::isRedirect()` as deprecated. Use the HistoryPlugin instead. +* Marked `Guzzle\Cache\CacheAdapterFactory::factory()` as deprecated +* Marked 'command.headers', 'command.response_body' and 'command.on_complete' as deprecated for AbstractCommand. + These will work through Guzzle 4.0 +* Marked 'request.params' for `Guzzle\Http\Client` as deprecated. Use [request.options][params]. +* Marked `Guzzle\Service\Client::enableMagicMethods()` as deprecated. Magic methods can no longer be disabled on a Guzzle\Service\Client. +* Marked `Guzzle\Service\Client::getDefaultHeaders()` as deprecated. Use $client->getConfig()->getPath('request.options/headers')`. +* Marked `Guzzle\Service\Client::setDefaultHeaders()` as deprecated. Use $client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. +* Marked `Guzzle\Parser\Url\UrlParser` as deprecated. Just use PHP's `parse_url()` and percent encode your UTF-8. +* Marked `Guzzle\Common\Collection::inject()` as deprecated. +* Marked `Guzzle\Plugin\CurlAuth\CurlAuthPlugin` as deprecated. Use `$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest');` +* CacheKeyProviderInterface and DefaultCacheKeyProvider are no longer used. All of this logic is handled in a + CacheStorageInterface. These two objects and interface will be removed in a future version. +* Always setting X-cache headers on cached responses +* Default cache TTLs are now handled by the CacheStorageInterface of a CachePlugin +* `CacheStorageInterface::cache($key, Response $response, $ttl = null)` has changed to `cache(RequestInterface + $request, Response $response);` +* `CacheStorageInterface::fetch($key)` has changed to `fetch(RequestInterface $request);` +* `CacheStorageInterface::delete($key)` has changed to `delete(RequestInterface $request);` +* Added `CacheStorageInterface::purge($url)` +* `DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin + $plugin)` has changed to `DefaultRevalidation::__construct(CacheStorageInterface $cache, + CanCacheStrategyInterface $canCache = null)` +* Added `RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)` + +## 3.6.0 - 2013-05-29 + +* ServiceDescription now implements ToArrayInterface +* Added command.hidden_params to blacklist certain headers from being treated as additionalParameters +* Guzzle can now correctly parse incomplete URLs +* Mixed casing of headers are now forced to be a single consistent casing across all values for that header. +* Messages internally use a HeaderCollection object to delegate handling case-insensitive header resolution +* Removed the whole changedHeader() function system of messages because all header changes now go through addHeader(). +* Specific header implementations can be created for complex headers. When a message creates a header, it uses a + HeaderFactory which can map specific headers to specific header classes. There is now a Link header and + CacheControl header implementation. +* Removed from interface: Guzzle\Http\ClientInterface::setUriTemplate +* Removed from interface: Guzzle\Http\ClientInterface::setCurlMulti() +* Removed Guzzle\Http\Message\Request::receivedRequestHeader() and implemented this functionality in + Guzzle\Http\Curl\RequestMediator +* Removed the optional $asString parameter from MessageInterface::getHeader(). Just cast the header to a string. +* Removed the optional $tryChunkedTransfer option from Guzzle\Http\Message\EntityEnclosingRequestInterface +* Removed the $asObjects argument from Guzzle\Http\Message\MessageInterface::getHeaders() +* Removed Guzzle\Parser\ParserRegister::get(). Use getParser() +* Removed Guzzle\Parser\ParserRegister::set(). Use registerParser(). +* All response header helper functions return a string rather than mixing Header objects and strings inconsistently +* Removed cURL blacklist support. This is no longer necessary now that Expect, Accept, etc. are managed by Guzzle + directly via interfaces +* Removed the injecting of a request object onto a response object. The methods to get and set a request still exist + but are a no-op until removed. +* Most classes that used to require a `Guzzle\Service\Command\CommandInterface` typehint now request a + `Guzzle\Service\Command\ArrayCommandInterface`. +* Added `Guzzle\Http\Message\RequestInterface::startResponse()` to the RequestInterface to handle injecting a response + on a request while the request is still being transferred +* The ability to case-insensitively search for header values +* Guzzle\Http\Message\Header::hasExactHeader +* Guzzle\Http\Message\Header::raw. Use getAll() +* Deprecated cache control specific methods on Guzzle\Http\Message\AbstractMessage. Use the CacheControl header object + instead. +* `Guzzle\Service\Command\CommandInterface` now extends from ToArrayInterface and ArrayAccess +* Added the ability to cast Model objects to a string to view debug information. + +## 3.5.0 - 2013-05-13 + +* Bug: Fixed a regression so that request responses are parsed only once per oncomplete event rather than multiple times +* Bug: Better cleanup of one-time events across the board (when an event is meant to fire once, it will now remove + itself from the EventDispatcher) +* Bug: `Guzzle\Log\MessageFormatter` now properly writes "total_time" and "connect_time" values +* Bug: Cloning an EntityEnclosingRequest now clones the EntityBody too +* Bug: Fixed an undefined index error when parsing nested JSON responses with a sentAs parameter that reference a + non-existent key +* Bug: All __call() method arguments are now required (helps with mocking frameworks) +* Deprecating Response::getRequest() and now using a shallow clone of a request object to remove a circular reference + to help with refcount based garbage collection of resources created by sending a request +* Deprecating ZF1 cache and log adapters. These will be removed in the next major version. +* Deprecating `Response::getPreviousResponse()` (method signature still exists, but it's deprecated). Use the + HistoryPlugin for a history. +* Added a `responseBody` alias for the `response_body` location +* Refactored internals to no longer rely on Response::getRequest() +* HistoryPlugin can now be cast to a string +* HistoryPlugin now logs transactions rather than requests and responses to more accurately keep track of the requests + and responses that are sent over the wire +* Added `getEffectiveUrl()` and `getRedirectCount()` to Response objects + +## 3.4.3 - 2013-04-30 + +* Bug fix: Fixing bug introduced in 3.4.2 where redirect responses are duplicated on the final redirected response +* Added a check to re-extract the temp cacert bundle from the phar before sending each request + +## 3.4.2 - 2013-04-29 + +* Bug fix: Stream objects now work correctly with "a" and "a+" modes +* Bug fix: Removing `Transfer-Encoding: chunked` header when a Content-Length is present +* Bug fix: AsyncPlugin no longer forces HEAD requests +* Bug fix: DateTime timezones are now properly handled when using the service description schema formatter +* Bug fix: CachePlugin now properly handles stale-if-error directives when a request to the origin server fails +* Setting a response on a request will write to the custom request body from the response body if one is specified +* LogPlugin now writes to php://output when STDERR is undefined +* Added the ability to set multiple POST files for the same key in a single call +* application/x-www-form-urlencoded POSTs now use the utf-8 charset by default +* Added the ability to queue CurlExceptions to the MockPlugin +* Cleaned up how manual responses are queued on requests (removed "queued_response" and now using request.before_send) +* Configuration loading now allows remote files + +## 3.4.1 - 2013-04-16 + +* Large refactoring to how CurlMulti handles work. There is now a proxy that sits in front of a pool of CurlMulti + handles. This greatly simplifies the implementation, fixes a couple bugs, and provides a small performance boost. +* Exceptions are now properly grouped when sending requests in parallel +* Redirects are now properly aggregated when a multi transaction fails +* Redirects now set the response on the original object even in the event of a failure +* Bug fix: Model names are now properly set even when using $refs +* Added support for PHP 5.5's CurlFile to prevent warnings with the deprecated @ syntax +* Added support for oauth_callback in OAuth signatures +* Added support for oauth_verifier in OAuth signatures +* Added support to attempt to retrieve a command first literally, then ucfirst, the with inflection + +## 3.4.0 - 2013-04-11 + +* Bug fix: URLs are now resolved correctly based on http://tools.ietf.org/html/rfc3986#section-5.2. #289 +* Bug fix: Absolute URLs with a path in a service description will now properly override the base URL. #289 +* Bug fix: Parsing a query string with a single PHP array value will now result in an array. #263 +* Bug fix: Better normalization of the User-Agent header to prevent duplicate headers. #264. +* Bug fix: Added `number` type to service descriptions. +* Bug fix: empty parameters are removed from an OAuth signature +* Bug fix: Revalidating a cache entry prefers the Last-Modified over the Date header +* Bug fix: Fixed "array to string" error when validating a union of types in a service description +* Bug fix: Removed code that attempted to determine the size of a stream when data is written to the stream +* Bug fix: Not including an `oauth_token` if the value is null in the OauthPlugin. +* Bug fix: Now correctly aggregating successful requests and failed requests in CurlMulti when a redirect occurs. +* The new default CURLOPT_TIMEOUT setting has been increased to 150 seconds so that Guzzle works on poor connections. +* Added a feature to EntityEnclosingRequest::setBody() that will automatically set the Content-Type of the request if + the Content-Type can be determined based on the entity body or the path of the request. +* Added the ability to overwrite configuration settings in a client when grabbing a throwaway client from a builder. +* Added support for a PSR-3 LogAdapter. +* Added a `command.after_prepare` event +* Added `oauth_callback` parameter to the OauthPlugin +* Added the ability to create a custom stream class when using a stream factory +* Added a CachingEntityBody decorator +* Added support for `additionalParameters` in service descriptions to define how custom parameters are serialized. +* The bundled SSL certificate is now provided in the phar file and extracted when running Guzzle from a phar. +* You can now send any EntityEnclosingRequest with POST fields or POST files and cURL will handle creating bodies +* POST requests using a custom entity body are now treated exactly like PUT requests but with a custom cURL method. This + means that the redirect behavior of POST requests with custom bodies will not be the same as POST requests that use + POST fields or files (the latter is only used when emulating a form POST in the browser). +* Lots of cleanup to CurlHandle::factory and RequestFactory::createRequest + +## 3.3.1 - 2013-03-10 + +* Added the ability to create PHP streaming responses from HTTP requests +* Bug fix: Running any filters when parsing response headers with service descriptions +* Bug fix: OauthPlugin fixes to allow for multi-dimensional array signing, and sorting parameters before signing +* Bug fix: Removed the adding of default empty arrays and false Booleans to responses in order to be consistent across + response location visitors. +* Bug fix: Removed the possibility of creating configuration files with circular dependencies +* RequestFactory::create() now uses the key of a POST file when setting the POST file name +* Added xmlAllowEmpty to serialize an XML body even if no XML specific parameters are set + +## 3.3.0 - 2013-03-03 + +* A large number of performance optimizations have been made +* Bug fix: Added 'wb' as a valid write mode for streams +* Bug fix: `Guzzle\Http\Message\Response::json()` now allows scalar values to be returned +* Bug fix: Fixed bug in `Guzzle\Http\Message\Response` where wrapping quotes were stripped from `getEtag()` +* BC: Removed `Guzzle\Http\Utils` class +* BC: Setting a service description on a client will no longer modify the client's command factories. +* BC: Emitting IO events from a RequestMediator is now a parameter that must be set in a request's curl options using + the 'emit_io' key. This was previously set under a request's parameters using 'curl.emit_io' +* BC: `Guzzle\Stream\Stream::getWrapper()` and `Guzzle\Stream\Stream::getSteamType()` are no longer converted to + lowercase +* Operation parameter objects are now lazy loaded internally +* Added ErrorResponsePlugin that can throw errors for responses defined in service description operations' errorResponses +* Added support for instantiating responseType=class responseClass classes. Classes must implement + `Guzzle\Service\Command\ResponseClassInterface` +* Added support for additionalProperties for top-level parameters in responseType=model responseClasses. These + additional properties also support locations and can be used to parse JSON responses where the outermost part of the + JSON is an array +* Added support for nested renaming of JSON models (rename sentAs to name) +* CachePlugin + * Added support for stale-if-error so that the CachePlugin can now serve stale content from the cache on error + * Debug headers can now added to cached response in the CachePlugin + +## 3.2.0 - 2013-02-14 + +* CurlMulti is no longer reused globally. A new multi object is created per-client. This helps to isolate clients. +* URLs with no path no longer contain a "/" by default +* Guzzle\Http\QueryString does no longer manages the leading "?". This is now handled in Guzzle\Http\Url. +* BadResponseException no longer includes the full request and response message +* Adding setData() to Guzzle\Service\Description\ServiceDescriptionInterface +* Adding getResponseBody() to Guzzle\Http\Message\RequestInterface +* Various updates to classes to use ServiceDescriptionInterface type hints rather than ServiceDescription +* Header values can now be normalized into distinct values when multiple headers are combined with a comma separated list +* xmlEncoding can now be customized for the XML declaration of a XML service description operation +* Guzzle\Http\QueryString now uses Guzzle\Http\QueryAggregator\QueryAggregatorInterface objects to add custom value + aggregation and no longer uses callbacks +* The URL encoding implementation of Guzzle\Http\QueryString can now be customized +* Bug fix: Filters were not always invoked for array service description parameters +* Bug fix: Redirects now use a target response body rather than a temporary response body +* Bug fix: The default exponential backoff BackoffPlugin was not giving when the request threshold was exceeded +* Bug fix: Guzzle now takes the first found value when grabbing Cache-Control directives + +## 3.1.2 - 2013-01-27 + +* Refactored how operation responses are parsed. Visitors now include a before() method responsible for parsing the + response body. For example, the XmlVisitor now parses the XML response into an array in the before() method. +* Fixed an issue where cURL would not automatically decompress responses when the Accept-Encoding header was sent +* CURLOPT_SSL_VERIFYHOST is never set to 1 because it is deprecated (see 5e0ff2ef20f839e19d1eeb298f90ba3598784444) +* Fixed a bug where redirect responses were not chained correctly using getPreviousResponse() +* Setting default headers on a client after setting the user-agent will not erase the user-agent setting + +## 3.1.1 - 2013-01-20 + +* Adding wildcard support to Guzzle\Common\Collection::getPath() +* Adding alias support to ServiceBuilder configs +* Adding Guzzle\Service\Resource\CompositeResourceIteratorFactory and cleaning up factory interface + +## 3.1.0 - 2013-01-12 + +* BC: CurlException now extends from RequestException rather than BadResponseException +* BC: Renamed Guzzle\Plugin\Cache\CanCacheStrategyInterface::canCache() to canCacheRequest() and added CanCacheResponse() +* Added getData to ServiceDescriptionInterface +* Added context array to RequestInterface::setState() +* Bug: Removing hard dependency on the BackoffPlugin from Guzzle\Http +* Bug: Adding required content-type when JSON request visitor adds JSON to a command +* Bug: Fixing the serialization of a service description with custom data +* Made it easier to deal with exceptions thrown when transferring commands or requests in parallel by providing + an array of successful and failed responses +* Moved getPath from Guzzle\Service\Resource\Model to Guzzle\Common\Collection +* Added Guzzle\Http\IoEmittingEntityBody +* Moved command filtration from validators to location visitors +* Added `extends` attributes to service description parameters +* Added getModels to ServiceDescriptionInterface + +## 3.0.7 - 2012-12-19 + +* Fixing phar detection when forcing a cacert to system if null or true +* Allowing filename to be passed to `Guzzle\Http\Message\Request::setResponseBody()` +* Cleaning up `Guzzle\Common\Collection::inject` method +* Adding a response_body location to service descriptions + +## 3.0.6 - 2012-12-09 + +* CurlMulti performance improvements +* Adding setErrorResponses() to Operation +* composer.json tweaks + +## 3.0.5 - 2012-11-18 + +* Bug: Fixing an infinite recursion bug caused from revalidating with the CachePlugin +* Bug: Response body can now be a string containing "0" +* Bug: Using Guzzle inside of a phar uses system by default but now allows for a custom cacert +* Bug: QueryString::fromString now properly parses query string parameters that contain equal signs +* Added support for XML attributes in service description responses +* DefaultRequestSerializer now supports array URI parameter values for URI template expansion +* Added better mimetype guessing to requests and post files + +## 3.0.4 - 2012-11-11 + +* Bug: Fixed a bug when adding multiple cookies to a request to use the correct glue value +* Bug: Cookies can now be added that have a name, domain, or value set to "0" +* Bug: Using the system cacert bundle when using the Phar +* Added json and xml methods to Response to make it easier to parse JSON and XML response data into data structures +* Enhanced cookie jar de-duplication +* Added the ability to enable strict cookie jars that throw exceptions when invalid cookies are added +* Added setStream to StreamInterface to actually make it possible to implement custom rewind behavior for entity bodies +* Added the ability to create any sort of hash for a stream rather than just an MD5 hash + +## 3.0.3 - 2012-11-04 + +* Implementing redirects in PHP rather than cURL +* Added PECL URI template extension and using as default parser if available +* Bug: Fixed Content-Length parsing of Response factory +* Adding rewind() method to entity bodies and streams. Allows for custom rewinding of non-repeatable streams. +* Adding ToArrayInterface throughout library +* Fixing OauthPlugin to create unique nonce values per request + +## 3.0.2 - 2012-10-25 + +* Magic methods are enabled by default on clients +* Magic methods return the result of a command +* Service clients no longer require a base_url option in the factory +* Bug: Fixed an issue with URI templates where null template variables were being expanded + +## 3.0.1 - 2012-10-22 + +* Models can now be used like regular collection objects by calling filter, map, etc. +* Models no longer require a Parameter structure or initial data in the constructor +* Added a custom AppendIterator to get around a PHP bug with the `\AppendIterator` + +## 3.0.0 - 2012-10-15 + +* Rewrote service description format to be based on Swagger + * Now based on JSON schema + * Added nested input structures and nested response models + * Support for JSON and XML input and output models + * Renamed `commands` to `operations` + * Removed dot class notation + * Removed custom types +* Broke the project into smaller top-level namespaces to be more component friendly +* Removed support for XML configs and descriptions. Use arrays or JSON files. +* Removed the Validation component and Inspector +* Moved all cookie code to Guzzle\Plugin\Cookie +* Magic methods on a Guzzle\Service\Client now return the command un-executed. +* Calling getResult() or getResponse() on a command will lazily execute the command if needed. +* Now shipping with cURL's CA certs and using it by default +* Added previousResponse() method to response objects +* No longer sending Accept and Accept-Encoding headers on every request +* Only sending an Expect header by default when a payload is greater than 1MB +* Added/moved client options: + * curl.blacklist to curl.option.blacklist + * Added ssl.certificate_authority +* Added a Guzzle\Iterator component +* Moved plugins from Guzzle\Http\Plugin to Guzzle\Plugin +* Added a more robust backoff retry strategy (replaced the ExponentialBackoffPlugin) +* Added a more robust caching plugin +* Added setBody to response objects +* Updating LogPlugin to use a more flexible MessageFormatter +* Added a completely revamped build process +* Cleaning up Collection class and removing default values from the get method +* Fixed ZF2 cache adapters + +## 2.8.8 - 2012-10-15 + +* Bug: Fixed a cookie issue that caused dot prefixed domains to not match where popular browsers did + +## 2.8.7 - 2012-09-30 + +* Bug: Fixed config file aliases for JSON includes +* Bug: Fixed cookie bug on a request object by using CookieParser to parse cookies on requests +* Bug: Removing the path to a file when sending a Content-Disposition header on a POST upload +* Bug: Hardening request and response parsing to account for missing parts +* Bug: Fixed PEAR packaging +* Bug: Fixed Request::getInfo +* Bug: Fixed cases where CURLM_CALL_MULTI_PERFORM return codes were causing curl transactions to fail +* Adding the ability for the namespace Iterator factory to look in multiple directories +* Added more getters/setters/removers from service descriptions +* Added the ability to remove POST fields from OAuth signatures +* OAuth plugin now supports 2-legged OAuth + +## 2.8.6 - 2012-09-05 + +* Added the ability to modify and build service descriptions +* Added the use of visitors to apply parameters to locations in service descriptions using the dynamic command +* Added a `json` parameter location +* Now allowing dot notation for classes in the CacheAdapterFactory +* Using the union of two arrays rather than an array_merge when extending service builder services and service params +* Ensuring that a service is a string before doing strpos() checks on it when substituting services for references + in service builder config files. +* Services defined in two different config files that include one another will by default replace the previously + defined service, but you can now create services that extend themselves and merge their settings over the previous +* The JsonLoader now supports aliasing filenames with different filenames. This allows you to alias something like + '_default' with a default JSON configuration file. + +## 2.8.5 - 2012-08-29 + +* Bug: Suppressed empty arrays from URI templates +* Bug: Added the missing $options argument from ServiceDescription::factory to enable caching +* Added support for HTTP responses that do not contain a reason phrase in the start-line +* AbstractCommand commands are now invokable +* Added a way to get the data used when signing an Oauth request before a request is sent + +## 2.8.4 - 2012-08-15 + +* Bug: Custom delay time calculations are no longer ignored in the ExponentialBackoffPlugin +* Added the ability to transfer entity bodies as a string rather than streamed. This gets around curl error 65. Set `body_as_string` in a request's curl options to enable. +* Added a StreamInterface, EntityBodyInterface, and added ftell() to Guzzle\Common\Stream +* Added an AbstractEntityBodyDecorator and a ReadLimitEntityBody decorator to transfer only a subset of a decorated stream +* Stream and EntityBody objects will now return the file position to the previous position after a read required operation (e.g. getContentMd5()) +* Added additional response status codes +* Removed SSL information from the default User-Agent header +* DELETE requests can now send an entity body +* Added an EventDispatcher to the ExponentialBackoffPlugin and added an ExponentialBackoffLogger to log backoff retries +* Added the ability of the MockPlugin to consume mocked request bodies +* LogPlugin now exposes request and response objects in the extras array + +## 2.8.3 - 2012-07-30 + +* Bug: Fixed a case where empty POST requests were sent as GET requests +* Bug: Fixed a bug in ExponentialBackoffPlugin that caused fatal errors when retrying an EntityEnclosingRequest that does not have a body +* Bug: Setting the response body of a request to null after completing a request, not when setting the state of a request to new +* Added multiple inheritance to service description commands +* Added an ApiCommandInterface and added `getParamNames()` and `hasParam()` +* Removed the default 2mb size cutoff from the Md5ValidatorPlugin so that it now defaults to validating everything +* Changed CurlMulti::perform to pass a smaller timeout to CurlMulti::executeHandles + +## 2.8.2 - 2012-07-24 + +* Bug: Query string values set to 0 are no longer dropped from the query string +* Bug: A Collection object is no longer created each time a call is made to `Guzzle\Service\Command\AbstractCommand::getRequestHeaders()` +* Bug: `+` is now treated as an encoded space when parsing query strings +* QueryString and Collection performance improvements +* Allowing dot notation for class paths in filters attribute of a service descriptions + +## 2.8.1 - 2012-07-16 + +* Loosening Event Dispatcher dependency +* POST redirects can now be customized using CURLOPT_POSTREDIR + +## 2.8.0 - 2012-07-15 + +* BC: Guzzle\Http\Query + * Query strings with empty variables will always show an equal sign unless the variable is set to QueryString::BLANK (e.g. ?acl= vs ?acl) + * Changed isEncodingValues() and isEncodingFields() to isUrlEncoding() + * Changed setEncodeValues(bool) and setEncodeFields(bool) to useUrlEncoding(bool) + * Changed the aggregation functions of QueryString to be static methods + * Can now use fromString() with querystrings that have a leading ? +* cURL configuration values can be specified in service descriptions using `curl.` prefixed parameters +* Content-Length is set to 0 before emitting the request.before_send event when sending an empty request body +* Cookies are no longer URL decoded by default +* Bug: URI template variables set to null are no longer expanded + +## 2.7.2 - 2012-07-02 + +* BC: Moving things to get ready for subtree splits. Moving Inflection into Common. Moving Guzzle\Http\Parser to Guzzle\Parser. +* BC: Removing Guzzle\Common\Batch\Batch::count() and replacing it with isEmpty() +* CachePlugin now allows for a custom request parameter function to check if a request can be cached +* Bug fix: CachePlugin now only caches GET and HEAD requests by default +* Bug fix: Using header glue when transferring headers over the wire +* Allowing deeply nested arrays for composite variables in URI templates +* Batch divisors can now return iterators or arrays + +## 2.7.1 - 2012-06-26 + +* Minor patch to update version number in UA string +* Updating build process + +## 2.7.0 - 2012-06-25 + +* BC: Inflection classes moved to Guzzle\Inflection. No longer static methods. Can now inject custom inflectors into classes. +* BC: Removed magic setX methods from commands +* BC: Magic methods mapped to service description commands are now inflected in the command factory rather than the client __call() method +* Verbose cURL options are no longer enabled by default. Set curl.debug to true on a client to enable. +* Bug: Now allowing colons in a response start-line (e.g. HTTP/1.1 503 Service Unavailable: Back-end server is at capacity) +* Guzzle\Service\Resource\ResourceIteratorApplyBatched now internally uses the Guzzle\Common\Batch namespace +* Added Guzzle\Service\Plugin namespace and a PluginCollectionPlugin +* Added the ability to set POST fields and files in a service description +* Guzzle\Http\EntityBody::factory() now accepts objects with a __toString() method +* Adding a command.before_prepare event to clients +* Added BatchClosureTransfer and BatchClosureDivisor +* BatchTransferException now includes references to the batch divisor and transfer strategies +* Fixed some tests so that they pass more reliably +* Added Guzzle\Common\Log\ArrayLogAdapter + +## 2.6.6 - 2012-06-10 + +* BC: Removing Guzzle\Http\Plugin\BatchQueuePlugin +* BC: Removing Guzzle\Service\Command\CommandSet +* Adding generic batching system (replaces the batch queue plugin and command set) +* Updating ZF cache and log adapters and now using ZF's composer repository +* Bug: Setting the name of each ApiParam when creating through an ApiCommand +* Adding result_type, result_doc, deprecated, and doc_url to service descriptions +* Bug: Changed the default cookie header casing back to 'Cookie' + +## 2.6.5 - 2012-06-03 + +* BC: Renaming Guzzle\Http\Message\RequestInterface::getResourceUri() to getResource() +* BC: Removing unused AUTH_BASIC and AUTH_DIGEST constants from +* BC: Guzzle\Http\Cookie is now used to manage Set-Cookie data, not Cookie data +* BC: Renaming methods in the CookieJarInterface +* Moving almost all cookie logic out of the CookiePlugin and into the Cookie or CookieJar implementations +* Making the default glue for HTTP headers ';' instead of ',' +* Adding a removeValue to Guzzle\Http\Message\Header +* Adding getCookies() to request interface. +* Making it easier to add event subscribers to HasDispatcherInterface classes. Can now directly call addSubscriber() + +## 2.6.4 - 2012-05-30 + +* BC: Cleaning up how POST files are stored in EntityEnclosingRequest objects. Adding PostFile class. +* BC: Moving ApiCommand specific functionality from the Inspector and on to the ApiCommand +* Bug: Fixing magic method command calls on clients +* Bug: Email constraint only validates strings +* Bug: Aggregate POST fields when POST files are present in curl handle +* Bug: Fixing default User-Agent header +* Bug: Only appending or prepending parameters in commands if they are specified +* Bug: Not requiring response reason phrases or status codes to match a predefined list of codes +* Allowing the use of dot notation for class namespaces when using instance_of constraint +* Added any_match validation constraint +* Added an AsyncPlugin +* Passing request object to the calculateWait method of the ExponentialBackoffPlugin +* Allowing the result of a command object to be changed +* Parsing location and type sub values when instantiating a service description rather than over and over at runtime + +## 2.6.3 - 2012-05-23 + +* [BC] Guzzle\Common\FromConfigInterface no longer requires any config options. +* [BC] Refactoring how POST files are stored on an EntityEnclosingRequest. They are now separate from POST fields. +* You can now use an array of data when creating PUT request bodies in the request factory. +* Removing the requirement that HTTPS requests needed a Cache-Control: public directive to be cacheable. +* [Http] Adding support for Content-Type in multipart POST uploads per upload +* [Http] Added support for uploading multiple files using the same name (foo[0], foo[1]) +* Adding more POST data operations for easier manipulation of POST data. +* You can now set empty POST fields. +* The body of a request is only shown on EntityEnclosingRequest objects that do not use POST files. +* Split the Guzzle\Service\Inspector::validateConfig method into two methods. One to initialize when a command is created, and one to validate. +* CS updates + +## 2.6.2 - 2012-05-19 + +* [Http] Better handling of nested scope requests in CurlMulti. Requests are now always prepares in the send() method rather than the addRequest() method. + +## 2.6.1 - 2012-05-19 + +* [BC] Removing 'path' support in service descriptions. Use 'uri'. +* [BC] Guzzle\Service\Inspector::parseDocBlock is now protected. Adding getApiParamsForClass() with cache. +* [BC] Removing Guzzle\Common\NullObject. Use https://github.com/mtdowling/NullObject if you need it. +* [BC] Removing Guzzle\Common\XmlElement. +* All commands, both dynamic and concrete, have ApiCommand objects. +* Adding a fix for CurlMulti so that if all of the connections encounter some sort of curl error, then the loop exits. +* Adding checks to EntityEnclosingRequest so that empty POST files and fields are ignored. +* Making the method signature of Guzzle\Service\Builder\ServiceBuilder::factory more flexible. + +## 2.6.0 - 2012-05-15 + +* [BC] Moving Guzzle\Service\Builder to Guzzle\Service\Builder\ServiceBuilder +* [BC] Executing a Command returns the result of the command rather than the command +* [BC] Moving all HTTP parsing logic to Guzzle\Http\Parsers. Allows for faster C implementations if needed. +* [BC] Changing the Guzzle\Http\Message\Response::setProtocol() method to accept a protocol and version in separate args. +* [BC] Moving ResourceIterator* to Guzzle\Service\Resource +* [BC] Completely refactored ResourceIterators to iterate over a cloned command object +* [BC] Moved Guzzle\Http\UriTemplate to Guzzle\Http\Parser\UriTemplate\UriTemplate +* [BC] Guzzle\Guzzle is now deprecated +* Moving Guzzle\Common\Guzzle::inject to Guzzle\Common\Collection::inject +* Adding Guzzle\Version class to give version information about Guzzle +* Adding Guzzle\Http\Utils class to provide getDefaultUserAgent() and getHttpDate() +* Adding Guzzle\Curl\CurlVersion to manage caching curl_version() data +* ServiceDescription and ServiceBuilder are now cacheable using similar configs +* Changing the format of XML and JSON service builder configs. Backwards compatible. +* Cleaned up Cookie parsing +* Trimming the default Guzzle User-Agent header +* Adding a setOnComplete() method to Commands that is called when a command completes +* Keeping track of requests that were mocked in the MockPlugin +* Fixed a caching bug in the CacheAdapterFactory +* Inspector objects can be injected into a Command object +* Refactoring a lot of code and tests to be case insensitive when dealing with headers +* Adding Guzzle\Http\Message\HeaderComparison for easy comparison of HTTP headers using a DSL +* Adding the ability to set global option overrides to service builder configs +* Adding the ability to include other service builder config files from within XML and JSON files +* Moving the parseQuery method out of Url and on to QueryString::fromString() as a static factory method. + +## 2.5.0 - 2012-05-08 + +* Major performance improvements +* [BC] Simplifying Guzzle\Common\Collection. Please check to see if you are using features that are now deprecated. +* [BC] Using a custom validation system that allows a flyweight implementation for much faster validation. No longer using Symfony2 Validation component. +* [BC] No longer supporting "{{ }}" for injecting into command or UriTemplates. Use "{}" +* Added the ability to passed parameters to all requests created by a client +* Added callback functionality to the ExponentialBackoffPlugin +* Using microtime in ExponentialBackoffPlugin to allow more granular backoff strategies. +* Rewinding request stream bodies when retrying requests +* Exception is thrown when JSON response body cannot be decoded +* Added configurable magic method calls to clients and commands. This is off by default. +* Fixed a defect that added a hash to every parsed URL part +* Fixed duplicate none generation for OauthPlugin. +* Emitting an event each time a client is generated by a ServiceBuilder +* Using an ApiParams object instead of a Collection for parameters of an ApiCommand +* cache.* request parameters should be renamed to params.cache.* +* Added the ability to set arbitrary curl options on requests (disable_wire, progress, etc.). See CurlHandle. +* Added the ability to disable type validation of service descriptions +* ServiceDescriptions and ServiceBuilders are now Serializable diff --git a/vendor/guzzlehttp/guzzle/Dockerfile b/vendor/guzzlehttp/guzzle/Dockerfile new file mode 100644 index 0000000..f6a0952 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/Dockerfile @@ -0,0 +1,18 @@ +FROM composer:latest as setup + +RUN mkdir /guzzle + +WORKDIR /guzzle + +RUN set -xe \ + && composer init --name=guzzlehttp/test --description="Simple project for testing Guzzle scripts" --author="Márk Sági-Kazár " --no-interaction \ + && composer require guzzlehttp/guzzle + + +FROM php:7.3 + +RUN mkdir /guzzle + +WORKDIR /guzzle + +COPY --from=setup /guzzle /guzzle diff --git a/vendor/guzzlehttp/guzzle/LICENSE b/vendor/guzzlehttp/guzzle/LICENSE new file mode 100644 index 0000000..fd2375d --- /dev/null +++ b/vendor/guzzlehttp/guzzle/LICENSE @@ -0,0 +1,27 @@ +The MIT License (MIT) + +Copyright (c) 2011 Michael Dowling +Copyright (c) 2012 Jeremy Lindblom +Copyright (c) 2014 Graham Campbell +Copyright (c) 2015 Márk Sági-Kazár +Copyright (c) 2015 Tobias Schultze +Copyright (c) 2016 Tobias Nyholm +Copyright (c) 2016 George Mponos + +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/guzzlehttp/guzzle/README.md b/vendor/guzzlehttp/guzzle/README.md new file mode 100644 index 0000000..00d2066 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/README.md @@ -0,0 +1,93 @@ +Guzzle, PHP HTTP client +======================= + +[![Latest Version](https://img.shields.io/github/release/guzzle/guzzle.svg?style=flat-square)](https://github.com/guzzle/guzzle/releases) +[![Build Status](https://img.shields.io/github/workflow/status/guzzle/guzzle/CI?label=ci%20build&style=flat-square)](https://github.com/guzzle/guzzle/actions?query=workflow%3ACI) +[![Total Downloads](https://img.shields.io/packagist/dt/guzzlehttp/guzzle.svg?style=flat-square)](https://packagist.org/packages/guzzlehttp/guzzle) + +Guzzle is a PHP HTTP client that makes it easy to send HTTP requests and +trivial to integrate with web services. + +- Simple interface for building query strings, POST requests, streaming large + uploads, streaming large downloads, using HTTP cookies, uploading JSON data, + etc... +- Can send both synchronous and asynchronous requests using the same interface. +- Uses PSR-7 interfaces for requests, responses, and streams. This allows you + to utilize other PSR-7 compatible libraries with Guzzle. +- Abstracts away the underlying HTTP transport, allowing you to write + environment and transport agnostic code; i.e., no hard dependency on cURL, + PHP streams, sockets, or non-blocking event loops. +- Middleware system allows you to augment and compose client behavior. + +```php +$client = new \GuzzleHttp\Client(); +$response = $client->request('GET', 'https://api.github.com/repos/guzzle/guzzle'); + +echo $response->getStatusCode(); # 200 +echo $response->getHeaderLine('content-type'); # 'application/json; charset=utf8' +echo $response->getBody(); # '{"id": 1420053, "name": "guzzle", ...}' + +# Send an asynchronous request. +$request = new \GuzzleHttp\Psr7\Request('GET', 'http://httpbin.org'); +$promise = $client->sendAsync($request)->then(function ($response) { + echo 'I completed! ' . $response->getBody(); +}); + +$promise->wait(); +``` + +## Help and docs + +- [Documentation](http://guzzlephp.org/) +- [Stack Overflow](http://stackoverflow.com/questions/tagged/guzzle) +- [Gitter](https://gitter.im/guzzle/guzzle) + + +## Installing Guzzle + +The recommended way to install Guzzle is through +[Composer](http://getcomposer.org). + +```bash +# Install Composer +curl -sS https://getcomposer.org/installer | php +``` + +Next, run the Composer command to install the latest stable version of Guzzle: + +```bash +composer require guzzlehttp/guzzle +``` + +After installing, you need to require Composer's autoloader: + +```php +require 'vendor/autoload.php'; +``` + +You can then later update Guzzle using composer: + + ```bash +composer update + ``` + + +## Version Guidance + +| Version | Status | Packagist | Namespace | Repo | Docs | PSR-7 | PHP Version | +|---------|----------------|---------------------|--------------|---------------------|---------------------|-------|--------------| +| 3.x | EOL | `guzzle/guzzle` | `Guzzle` | [v3][guzzle-3-repo] | [v3][guzzle-3-docs] | No | >=5.3.3,<7.0 | +| 4.x | EOL | `guzzlehttp/guzzle` | `GuzzleHttp` | [v4][guzzle-4-repo] | N/A | No | >=5.4,<7.0 | +| 5.x | EOL | `guzzlehttp/guzzle` | `GuzzleHttp` | [v5][guzzle-5-repo] | [v5][guzzle-5-docs] | No | >=5.4,<7.4 | +| 6.x | Security fixes | `guzzlehttp/guzzle` | `GuzzleHttp` | [v6][guzzle-6-repo] | [v6][guzzle-6-docs] | Yes | >=5.5,<8.0 | +| 7.x | Latest | `guzzlehttp/guzzle` | `GuzzleHttp` | [v7][guzzle-7-repo] | [v7][guzzle-7-docs] | Yes | >=7.2.5,<8.2 | + +[guzzle-3-repo]: https://github.com/guzzle/guzzle3 +[guzzle-4-repo]: https://github.com/guzzle/guzzle/tree/4.x +[guzzle-5-repo]: https://github.com/guzzle/guzzle/tree/5.3 +[guzzle-6-repo]: https://github.com/guzzle/guzzle/tree/6.5 +[guzzle-7-repo]: https://github.com/guzzle/guzzle +[guzzle-3-docs]: http://guzzle3.readthedocs.org +[guzzle-5-docs]: http://docs.guzzlephp.org/en/5.3/ +[guzzle-6-docs]: http://docs.guzzlephp.org/en/6.5/ +[guzzle-7-docs]: http://docs.guzzlephp.org/en/latest/ diff --git a/vendor/guzzlehttp/guzzle/UPGRADING.md b/vendor/guzzlehttp/guzzle/UPGRADING.md new file mode 100644 index 0000000..91d1dcc --- /dev/null +++ b/vendor/guzzlehttp/guzzle/UPGRADING.md @@ -0,0 +1,1203 @@ +Guzzle Upgrade Guide +==================== + +5.0 to 6.0 +---------- + +Guzzle now uses [PSR-7](http://www.php-fig.org/psr/psr-7/) for HTTP messages. +Due to the fact that these messages are immutable, this prompted a refactoring +of Guzzle to use a middleware based system rather than an event system. Any +HTTP message interaction (e.g., `GuzzleHttp\Message\Request`) need to be +updated to work with the new immutable PSR-7 request and response objects. Any +event listeners or subscribers need to be updated to become middleware +functions that wrap handlers (or are injected into a +`GuzzleHttp\HandlerStack`). + +- Removed `GuzzleHttp\BatchResults` +- Removed `GuzzleHttp\Collection` +- Removed `GuzzleHttp\HasDataTrait` +- Removed `GuzzleHttp\ToArrayInterface` +- The `guzzlehttp/streams` dependency has been removed. Stream functionality + is now present in the `GuzzleHttp\Psr7` namespace provided by the + `guzzlehttp/psr7` package. +- Guzzle no longer uses ReactPHP promises and now uses the + `guzzlehttp/promises` library. We use a custom promise library for three + significant reasons: + 1. React promises (at the time of writing this) are recursive. Promise + chaining and promise resolution will eventually blow the stack. Guzzle + promises are not recursive as they use a sort of trampolining technique. + Note: there has been movement in the React project to modify promises to + no longer utilize recursion. + 2. Guzzle needs to have the ability to synchronously block on a promise to + wait for a result. Guzzle promises allows this functionality (and does + not require the use of recursion). + 3. Because we need to be able to wait on a result, doing so using React + promises requires wrapping react promises with RingPHP futures. This + overhead is no longer needed, reducing stack sizes, reducing complexity, + and improving performance. +- `GuzzleHttp\Mimetypes` has been moved to a function in + `GuzzleHttp\Psr7\mimetype_from_extension` and + `GuzzleHttp\Psr7\mimetype_from_filename`. +- `GuzzleHttp\Query` and `GuzzleHttp\QueryParser` have been removed. Query + strings must now be passed into request objects as strings, or provided to + the `query` request option when creating requests with clients. The `query` + option uses PHP's `http_build_query` to convert an array to a string. If you + need a different serialization technique, you will need to pass the query + string in as a string. There are a couple helper functions that will make + working with query strings easier: `GuzzleHttp\Psr7\parse_query` and + `GuzzleHttp\Psr7\build_query`. +- Guzzle no longer has a dependency on RingPHP. Due to the use of a middleware + system based on PSR-7, using RingPHP and it's middleware system as well adds + more complexity than the benefits it provides. All HTTP handlers that were + present in RingPHP have been modified to work directly with PSR-7 messages + and placed in the `GuzzleHttp\Handler` namespace. This significantly reduces + complexity in Guzzle, removes a dependency, and improves performance. RingPHP + will be maintained for Guzzle 5 support, but will no longer be a part of + Guzzle 6. +- As Guzzle now uses a middleware based systems the event system and RingPHP + integration has been removed. Note: while the event system has been removed, + it is possible to add your own type of event system that is powered by the + middleware system. + - Removed the `Event` namespace. + - Removed the `Subscriber` namespace. + - Removed `Transaction` class + - Removed `RequestFsm` + - Removed `RingBridge` + - `GuzzleHttp\Subscriber\Cookie` is now provided by + `GuzzleHttp\Middleware::cookies` + - `GuzzleHttp\Subscriber\HttpError` is now provided by + `GuzzleHttp\Middleware::httpError` + - `GuzzleHttp\Subscriber\History` is now provided by + `GuzzleHttp\Middleware::history` + - `GuzzleHttp\Subscriber\Mock` is now provided by + `GuzzleHttp\Handler\MockHandler` + - `GuzzleHttp\Subscriber\Prepare` is now provided by + `GuzzleHttp\PrepareBodyMiddleware` + - `GuzzleHttp\Subscriber\Redirect` is now provided by + `GuzzleHttp\RedirectMiddleware` +- Guzzle now uses `Psr\Http\Message\UriInterface` (implements in + `GuzzleHttp\Psr7\Uri`) for URI support. `GuzzleHttp\Url` is now gone. +- Static functions in `GuzzleHttp\Utils` have been moved to namespaced + functions under the `GuzzleHttp` namespace. This requires either a Composer + based autoloader or you to include functions.php. +- `GuzzleHttp\ClientInterface::getDefaultOption` has been renamed to + `GuzzleHttp\ClientInterface::getConfig`. +- `GuzzleHttp\ClientInterface::setDefaultOption` has been removed. +- The `json` and `xml` methods of response objects has been removed. With the + migration to strictly adhering to PSR-7 as the interface for Guzzle messages, + adding methods to message interfaces would actually require Guzzle messages + to extend from PSR-7 messages rather then work with them directly. + +## Migrating to middleware + +The change to PSR-7 unfortunately required significant refactoring to Guzzle +due to the fact that PSR-7 messages are immutable. Guzzle 5 relied on an event +system from plugins. The event system relied on mutability of HTTP messages and +side effects in order to work. With immutable messages, you have to change your +workflow to become more about either returning a value (e.g., functional +middlewares) or setting a value on an object. Guzzle v6 has chosen the +functional middleware approach. + +Instead of using the event system to listen for things like the `before` event, +you now create a stack based middleware function that intercepts a request on +the way in and the promise of the response on the way out. This is a much +simpler and more predictable approach than the event system and works nicely +with PSR-7 middleware. Due to the use of promises, the middleware system is +also asynchronous. + +v5: + +```php +use GuzzleHttp\Event\BeforeEvent; +$client = new GuzzleHttp\Client(); +// Get the emitter and listen to the before event. +$client->getEmitter()->on('before', function (BeforeEvent $e) { + // Guzzle v5 events relied on mutation + $e->getRequest()->setHeader('X-Foo', 'Bar'); +}); +``` + +v6: + +In v6, you can modify the request before it is sent using the `mapRequest` +middleware. The idiomatic way in v6 to modify the request/response lifecycle is +to setup a handler middleware stack up front and inject the handler into a +client. + +```php +use GuzzleHttp\Middleware; +// Create a handler stack that has all of the default middlewares attached +$handler = GuzzleHttp\HandlerStack::create(); +// Push the handler onto the handler stack +$handler->push(Middleware::mapRequest(function (RequestInterface $request) { + // Notice that we have to return a request object + return $request->withHeader('X-Foo', 'Bar'); +})); +// Inject the handler into the client +$client = new GuzzleHttp\Client(['handler' => $handler]); +``` + +## POST Requests + +This version added the [`form_params`](http://guzzle.readthedocs.org/en/latest/request-options.html#form_params) +and `multipart` request options. `form_params` is an associative array of +strings or array of strings and is used to serialize an +`application/x-www-form-urlencoded` POST request. The +[`multipart`](http://guzzle.readthedocs.org/en/latest/request-options.html#multipart) +option is now used to send a multipart/form-data POST request. + +`GuzzleHttp\Post\PostFile` has been removed. Use the `multipart` option to add +POST files to a multipart/form-data request. + +The `body` option no longer accepts an array to send POST requests. Please use +`multipart` or `form_params` instead. + +The `base_url` option has been renamed to `base_uri`. + +4.x to 5.0 +---------- + +## Rewritten Adapter Layer + +Guzzle now uses [RingPHP](http://ringphp.readthedocs.org/en/latest) to send +HTTP requests. The `adapter` option in a `GuzzleHttp\Client` constructor +is still supported, but it has now been renamed to `handler`. Instead of +passing a `GuzzleHttp\Adapter\AdapterInterface`, you must now pass a PHP +`callable` that follows the RingPHP specification. + +## Removed Fluent Interfaces + +[Fluent interfaces were removed](http://ocramius.github.io/blog/fluent-interfaces-are-evil) +from the following classes: + +- `GuzzleHttp\Collection` +- `GuzzleHttp\Url` +- `GuzzleHttp\Query` +- `GuzzleHttp\Post\PostBody` +- `GuzzleHttp\Cookie\SetCookie` + +## Removed functions.php + +Removed "functions.php", so that Guzzle is truly PSR-4 compliant. The following +functions can be used as replacements. + +- `GuzzleHttp\json_decode` -> `GuzzleHttp\Utils::jsonDecode` +- `GuzzleHttp\get_path` -> `GuzzleHttp\Utils::getPath` +- `GuzzleHttp\Utils::setPath` -> `GuzzleHttp\set_path` +- `GuzzleHttp\Pool::batch` -> `GuzzleHttp\batch`. This function is, however, + deprecated in favor of using `GuzzleHttp\Pool::batch()`. + +The "procedural" global client has been removed with no replacement (e.g., +`GuzzleHttp\get()`, `GuzzleHttp\post()`, etc.). Use a `GuzzleHttp\Client` +object as a replacement. + +## `throwImmediately` has been removed + +The concept of "throwImmediately" has been removed from exceptions and error +events. This control mechanism was used to stop a transfer of concurrent +requests from completing. This can now be handled by throwing the exception or +by cancelling a pool of requests or each outstanding future request +individually. + +## headers event has been removed + +Removed the "headers" event. This event was only useful for changing the +body a response once the headers of the response were known. You can implement +a similar behavior in a number of ways. One example might be to use a +FnStream that has access to the transaction being sent. For example, when the +first byte is written, you could check if the response headers match your +expectations, and if so, change the actual stream body that is being +written to. + +## Updates to HTTP Messages + +Removed the `asArray` parameter from +`GuzzleHttp\Message\MessageInterface::getHeader`. If you want to get a header +value as an array, then use the newly added `getHeaderAsArray()` method of +`MessageInterface`. This change makes the Guzzle interfaces compatible with +the PSR-7 interfaces. + +3.x to 4.0 +---------- + +## Overarching changes: + +- Now requires PHP 5.4 or greater. +- No longer requires cURL to send requests. +- Guzzle no longer wraps every exception it throws. Only exceptions that are + recoverable are now wrapped by Guzzle. +- Various namespaces have been removed or renamed. +- No longer requiring the Symfony EventDispatcher. A custom event dispatcher + based on the Symfony EventDispatcher is + now utilized in `GuzzleHttp\Event\EmitterInterface` (resulting in significant + speed and functionality improvements). + +Changes per Guzzle 3.x namespace are described below. + +## Batch + +The `Guzzle\Batch` namespace has been removed. This is best left to +third-parties to implement on top of Guzzle's core HTTP library. + +## Cache + +The `Guzzle\Cache` namespace has been removed. (Todo: No suitable replacement +has been implemented yet, but hoping to utilize a PSR cache interface). + +## Common + +- Removed all of the wrapped exceptions. It's better to use the standard PHP + library for unrecoverable exceptions. +- `FromConfigInterface` has been removed. +- `Guzzle\Common\Version` has been removed. The VERSION constant can be found + at `GuzzleHttp\ClientInterface::VERSION`. + +### Collection + +- `getAll` has been removed. Use `toArray` to convert a collection to an array. +- `inject` has been removed. +- `keySearch` has been removed. +- `getPath` no longer supports wildcard expressions. Use something better like + JMESPath for this. +- `setPath` now supports appending to an existing array via the `[]` notation. + +### Events + +Guzzle no longer requires Symfony's EventDispatcher component. Guzzle now uses +`GuzzleHttp\Event\Emitter`. + +- `Symfony\Component\EventDispatcher\EventDispatcherInterface` is replaced by + `GuzzleHttp\Event\EmitterInterface`. +- `Symfony\Component\EventDispatcher\EventDispatcher` is replaced by + `GuzzleHttp\Event\Emitter`. +- `Symfony\Component\EventDispatcher\Event` is replaced by + `GuzzleHttp\Event\Event`, and Guzzle now has an EventInterface in + `GuzzleHttp\Event\EventInterface`. +- `AbstractHasDispatcher` has moved to a trait, `HasEmitterTrait`, and + `HasDispatcherInterface` has moved to `HasEmitterInterface`. Retrieving the + event emitter of a request, client, etc. now uses the `getEmitter` method + rather than the `getDispatcher` method. + +#### Emitter + +- Use the `once()` method to add a listener that automatically removes itself + the first time it is invoked. +- Use the `listeners()` method to retrieve a list of event listeners rather than + the `getListeners()` method. +- Use `emit()` instead of `dispatch()` to emit an event from an emitter. +- Use `attach()` instead of `addSubscriber()` and `detach()` instead of + `removeSubscriber()`. + +```php +$mock = new Mock(); +// 3.x +$request->getEventDispatcher()->addSubscriber($mock); +$request->getEventDispatcher()->removeSubscriber($mock); +// 4.x +$request->getEmitter()->attach($mock); +$request->getEmitter()->detach($mock); +``` + +Use the `on()` method to add a listener rather than the `addListener()` method. + +```php +// 3.x +$request->getEventDispatcher()->addListener('foo', function (Event $event) { /* ... */ } ); +// 4.x +$request->getEmitter()->on('foo', function (Event $event, $name) { /* ... */ } ); +``` + +## Http + +### General changes + +- The cacert.pem certificate has been moved to `src/cacert.pem`. +- Added the concept of adapters that are used to transfer requests over the + wire. +- Simplified the event system. +- Sending requests in parallel is still possible, but batching is no longer a + concept of the HTTP layer. Instead, you must use the `complete` and `error` + events to asynchronously manage parallel request transfers. +- `Guzzle\Http\Url` has moved to `GuzzleHttp\Url`. +- `Guzzle\Http\QueryString` has moved to `GuzzleHttp\Query`. +- QueryAggregators have been rewritten so that they are simply callable + functions. +- `GuzzleHttp\StaticClient` has been removed. Use the functions provided in + `functions.php` for an easy to use static client instance. +- Exceptions in `GuzzleHttp\Exception` have been updated to all extend from + `GuzzleHttp\Exception\TransferException`. + +### Client + +Calling methods like `get()`, `post()`, `head()`, etc. no longer create and +return a request, but rather creates a request, sends the request, and returns +the response. + +```php +// 3.0 +$request = $client->get('/'); +$response = $request->send(); + +// 4.0 +$response = $client->get('/'); + +// or, to mirror the previous behavior +$request = $client->createRequest('GET', '/'); +$response = $client->send($request); +``` + +`GuzzleHttp\ClientInterface` has changed. + +- The `send` method no longer accepts more than one request. Use `sendAll` to + send multiple requests in parallel. +- `setUserAgent()` has been removed. Use a default request option instead. You + could, for example, do something like: + `$client->setConfig('defaults/headers/User-Agent', 'Foo/Bar ' . $client::getDefaultUserAgent())`. +- `setSslVerification()` has been removed. Use default request options instead, + like `$client->setConfig('defaults/verify', true)`. + +`GuzzleHttp\Client` has changed. + +- The constructor now accepts only an associative array. You can include a + `base_url` string or array to use a URI template as the base URL of a client. + You can also specify a `defaults` key that is an associative array of default + request options. You can pass an `adapter` to use a custom adapter, + `batch_adapter` to use a custom adapter for sending requests in parallel, or + a `message_factory` to change the factory used to create HTTP requests and + responses. +- The client no longer emits a `client.create_request` event. +- Creating requests with a client no longer automatically utilize a URI + template. You must pass an array into a creational method (e.g., + `createRequest`, `get`, `put`, etc.) in order to expand a URI template. + +### Messages + +Messages no longer have references to their counterparts (i.e., a request no +longer has a reference to it's response, and a response no loger has a +reference to its request). This association is now managed through a +`GuzzleHttp\Adapter\TransactionInterface` object. You can get references to +these transaction objects using request events that are emitted over the +lifecycle of a request. + +#### Requests with a body + +- `GuzzleHttp\Message\EntityEnclosingRequest` and + `GuzzleHttp\Message\EntityEnclosingRequestInterface` have been removed. The + separation between requests that contain a body and requests that do not + contain a body has been removed, and now `GuzzleHttp\Message\RequestInterface` + handles both use cases. +- Any method that previously accepts a `GuzzleHttp\Response` object now accept a + `GuzzleHttp\Message\ResponseInterface`. +- `GuzzleHttp\Message\RequestFactoryInterface` has been renamed to + `GuzzleHttp\Message\MessageFactoryInterface`. This interface is used to create + both requests and responses and is implemented in + `GuzzleHttp\Message\MessageFactory`. +- POST field and file methods have been removed from the request object. You + must now use the methods made available to `GuzzleHttp\Post\PostBodyInterface` + to control the format of a POST body. Requests that are created using a + standard `GuzzleHttp\Message\MessageFactoryInterface` will automatically use + a `GuzzleHttp\Post\PostBody` body if the body was passed as an array or if + the method is POST and no body is provided. + +```php +$request = $client->createRequest('POST', '/'); +$request->getBody()->setField('foo', 'bar'); +$request->getBody()->addFile(new PostFile('file_key', fopen('/path/to/content', 'r'))); +``` + +#### Headers + +- `GuzzleHttp\Message\Header` has been removed. Header values are now simply + represented by an array of values or as a string. Header values are returned + as a string by default when retrieving a header value from a message. You can + pass an optional argument of `true` to retrieve a header value as an array + of strings instead of a single concatenated string. +- `GuzzleHttp\PostFile` and `GuzzleHttp\PostFileInterface` have been moved to + `GuzzleHttp\Post`. This interface has been simplified and now allows the + addition of arbitrary headers. +- Custom headers like `GuzzleHttp\Message\Header\Link` have been removed. Most + of the custom headers are now handled separately in specific + subscribers/plugins, and `GuzzleHttp\Message\HeaderValues::parseParams()` has + been updated to properly handle headers that contain parameters (like the + `Link` header). + +#### Responses + +- `GuzzleHttp\Message\Response::getInfo()` and + `GuzzleHttp\Message\Response::setInfo()` have been removed. Use the event + system to retrieve this type of information. +- `GuzzleHttp\Message\Response::getRawHeaders()` has been removed. +- `GuzzleHttp\Message\Response::getMessage()` has been removed. +- `GuzzleHttp\Message\Response::calculateAge()` and other cache specific + methods have moved to the CacheSubscriber. +- Header specific helper functions like `getContentMd5()` have been removed. + Just use `getHeader('Content-MD5')` instead. +- `GuzzleHttp\Message\Response::setRequest()` and + `GuzzleHttp\Message\Response::getRequest()` have been removed. Use the event + system to work with request and response objects as a transaction. +- `GuzzleHttp\Message\Response::getRedirectCount()` has been removed. Use the + Redirect subscriber instead. +- `GuzzleHttp\Message\Response::isSuccessful()` and other related methods have + been removed. Use `getStatusCode()` instead. + +#### Streaming responses + +Streaming requests can now be created by a client directly, returning a +`GuzzleHttp\Message\ResponseInterface` object that contains a body stream +referencing an open PHP HTTP stream. + +```php +// 3.0 +use Guzzle\Stream\PhpStreamRequestFactory; +$request = $client->get('/'); +$factory = new PhpStreamRequestFactory(); +$stream = $factory->fromRequest($request); +$data = $stream->read(1024); + +// 4.0 +$response = $client->get('/', ['stream' => true]); +// Read some data off of the stream in the response body +$data = $response->getBody()->read(1024); +``` + +#### Redirects + +The `configureRedirects()` method has been removed in favor of a +`allow_redirects` request option. + +```php +// Standard redirects with a default of a max of 5 redirects +$request = $client->createRequest('GET', '/', ['allow_redirects' => true]); + +// Strict redirects with a custom number of redirects +$request = $client->createRequest('GET', '/', [ + 'allow_redirects' => ['max' => 5, 'strict' => true] +]); +``` + +#### EntityBody + +EntityBody interfaces and classes have been removed or moved to +`GuzzleHttp\Stream`. All classes and interfaces that once required +`GuzzleHttp\EntityBodyInterface` now require +`GuzzleHttp\Stream\StreamInterface`. Creating a new body for a request no +longer uses `GuzzleHttp\EntityBody::factory` but now uses +`GuzzleHttp\Stream\Stream::factory` or even better: +`GuzzleHttp\Stream\create()`. + +- `Guzzle\Http\EntityBodyInterface` is now `GuzzleHttp\Stream\StreamInterface` +- `Guzzle\Http\EntityBody` is now `GuzzleHttp\Stream\Stream` +- `Guzzle\Http\CachingEntityBody` is now `GuzzleHttp\Stream\CachingStream` +- `Guzzle\Http\ReadLimitEntityBody` is now `GuzzleHttp\Stream\LimitStream` +- `Guzzle\Http\IoEmittyinEntityBody` has been removed. + +#### Request lifecycle events + +Requests previously submitted a large number of requests. The number of events +emitted over the lifecycle of a request has been significantly reduced to make +it easier to understand how to extend the behavior of a request. All events +emitted during the lifecycle of a request now emit a custom +`GuzzleHttp\Event\EventInterface` object that contains context providing +methods and a way in which to modify the transaction at that specific point in +time (e.g., intercept the request and set a response on the transaction). + +- `request.before_send` has been renamed to `before` and now emits a + `GuzzleHttp\Event\BeforeEvent` +- `request.complete` has been renamed to `complete` and now emits a + `GuzzleHttp\Event\CompleteEvent`. +- `request.sent` has been removed. Use `complete`. +- `request.success` has been removed. Use `complete`. +- `error` is now an event that emits a `GuzzleHttp\Event\ErrorEvent`. +- `request.exception` has been removed. Use `error`. +- `request.receive.status_line` has been removed. +- `curl.callback.progress` has been removed. Use a custom `StreamInterface` to + maintain a status update. +- `curl.callback.write` has been removed. Use a custom `StreamInterface` to + intercept writes. +- `curl.callback.read` has been removed. Use a custom `StreamInterface` to + intercept reads. + +`headers` is a new event that is emitted after the response headers of a +request have been received before the body of the response is downloaded. This +event emits a `GuzzleHttp\Event\HeadersEvent`. + +You can intercept a request and inject a response using the `intercept()` event +of a `GuzzleHttp\Event\BeforeEvent`, `GuzzleHttp\Event\CompleteEvent`, and +`GuzzleHttp\Event\ErrorEvent` event. + +See: http://docs.guzzlephp.org/en/latest/events.html + +## Inflection + +The `Guzzle\Inflection` namespace has been removed. This is not a core concern +of Guzzle. + +## Iterator + +The `Guzzle\Iterator` namespace has been removed. + +- `Guzzle\Iterator\AppendIterator`, `Guzzle\Iterator\ChunkedIterator`, and + `Guzzle\Iterator\MethodProxyIterator` are nice, but not a core requirement of + Guzzle itself. +- `Guzzle\Iterator\FilterIterator` is no longer needed because an equivalent + class is shipped with PHP 5.4. +- `Guzzle\Iterator\MapIterator` is not really needed when using PHP 5.5 because + it's easier to just wrap an iterator in a generator that maps values. + +For a replacement of these iterators, see https://github.com/nikic/iter + +## Log + +The LogPlugin has moved to https://github.com/guzzle/log-subscriber. The +`Guzzle\Log` namespace has been removed. Guzzle now relies on +`Psr\Log\LoggerInterface` for all logging. The MessageFormatter class has been +moved to `GuzzleHttp\Subscriber\Log\Formatter`. + +## Parser + +The `Guzzle\Parser` namespace has been removed. This was previously used to +make it possible to plug in custom parsers for cookies, messages, URI +templates, and URLs; however, this level of complexity is not needed in Guzzle +so it has been removed. + +- Cookie: Cookie parsing logic has been moved to + `GuzzleHttp\Cookie\SetCookie::fromString`. +- Message: Message parsing logic for both requests and responses has been moved + to `GuzzleHttp\Message\MessageFactory::fromMessage`. Message parsing is only + used in debugging or deserializing messages, so it doesn't make sense for + Guzzle as a library to add this level of complexity to parsing messages. +- UriTemplate: URI template parsing has been moved to + `GuzzleHttp\UriTemplate`. The Guzzle library will automatically use the PECL + URI template library if it is installed. +- Url: URL parsing is now performed in `GuzzleHttp\Url::fromString` (previously + it was `Guzzle\Http\Url::factory()`). If custom URL parsing is necessary, + then developers are free to subclass `GuzzleHttp\Url`. + +## Plugin + +The `Guzzle\Plugin` namespace has been renamed to `GuzzleHttp\Subscriber`. +Several plugins are shipping with the core Guzzle library under this namespace. + +- `GuzzleHttp\Subscriber\Cookie`: Replaces the old CookiePlugin. Cookie jar + code has moved to `GuzzleHttp\Cookie`. +- `GuzzleHttp\Subscriber\History`: Replaces the old HistoryPlugin. +- `GuzzleHttp\Subscriber\HttpError`: Throws errors when a bad HTTP response is + received. +- `GuzzleHttp\Subscriber\Mock`: Replaces the old MockPlugin. +- `GuzzleHttp\Subscriber\Prepare`: Prepares the body of a request just before + sending. This subscriber is attached to all requests by default. +- `GuzzleHttp\Subscriber\Redirect`: Replaces the RedirectPlugin. + +The following plugins have been removed (third-parties are free to re-implement +these if needed): + +- `GuzzleHttp\Plugin\Async` has been removed. +- `GuzzleHttp\Plugin\CurlAuth` has been removed. +- `GuzzleHttp\Plugin\ErrorResponse\ErrorResponsePlugin` has been removed. This + functionality should instead be implemented with event listeners that occur + after normal response parsing occurs in the guzzle/command package. + +The following plugins are not part of the core Guzzle package, but are provided +in separate repositories: + +- `Guzzle\Http\Plugin\BackoffPlugin` has been rewritten to be much simpler + to build custom retry policies using simple functions rather than various + chained classes. See: https://github.com/guzzle/retry-subscriber +- `Guzzle\Http\Plugin\Cache\CachePlugin` has moved to + https://github.com/guzzle/cache-subscriber +- `Guzzle\Http\Plugin\Log\LogPlugin` has moved to + https://github.com/guzzle/log-subscriber +- `Guzzle\Http\Plugin\Md5\Md5Plugin` has moved to + https://github.com/guzzle/message-integrity-subscriber +- `Guzzle\Http\Plugin\Mock\MockPlugin` has moved to + `GuzzleHttp\Subscriber\MockSubscriber`. +- `Guzzle\Http\Plugin\Oauth\OauthPlugin` has moved to + https://github.com/guzzle/oauth-subscriber + +## Service + +The service description layer of Guzzle has moved into two separate packages: + +- http://github.com/guzzle/command Provides a high level abstraction over web + services by representing web service operations using commands. +- http://github.com/guzzle/guzzle-services Provides an implementation of + guzzle/command that provides request serialization and response parsing using + Guzzle service descriptions. + +## Stream + +Stream have moved to a separate package available at +https://github.com/guzzle/streams. + +`Guzzle\Stream\StreamInterface` has been given a large update to cleanly take +on the responsibilities of `Guzzle\Http\EntityBody` and +`Guzzle\Http\EntityBodyInterface` now that they have been removed. The number +of methods implemented by the `StreamInterface` has been drastically reduced to +allow developers to more easily extend and decorate stream behavior. + +## Removed methods from StreamInterface + +- `getStream` and `setStream` have been removed to better encapsulate streams. +- `getMetadata` and `setMetadata` have been removed in favor of + `GuzzleHttp\Stream\MetadataStreamInterface`. +- `getWrapper`, `getWrapperData`, `getStreamType`, and `getUri` have all been + removed. This data is accessible when + using streams that implement `GuzzleHttp\Stream\MetadataStreamInterface`. +- `rewind` has been removed. Use `seek(0)` for a similar behavior. + +## Renamed methods + +- `detachStream` has been renamed to `detach`. +- `feof` has been renamed to `eof`. +- `ftell` has been renamed to `tell`. +- `readLine` has moved from an instance method to a static class method of + `GuzzleHttp\Stream\Stream`. + +## Metadata streams + +`GuzzleHttp\Stream\MetadataStreamInterface` has been added to denote streams +that contain additional metadata accessible via `getMetadata()`. +`GuzzleHttp\Stream\StreamInterface::getMetadata` and +`GuzzleHttp\Stream\StreamInterface::setMetadata` have been removed. + +## StreamRequestFactory + +The entire concept of the StreamRequestFactory has been removed. The way this +was used in Guzzle 3 broke the actual interface of sending streaming requests +(instead of getting back a Response, you got a StreamInterface). Streaming +PHP requests are now implemented through the `GuzzleHttp\Adapter\StreamAdapter`. + +3.6 to 3.7 +---------- + +### Deprecations + +- You can now enable E_USER_DEPRECATED warnings to see if you are using any deprecated methods.: + +```php +\Guzzle\Common\Version::$emitWarnings = true; +``` + +The following APIs and options have been marked as deprecated: + +- Marked `Guzzle\Http\Message\Request::isResponseBodyRepeatable()` as deprecated. Use `$request->getResponseBody()->isRepeatable()` instead. +- Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead. +- Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead. +- Marked `Guzzle\Http\Message\Request::setIsRedirect()` as deprecated. Use the HistoryPlugin instead. +- Marked `Guzzle\Http\Message\Request::isRedirect()` as deprecated. Use the HistoryPlugin instead. +- Marked `Guzzle\Cache\CacheAdapterFactory::factory()` as deprecated +- Marked `Guzzle\Service\Client::enableMagicMethods()` as deprecated. Magic methods can no longer be disabled on a Guzzle\Service\Client. +- Marked `Guzzle\Parser\Url\UrlParser` as deprecated. Just use PHP's `parse_url()` and percent encode your UTF-8. +- Marked `Guzzle\Common\Collection::inject()` as deprecated. +- Marked `Guzzle\Plugin\CurlAuth\CurlAuthPlugin` as deprecated. Use + `$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest|NTLM|Any'));` or + `$client->setDefaultOption('auth', array('user', 'pass', 'Basic|Digest|NTLM|Any'));` + +3.7 introduces `request.options` as a parameter for a client configuration and as an optional argument to all creational +request methods. When paired with a client's configuration settings, these options allow you to specify default settings +for various aspects of a request. Because these options make other previous configuration options redundant, several +configuration options and methods of a client and AbstractCommand have been deprecated. + +- Marked `Guzzle\Service\Client::getDefaultHeaders()` as deprecated. Use `$client->getDefaultOption('headers')`. +- Marked `Guzzle\Service\Client::setDefaultHeaders()` as deprecated. Use `$client->setDefaultOption('headers/{header_name}', 'value')`. +- Marked 'request.params' for `Guzzle\Http\Client` as deprecated. Use `$client->setDefaultOption('params/{param_name}', 'value')` +- Marked 'command.headers', 'command.response_body' and 'command.on_complete' as deprecated for AbstractCommand. These will work through Guzzle 4.0 + + $command = $client->getCommand('foo', array( + 'command.headers' => array('Test' => '123'), + 'command.response_body' => '/path/to/file' + )); + + // Should be changed to: + + $command = $client->getCommand('foo', array( + 'command.request_options' => array( + 'headers' => array('Test' => '123'), + 'save_as' => '/path/to/file' + ) + )); + +### Interface changes + +Additions and changes (you will need to update any implementations or subclasses you may have created): + +- Added an `$options` argument to the end of the following methods of `Guzzle\Http\ClientInterface`: + createRequest, head, delete, put, patch, post, options, prepareRequest +- Added an `$options` argument to the end of `Guzzle\Http\Message\Request\RequestFactoryInterface::createRequest()` +- Added an `applyOptions()` method to `Guzzle\Http\Message\Request\RequestFactoryInterface` +- Changed `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)` to + `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $options = array())`. You can still pass in a + resource, string, or EntityBody into the $options parameter to specify the download location of the response. +- Changed `Guzzle\Common\Collection::__construct($data)` to no longer accepts a null value for `$data` but a + default `array()` +- Added `Guzzle\Stream\StreamInterface::isRepeatable` +- Made `Guzzle\Http\Client::expandTemplate` and `getUriTemplate` protected methods. + +The following methods were removed from interfaces. All of these methods are still available in the concrete classes +that implement them, but you should update your code to use alternative methods: + +- Removed `Guzzle\Http\ClientInterface::setDefaultHeaders(). Use + `$client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. or + `$client->getConfig()->setPath('request.options/headers', array('header_name' => 'value'))` or + `$client->setDefaultOption('headers/{header_name}', 'value')`. or + `$client->setDefaultOption('headers', array('header_name' => 'value'))`. +- Removed `Guzzle\Http\ClientInterface::getDefaultHeaders(). Use `$client->getConfig()->getPath('request.options/headers')`. +- Removed `Guzzle\Http\ClientInterface::expandTemplate()`. This is an implementation detail. +- Removed `Guzzle\Http\ClientInterface::setRequestFactory()`. This is an implementation detail. +- Removed `Guzzle\Http\ClientInterface::getCurlMulti()`. This is a very specific implementation detail. +- Removed `Guzzle\Http\Message\RequestInterface::canCache`. Use the CachePlugin. +- Removed `Guzzle\Http\Message\RequestInterface::setIsRedirect`. Use the HistoryPlugin. +- Removed `Guzzle\Http\Message\RequestInterface::isRedirect`. Use the HistoryPlugin. + +### Cache plugin breaking changes + +- CacheKeyProviderInterface and DefaultCacheKeyProvider are no longer used. All of this logic is handled in a + CacheStorageInterface. These two objects and interface will be removed in a future version. +- Always setting X-cache headers on cached responses +- Default cache TTLs are now handled by the CacheStorageInterface of a CachePlugin +- `CacheStorageInterface::cache($key, Response $response, $ttl = null)` has changed to `cache(RequestInterface + $request, Response $response);` +- `CacheStorageInterface::fetch($key)` has changed to `fetch(RequestInterface $request);` +- `CacheStorageInterface::delete($key)` has changed to `delete(RequestInterface $request);` +- Added `CacheStorageInterface::purge($url)` +- `DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin + $plugin)` has changed to `DefaultRevalidation::__construct(CacheStorageInterface $cache, + CanCacheStrategyInterface $canCache = null)` +- Added `RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)` + +3.5 to 3.6 +---------- + +* Mixed casing of headers are now forced to be a single consistent casing across all values for that header. +* Messages internally use a HeaderCollection object to delegate handling case-insensitive header resolution +* Removed the whole changedHeader() function system of messages because all header changes now go through addHeader(). + For example, setHeader() first removes the header using unset on a HeaderCollection and then calls addHeader(). + Keeping the Host header and URL host in sync is now handled by overriding the addHeader method in Request. +* Specific header implementations can be created for complex headers. When a message creates a header, it uses a + HeaderFactory which can map specific headers to specific header classes. There is now a Link header and + CacheControl header implementation. +* Moved getLinks() from Response to just be used on a Link header object. + +If you previously relied on Guzzle\Http\Message\Header::raw(), then you will need to update your code to use the +HeaderInterface (e.g. toArray(), getAll(), etc.). + +### Interface changes + +* Removed from interface: Guzzle\Http\ClientInterface::setUriTemplate +* Removed from interface: Guzzle\Http\ClientInterface::setCurlMulti() +* Removed Guzzle\Http\Message\Request::receivedRequestHeader() and implemented this functionality in + Guzzle\Http\Curl\RequestMediator +* Removed the optional $asString parameter from MessageInterface::getHeader(). Just cast the header to a string. +* Removed the optional $tryChunkedTransfer option from Guzzle\Http\Message\EntityEnclosingRequestInterface +* Removed the $asObjects argument from Guzzle\Http\Message\MessageInterface::getHeaders() + +### Removed deprecated functions + +* Removed Guzzle\Parser\ParserRegister::get(). Use getParser() +* Removed Guzzle\Parser\ParserRegister::set(). Use registerParser(). + +### Deprecations + +* The ability to case-insensitively search for header values +* Guzzle\Http\Message\Header::hasExactHeader +* Guzzle\Http\Message\Header::raw. Use getAll() +* Deprecated cache control specific methods on Guzzle\Http\Message\AbstractMessage. Use the CacheControl header object + instead. + +### Other changes + +* All response header helper functions return a string rather than mixing Header objects and strings inconsistently +* Removed cURL blacklist support. This is no longer necessary now that Expect, Accept, etc. are managed by Guzzle + directly via interfaces +* Removed the injecting of a request object onto a response object. The methods to get and set a request still exist + but are a no-op until removed. +* Most classes that used to require a `Guzzle\Service\Command\CommandInterface` typehint now request a + `Guzzle\Service\Command\ArrayCommandInterface`. +* Added `Guzzle\Http\Message\RequestInterface::startResponse()` to the RequestInterface to handle injecting a response + on a request while the request is still being transferred +* `Guzzle\Service\Command\CommandInterface` now extends from ToArrayInterface and ArrayAccess + +3.3 to 3.4 +---------- + +Base URLs of a client now follow the rules of http://tools.ietf.org/html/rfc3986#section-5.2.2 when merging URLs. + +3.2 to 3.3 +---------- + +### Response::getEtag() quote stripping removed + +`Guzzle\Http\Message\Response::getEtag()` no longer strips quotes around the ETag response header + +### Removed `Guzzle\Http\Utils` + +The `Guzzle\Http\Utils` class was removed. This class was only used for testing. + +### Stream wrapper and type + +`Guzzle\Stream\Stream::getWrapper()` and `Guzzle\Stream\Stream::getStreamType()` are no longer converted to lowercase. + +### curl.emit_io became emit_io + +Emitting IO events from a RequestMediator is now a parameter that must be set in a request's curl options using the +'emit_io' key. This was previously set under a request's parameters using 'curl.emit_io' + +3.1 to 3.2 +---------- + +### CurlMulti is no longer reused globally + +Before 3.2, the same CurlMulti object was reused globally for each client. This can cause issue where plugins added +to a single client can pollute requests dispatched from other clients. + +If you still wish to reuse the same CurlMulti object with each client, then you can add a listener to the +ServiceBuilder's `service_builder.create_client` event to inject a custom CurlMulti object into each client as it is +created. + +```php +$multi = new Guzzle\Http\Curl\CurlMulti(); +$builder = Guzzle\Service\Builder\ServiceBuilder::factory('/path/to/config.json'); +$builder->addListener('service_builder.create_client', function ($event) use ($multi) { + $event['client']->setCurlMulti($multi); +} +}); +``` + +### No default path + +URLs no longer have a default path value of '/' if no path was specified. + +Before: + +```php +$request = $client->get('http://www.foo.com'); +echo $request->getUrl(); +// >> http://www.foo.com/ +``` + +After: + +```php +$request = $client->get('http://www.foo.com'); +echo $request->getUrl(); +// >> http://www.foo.com +``` + +### Less verbose BadResponseException + +The exception message for `Guzzle\Http\Exception\BadResponseException` no longer contains the full HTTP request and +response information. You can, however, get access to the request and response object by calling `getRequest()` or +`getResponse()` on the exception object. + +### Query parameter aggregation + +Multi-valued query parameters are no longer aggregated using a callback function. `Guzzle\Http\Query` now has a +setAggregator() method that accepts a `Guzzle\Http\QueryAggregator\QueryAggregatorInterface` object. This object is +responsible for handling the aggregation of multi-valued query string variables into a flattened hash. + +2.8 to 3.x +---------- + +### Guzzle\Service\Inspector + +Change `\Guzzle\Service\Inspector::fromConfig` to `\Guzzle\Common\Collection::fromConfig` + +**Before** + +```php +use Guzzle\Service\Inspector; + +class YourClient extends \Guzzle\Service\Client +{ + public static function factory($config = array()) + { + $default = array(); + $required = array('base_url', 'username', 'api_key'); + $config = Inspector::fromConfig($config, $default, $required); + + $client = new self( + $config->get('base_url'), + $config->get('username'), + $config->get('api_key') + ); + $client->setConfig($config); + + $client->setDescription(ServiceDescription::factory(__DIR__ . DIRECTORY_SEPARATOR . 'client.json')); + + return $client; + } +``` + +**After** + +```php +use Guzzle\Common\Collection; + +class YourClient extends \Guzzle\Service\Client +{ + public static function factory($config = array()) + { + $default = array(); + $required = array('base_url', 'username', 'api_key'); + $config = Collection::fromConfig($config, $default, $required); + + $client = new self( + $config->get('base_url'), + $config->get('username'), + $config->get('api_key') + ); + $client->setConfig($config); + + $client->setDescription(ServiceDescription::factory(__DIR__ . DIRECTORY_SEPARATOR . 'client.json')); + + return $client; + } +``` + +### Convert XML Service Descriptions to JSON + +**Before** + +```xml + + + + + + Get a list of groups + + + Uses a search query to get a list of groups + + + + Create a group + + + + + Delete a group by ID + + + + + + + Update a group + + + + + + +``` + +**After** + +```json +{ + "name": "Zendesk REST API v2", + "apiVersion": "2012-12-31", + "description":"Provides access to Zendesk views, groups, tickets, ticket fields, and users", + "operations": { + "list_groups": { + "httpMethod":"GET", + "uri": "groups.json", + "summary": "Get a list of groups" + }, + "search_groups":{ + "httpMethod":"GET", + "uri": "search.json?query=\"{query} type:group\"", + "summary": "Uses a search query to get a list of groups", + "parameters":{ + "query":{ + "location": "uri", + "description":"Zendesk Search Query", + "type": "string", + "required": true + } + } + }, + "create_group": { + "httpMethod":"POST", + "uri": "groups.json", + "summary": "Create a group", + "parameters":{ + "data": { + "type": "array", + "location": "body", + "description":"Group JSON", + "filters": "json_encode", + "required": true + }, + "Content-Type":{ + "type": "string", + "location":"header", + "static": "application/json" + } + } + }, + "delete_group": { + "httpMethod":"DELETE", + "uri": "groups/{id}.json", + "summary": "Delete a group", + "parameters":{ + "id":{ + "location": "uri", + "description":"Group to delete by ID", + "type": "integer", + "required": true + } + } + }, + "get_group": { + "httpMethod":"GET", + "uri": "groups/{id}.json", + "summary": "Get a ticket", + "parameters":{ + "id":{ + "location": "uri", + "description":"Group to get by ID", + "type": "integer", + "required": true + } + } + }, + "update_group": { + "httpMethod":"PUT", + "uri": "groups/{id}.json", + "summary": "Update a group", + "parameters":{ + "id": { + "location": "uri", + "description":"Group to update by ID", + "type": "integer", + "required": true + }, + "data": { + "type": "array", + "location": "body", + "description":"Group JSON", + "filters": "json_encode", + "required": true + }, + "Content-Type":{ + "type": "string", + "location":"header", + "static": "application/json" + } + } + } +} +``` + +### Guzzle\Service\Description\ServiceDescription + +Commands are now called Operations + +**Before** + +```php +use Guzzle\Service\Description\ServiceDescription; + +$sd = new ServiceDescription(); +$sd->getCommands(); // @returns ApiCommandInterface[] +$sd->hasCommand($name); +$sd->getCommand($name); // @returns ApiCommandInterface|null +$sd->addCommand($command); // @param ApiCommandInterface $command +``` + +**After** + +```php +use Guzzle\Service\Description\ServiceDescription; + +$sd = new ServiceDescription(); +$sd->getOperations(); // @returns OperationInterface[] +$sd->hasOperation($name); +$sd->getOperation($name); // @returns OperationInterface|null +$sd->addOperation($operation); // @param OperationInterface $operation +``` + +### Guzzle\Common\Inflection\Inflector + +Namespace is now `Guzzle\Inflection\Inflector` + +### Guzzle\Http\Plugin + +Namespace is now `Guzzle\Plugin`. Many other changes occur within this namespace and are detailed in their own sections below. + +### Guzzle\Http\Plugin\LogPlugin and Guzzle\Common\Log + +Now `Guzzle\Plugin\Log\LogPlugin` and `Guzzle\Log` respectively. + +**Before** + +```php +use Guzzle\Common\Log\ClosureLogAdapter; +use Guzzle\Http\Plugin\LogPlugin; + +/** @var \Guzzle\Http\Client */ +$client; + +// $verbosity is an integer indicating desired message verbosity level +$client->addSubscriber(new LogPlugin(new ClosureLogAdapter(function($m) { echo $m; }, $verbosity = LogPlugin::LOG_VERBOSE); +``` + +**After** + +```php +use Guzzle\Log\ClosureLogAdapter; +use Guzzle\Log\MessageFormatter; +use Guzzle\Plugin\Log\LogPlugin; + +/** @var \Guzzle\Http\Client */ +$client; + +// $format is a string indicating desired message format -- @see MessageFormatter +$client->addSubscriber(new LogPlugin(new ClosureLogAdapter(function($m) { echo $m; }, $format = MessageFormatter::DEBUG_FORMAT); +``` + +### Guzzle\Http\Plugin\CurlAuthPlugin + +Now `Guzzle\Plugin\CurlAuth\CurlAuthPlugin`. + +### Guzzle\Http\Plugin\ExponentialBackoffPlugin + +Now `Guzzle\Plugin\Backoff\BackoffPlugin`, and other changes. + +**Before** + +```php +use Guzzle\Http\Plugin\ExponentialBackoffPlugin; + +$backoffPlugin = new ExponentialBackoffPlugin($maxRetries, array_merge( + ExponentialBackoffPlugin::getDefaultFailureCodes(), array(429) + )); + +$client->addSubscriber($backoffPlugin); +``` + +**After** + +```php +use Guzzle\Plugin\Backoff\BackoffPlugin; +use Guzzle\Plugin\Backoff\HttpBackoffStrategy; + +// Use convenient factory method instead -- see implementation for ideas of what +// you can do with chaining backoff strategies +$backoffPlugin = BackoffPlugin::getExponentialBackoff($maxRetries, array_merge( + HttpBackoffStrategy::getDefaultFailureCodes(), array(429) + )); +$client->addSubscriber($backoffPlugin); +``` + +### Known Issues + +#### [BUG] Accept-Encoding header behavior changed unintentionally. + +(See #217) (Fixed in 09daeb8c666fb44499a0646d655a8ae36456575e) + +In version 2.8 setting the `Accept-Encoding` header would set the CURLOPT_ENCODING option, which permitted cURL to +properly handle gzip/deflate compressed responses from the server. In versions affected by this bug this does not happen. +See issue #217 for a workaround, or use a version containing the fix. diff --git a/vendor/guzzlehttp/guzzle/composer.json b/vendor/guzzlehttp/guzzle/composer.json new file mode 100644 index 0000000..b9cb386 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/composer.json @@ -0,0 +1,89 @@ +{ + "name": "guzzlehttp/guzzle", + "type": "library", + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "framework", + "http", + "rest", + "web service", + "curl", + "client", + "HTTP client" + ], + "homepage": "http://guzzlephp.org/", + "license": "MIT", + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "require": { + "php": ">=5.5", + "ext-json": "*", + "symfony/polyfill-intl-idn": "^1.17.0", + "guzzlehttp/promises": "^1.0", + "guzzlehttp/psr7": "^1.6.1" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0", + "psr/log": "^1.1" + }, + "suggest": { + "psr/log": "Required for using the Log middleware" + }, + "config": { + "sort-packages": true + }, + "extra": { + "branch-alias": { + "dev-master": "6.5-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "autoload-dev": { + "psr-4": { + "GuzzleHttp\\Tests\\": "tests/" + } + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Client.php b/vendor/guzzlehttp/guzzle/src/Client.php new file mode 100644 index 0000000..315a022 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Client.php @@ -0,0 +1,501 @@ + 'http://www.foo.com/1.0/', + * 'timeout' => 0, + * 'allow_redirects' => false, + * 'proxy' => '192.168.16.1:10' + * ]); + * + * Client configuration settings include the following options: + * + * - handler: (callable) Function that transfers HTTP requests over the + * wire. The function is called with a Psr7\Http\Message\RequestInterface + * and array of transfer options, and must return a + * GuzzleHttp\Promise\PromiseInterface that is fulfilled with a + * Psr7\Http\Message\ResponseInterface on success. + * If no handler is provided, a default handler will be created + * that enables all of the request options below by attaching all of the + * default middleware to the handler. + * - base_uri: (string|UriInterface) Base URI of the client that is merged + * into relative URIs. Can be a string or instance of UriInterface. + * - **: any request option + * + * @param array $config Client configuration settings. + * + * @see \GuzzleHttp\RequestOptions for a list of available request options. + */ + public function __construct(array $config = []) + { + if (!isset($config['handler'])) { + $config['handler'] = HandlerStack::create(); + } elseif (!is_callable($config['handler'])) { + throw new \InvalidArgumentException('handler must be a callable'); + } + + // Convert the base_uri to a UriInterface + if (isset($config['base_uri'])) { + $config['base_uri'] = Psr7\uri_for($config['base_uri']); + } + + $this->configureDefaults($config); + } + + /** + * @param string $method + * @param array $args + * + * @return Promise\PromiseInterface + */ + public function __call($method, $args) + { + if (count($args) < 1) { + throw new \InvalidArgumentException('Magic request methods require a URI and optional options array'); + } + + $uri = $args[0]; + $opts = isset($args[1]) ? $args[1] : []; + + return substr($method, -5) === 'Async' + ? $this->requestAsync(substr($method, 0, -5), $uri, $opts) + : $this->request($method, $uri, $opts); + } + + /** + * Asynchronously send an HTTP request. + * + * @param array $options Request options to apply to the given + * request and to the transfer. See \GuzzleHttp\RequestOptions. + * + * @return Promise\PromiseInterface + */ + public function sendAsync(RequestInterface $request, array $options = []) + { + // Merge the base URI into the request URI if needed. + $options = $this->prepareDefaults($options); + + return $this->transfer( + $request->withUri($this->buildUri($request->getUri(), $options), $request->hasHeader('Host')), + $options + ); + } + + /** + * Send an HTTP request. + * + * @param array $options Request options to apply to the given + * request and to the transfer. See \GuzzleHttp\RequestOptions. + * + * @return ResponseInterface + * @throws GuzzleException + */ + public function send(RequestInterface $request, array $options = []) + { + $options[RequestOptions::SYNCHRONOUS] = true; + return $this->sendAsync($request, $options)->wait(); + } + + /** + * Create and send an asynchronous HTTP request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. Use an array to provide a URL + * template and additional variables to use in the URL template expansion. + * + * @param string $method HTTP method + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. See \GuzzleHttp\RequestOptions. + * + * @return Promise\PromiseInterface + */ + public function requestAsync($method, $uri = '', array $options = []) + { + $options = $this->prepareDefaults($options); + // Remove request modifying parameter because it can be done up-front. + $headers = isset($options['headers']) ? $options['headers'] : []; + $body = isset($options['body']) ? $options['body'] : null; + $version = isset($options['version']) ? $options['version'] : '1.1'; + // Merge the URI into the base URI. + $uri = $this->buildUri($uri, $options); + if (is_array($body)) { + $this->invalidBody(); + } + $request = new Psr7\Request($method, $uri, $headers, $body, $version); + // Remove the option so that they are not doubly-applied. + unset($options['headers'], $options['body'], $options['version']); + + return $this->transfer($request, $options); + } + + /** + * Create and send an HTTP request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. + * + * @param string $method HTTP method. + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. See \GuzzleHttp\RequestOptions. + * + * @return ResponseInterface + * @throws GuzzleException + */ + public function request($method, $uri = '', array $options = []) + { + $options[RequestOptions::SYNCHRONOUS] = true; + return $this->requestAsync($method, $uri, $options)->wait(); + } + + /** + * Get a client configuration option. + * + * These options include default request options of the client, a "handler" + * (if utilized by the concrete client), and a "base_uri" if utilized by + * the concrete client. + * + * @param string|null $option The config option to retrieve. + * + * @return mixed + */ + public function getConfig($option = null) + { + return $option === null + ? $this->config + : (isset($this->config[$option]) ? $this->config[$option] : null); + } + + /** + * @param string|null $uri + * + * @return UriInterface + */ + private function buildUri($uri, array $config) + { + // for BC we accept null which would otherwise fail in uri_for + $uri = Psr7\uri_for($uri === null ? '' : $uri); + + if (isset($config['base_uri'])) { + $uri = Psr7\UriResolver::resolve(Psr7\uri_for($config['base_uri']), $uri); + } + + if (isset($config['idn_conversion']) && ($config['idn_conversion'] !== false)) { + $idnOptions = ($config['idn_conversion'] === true) ? IDNA_DEFAULT : $config['idn_conversion']; + $uri = Utils::idnUriConvert($uri, $idnOptions); + } + + return $uri->getScheme() === '' && $uri->getHost() !== '' ? $uri->withScheme('http') : $uri; + } + + /** + * Configures the default options for a client. + * + * @param array $config + * @return void + */ + private function configureDefaults(array $config) + { + $defaults = [ + 'allow_redirects' => RedirectMiddleware::$defaultSettings, + 'http_errors' => true, + 'decode_content' => true, + 'verify' => true, + 'cookies' => false, + 'idn_conversion' => true, + ]; + + // Use the standard Linux HTTP_PROXY and HTTPS_PROXY if set. + + // We can only trust the HTTP_PROXY environment variable in a CLI + // process due to the fact that PHP has no reliable mechanism to + // get environment variables that start with "HTTP_". + if (php_sapi_name() === 'cli' && getenv('HTTP_PROXY')) { + $defaults['proxy']['http'] = getenv('HTTP_PROXY'); + } + + if ($proxy = getenv('HTTPS_PROXY')) { + $defaults['proxy']['https'] = $proxy; + } + + if ($noProxy = getenv('NO_PROXY')) { + $cleanedNoProxy = str_replace(' ', '', $noProxy); + $defaults['proxy']['no'] = explode(',', $cleanedNoProxy); + } + + $this->config = $config + $defaults; + + if (!empty($config['cookies']) && $config['cookies'] === true) { + $this->config['cookies'] = new CookieJar(); + } + + // Add the default user-agent header. + if (!isset($this->config['headers'])) { + $this->config['headers'] = ['User-Agent' => default_user_agent()]; + } else { + // Add the User-Agent header if one was not already set. + foreach (array_keys($this->config['headers']) as $name) { + if (strtolower($name) === 'user-agent') { + return; + } + } + $this->config['headers']['User-Agent'] = default_user_agent(); + } + } + + /** + * Merges default options into the array. + * + * @param array $options Options to modify by reference + * + * @return array + */ + private function prepareDefaults(array $options) + { + $defaults = $this->config; + + if (!empty($defaults['headers'])) { + // Default headers are only added if they are not present. + $defaults['_conditional'] = $defaults['headers']; + unset($defaults['headers']); + } + + // Special handling for headers is required as they are added as + // conditional headers and as headers passed to a request ctor. + if (array_key_exists('headers', $options)) { + // Allows default headers to be unset. + if ($options['headers'] === null) { + $defaults['_conditional'] = []; + unset($options['headers']); + } elseif (!is_array($options['headers'])) { + throw new \InvalidArgumentException('headers must be an array'); + } + } + + // Shallow merge defaults underneath options. + $result = $options + $defaults; + + // Remove null values. + foreach ($result as $k => $v) { + if ($v === null) { + unset($result[$k]); + } + } + + return $result; + } + + /** + * Transfers the given request and applies request options. + * + * The URI of the request is not modified and the request options are used + * as-is without merging in default options. + * + * @param array $options See \GuzzleHttp\RequestOptions. + * + * @return Promise\PromiseInterface + */ + private function transfer(RequestInterface $request, array $options) + { + // save_to -> sink + if (isset($options['save_to'])) { + $options['sink'] = $options['save_to']; + unset($options['save_to']); + } + + // exceptions -> http_errors + if (isset($options['exceptions'])) { + $options['http_errors'] = $options['exceptions']; + unset($options['exceptions']); + } + + $request = $this->applyOptions($request, $options); + /** @var HandlerStack $handler */ + $handler = $options['handler']; + + try { + return Promise\promise_for($handler($request, $options)); + } catch (\Exception $e) { + return Promise\rejection_for($e); + } + } + + /** + * Applies the array of request options to a request. + * + * @param RequestInterface $request + * @param array $options + * + * @return RequestInterface + */ + private function applyOptions(RequestInterface $request, array &$options) + { + $modify = [ + 'set_headers' => [], + ]; + + if (isset($options['headers'])) { + $modify['set_headers'] = $options['headers']; + unset($options['headers']); + } + + if (isset($options['form_params'])) { + if (isset($options['multipart'])) { + throw new \InvalidArgumentException('You cannot use ' + . 'form_params and multipart at the same time. Use the ' + . 'form_params option if you want to send application/' + . 'x-www-form-urlencoded requests, and the multipart ' + . 'option to send multipart/form-data requests.'); + } + $options['body'] = http_build_query($options['form_params'], '', '&'); + unset($options['form_params']); + // Ensure that we don't have the header in different case and set the new value. + $options['_conditional'] = Psr7\_caseless_remove(['Content-Type'], $options['_conditional']); + $options['_conditional']['Content-Type'] = 'application/x-www-form-urlencoded'; + } + + if (isset($options['multipart'])) { + $options['body'] = new Psr7\MultipartStream($options['multipart']); + unset($options['multipart']); + } + + if (isset($options['json'])) { + $options['body'] = \GuzzleHttp\json_encode($options['json']); + unset($options['json']); + // Ensure that we don't have the header in different case and set the new value. + $options['_conditional'] = Psr7\_caseless_remove(['Content-Type'], $options['_conditional']); + $options['_conditional']['Content-Type'] = 'application/json'; + } + + if (!empty($options['decode_content']) + && $options['decode_content'] !== true + ) { + // Ensure that we don't have the header in different case and set the new value. + $options['_conditional'] = Psr7\_caseless_remove(['Accept-Encoding'], $options['_conditional']); + $modify['set_headers']['Accept-Encoding'] = $options['decode_content']; + } + + if (isset($options['body'])) { + if (is_array($options['body'])) { + $this->invalidBody(); + } + $modify['body'] = Psr7\stream_for($options['body']); + unset($options['body']); + } + + if (!empty($options['auth']) && is_array($options['auth'])) { + $value = $options['auth']; + $type = isset($value[2]) ? strtolower($value[2]) : 'basic'; + switch ($type) { + case 'basic': + // Ensure that we don't have the header in different case and set the new value. + $modify['set_headers'] = Psr7\_caseless_remove(['Authorization'], $modify['set_headers']); + $modify['set_headers']['Authorization'] = 'Basic ' + . base64_encode("$value[0]:$value[1]"); + break; + case 'digest': + // @todo: Do not rely on curl + $options['curl'][CURLOPT_HTTPAUTH] = CURLAUTH_DIGEST; + $options['curl'][CURLOPT_USERPWD] = "$value[0]:$value[1]"; + break; + case 'ntlm': + $options['curl'][CURLOPT_HTTPAUTH] = CURLAUTH_NTLM; + $options['curl'][CURLOPT_USERPWD] = "$value[0]:$value[1]"; + break; + } + } + + if (isset($options['query'])) { + $value = $options['query']; + if (is_array($value)) { + $value = http_build_query($value, null, '&', PHP_QUERY_RFC3986); + } + if (!is_string($value)) { + throw new \InvalidArgumentException('query must be a string or array'); + } + $modify['query'] = $value; + unset($options['query']); + } + + // Ensure that sink is not an invalid value. + if (isset($options['sink'])) { + // TODO: Add more sink validation? + if (is_bool($options['sink'])) { + throw new \InvalidArgumentException('sink must not be a boolean'); + } + } + + $request = Psr7\modify_request($request, $modify); + if ($request->getBody() instanceof Psr7\MultipartStream) { + // Use a multipart/form-data POST if a Content-Type is not set. + // Ensure that we don't have the header in different case and set the new value. + $options['_conditional'] = Psr7\_caseless_remove(['Content-Type'], $options['_conditional']); + $options['_conditional']['Content-Type'] = 'multipart/form-data; boundary=' + . $request->getBody()->getBoundary(); + } + + // Merge in conditional headers if they are not present. + if (isset($options['_conditional'])) { + // Build up the changes so it's in a single clone of the message. + $modify = []; + foreach ($options['_conditional'] as $k => $v) { + if (!$request->hasHeader($k)) { + $modify['set_headers'][$k] = $v; + } + } + $request = Psr7\modify_request($request, $modify); + // Don't pass this internal value along to middleware/handlers. + unset($options['_conditional']); + } + + return $request; + } + + /** + * Throw Exception with pre-set message. + * @return void + * @throws \InvalidArgumentException Invalid body. + */ + private function invalidBody() + { + throw new \InvalidArgumentException('Passing in the "body" request ' + . 'option as an array to send a POST request has been deprecated. ' + . 'Please use the "form_params" request option to send a ' + . 'application/x-www-form-urlencoded request, or the "multipart" ' + . 'request option to send a multipart/form-data request.'); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/ClientInterface.php b/vendor/guzzlehttp/guzzle/src/ClientInterface.php new file mode 100644 index 0000000..638b75d --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/ClientInterface.php @@ -0,0 +1,87 @@ +strictMode = $strictMode; + + foreach ($cookieArray as $cookie) { + if (!($cookie instanceof SetCookie)) { + $cookie = new SetCookie($cookie); + } + $this->setCookie($cookie); + } + } + + /** + * Create a new Cookie jar from an associative array and domain. + * + * @param array $cookies Cookies to create the jar from + * @param string $domain Domain to set the cookies to + * + * @return self + */ + public static function fromArray(array $cookies, $domain) + { + $cookieJar = new self(); + foreach ($cookies as $name => $value) { + $cookieJar->setCookie(new SetCookie([ + 'Domain' => $domain, + 'Name' => $name, + 'Value' => $value, + 'Discard' => true + ])); + } + + return $cookieJar; + } + + /** + * @deprecated + */ + public static function getCookieValue($value) + { + return $value; + } + + /** + * Evaluate if this cookie should be persisted to storage + * that survives between requests. + * + * @param SetCookie $cookie Being evaluated. + * @param bool $allowSessionCookies If we should persist session cookies + * @return bool + */ + public static function shouldPersist( + SetCookie $cookie, + $allowSessionCookies = false + ) { + if ($cookie->getExpires() || $allowSessionCookies) { + if (!$cookie->getDiscard()) { + return true; + } + } + + return false; + } + + /** + * Finds and returns the cookie based on the name + * + * @param string $name cookie name to search for + * @return SetCookie|null cookie that was found or null if not found + */ + public function getCookieByName($name) + { + // don't allow a non string name + if ($name === null || !is_scalar($name)) { + return null; + } + foreach ($this->cookies as $cookie) { + if ($cookie->getName() !== null && strcasecmp($cookie->getName(), $name) === 0) { + return $cookie; + } + } + + return null; + } + + public function toArray() + { + return array_map(function (SetCookie $cookie) { + return $cookie->toArray(); + }, $this->getIterator()->getArrayCopy()); + } + + public function clear($domain = null, $path = null, $name = null) + { + if (!$domain) { + $this->cookies = []; + return; + } elseif (!$path) { + $this->cookies = array_filter( + $this->cookies, + function (SetCookie $cookie) use ($domain) { + return !$cookie->matchesDomain($domain); + } + ); + } elseif (!$name) { + $this->cookies = array_filter( + $this->cookies, + function (SetCookie $cookie) use ($path, $domain) { + return !($cookie->matchesPath($path) && + $cookie->matchesDomain($domain)); + } + ); + } else { + $this->cookies = array_filter( + $this->cookies, + function (SetCookie $cookie) use ($path, $domain, $name) { + return !($cookie->getName() == $name && + $cookie->matchesPath($path) && + $cookie->matchesDomain($domain)); + } + ); + } + } + + public function clearSessionCookies() + { + $this->cookies = array_filter( + $this->cookies, + function (SetCookie $cookie) { + return !$cookie->getDiscard() && $cookie->getExpires(); + } + ); + } + + public function setCookie(SetCookie $cookie) + { + // If the name string is empty (but not 0), ignore the set-cookie + // string entirely. + $name = $cookie->getName(); + if (!$name && $name !== '0') { + return false; + } + + // Only allow cookies with set and valid domain, name, value + $result = $cookie->validate(); + if ($result !== true) { + if ($this->strictMode) { + throw new \RuntimeException('Invalid cookie: ' . $result); + } else { + $this->removeCookieIfEmpty($cookie); + return false; + } + } + + // Resolve conflicts with previously set cookies + foreach ($this->cookies as $i => $c) { + + // Two cookies are identical, when their path, and domain are + // identical. + if ($c->getPath() != $cookie->getPath() || + $c->getDomain() != $cookie->getDomain() || + $c->getName() != $cookie->getName() + ) { + continue; + } + + // The previously set cookie is a discard cookie and this one is + // not so allow the new cookie to be set + if (!$cookie->getDiscard() && $c->getDiscard()) { + unset($this->cookies[$i]); + continue; + } + + // If the new cookie's expiration is further into the future, then + // replace the old cookie + if ($cookie->getExpires() > $c->getExpires()) { + unset($this->cookies[$i]); + continue; + } + + // If the value has changed, we better change it + if ($cookie->getValue() !== $c->getValue()) { + unset($this->cookies[$i]); + continue; + } + + // The cookie exists, so no need to continue + return false; + } + + $this->cookies[] = $cookie; + + return true; + } + + public function count() + { + return count($this->cookies); + } + + public function getIterator() + { + return new \ArrayIterator(array_values($this->cookies)); + } + + public function extractCookies( + RequestInterface $request, + ResponseInterface $response + ) { + if ($cookieHeader = $response->getHeader('Set-Cookie')) { + foreach ($cookieHeader as $cookie) { + $sc = SetCookie::fromString($cookie); + if (!$sc->getDomain()) { + $sc->setDomain($request->getUri()->getHost()); + } + if (0 !== strpos($sc->getPath(), '/')) { + $sc->setPath($this->getCookiePathFromRequest($request)); + } + if (!$sc->matchesDomain($request->getUri()->getHost())) { + continue; + } + // Note: At this point `$sc->getDomain()` being a public suffix should + // be rejected, but we don't want to pull in the full PSL dependency. + $this->setCookie($sc); + } + } + } + + /** + * Computes cookie path following RFC 6265 section 5.1.4 + * + * @link https://tools.ietf.org/html/rfc6265#section-5.1.4 + * + * @param RequestInterface $request + * @return string + */ + private function getCookiePathFromRequest(RequestInterface $request) + { + $uriPath = $request->getUri()->getPath(); + if ('' === $uriPath) { + return '/'; + } + if (0 !== strpos($uriPath, '/')) { + return '/'; + } + if ('/' === $uriPath) { + return '/'; + } + if (0 === $lastSlashPos = strrpos($uriPath, '/')) { + return '/'; + } + + return substr($uriPath, 0, $lastSlashPos); + } + + public function withCookieHeader(RequestInterface $request) + { + $values = []; + $uri = $request->getUri(); + $scheme = $uri->getScheme(); + $host = $uri->getHost(); + $path = $uri->getPath() ?: '/'; + + foreach ($this->cookies as $cookie) { + if ($cookie->matchesPath($path) && + $cookie->matchesDomain($host) && + !$cookie->isExpired() && + (!$cookie->getSecure() || $scheme === 'https') + ) { + $values[] = $cookie->getName() . '=' + . $cookie->getValue(); + } + } + + return $values + ? $request->withHeader('Cookie', implode('; ', $values)) + : $request; + } + + /** + * If a cookie already exists and the server asks to set it again with a + * null value, the cookie must be deleted. + * + * @param SetCookie $cookie + */ + private function removeCookieIfEmpty(SetCookie $cookie) + { + $cookieValue = $cookie->getValue(); + if ($cookieValue === null || $cookieValue === '') { + $this->clear( + $cookie->getDomain(), + $cookie->getPath(), + $cookie->getName() + ); + } + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php b/vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php new file mode 100644 index 0000000..6ee1188 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php @@ -0,0 +1,84 @@ +filename = $cookieFile; + $this->storeSessionCookies = $storeSessionCookies; + + if (file_exists($cookieFile)) { + $this->load($cookieFile); + } + } + + /** + * Saves the file when shutting down + */ + public function __destruct() + { + $this->save($this->filename); + } + + /** + * Saves the cookies to a file. + * + * @param string $filename File to save + * @throws \RuntimeException if the file cannot be found or created + */ + public function save($filename) + { + $json = []; + foreach ($this as $cookie) { + /** @var SetCookie $cookie */ + if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) { + $json[] = $cookie->toArray(); + } + } + + $jsonStr = \GuzzleHttp\json_encode($json); + if (false === file_put_contents($filename, $jsonStr, LOCK_EX)) { + throw new \RuntimeException("Unable to save file {$filename}"); + } + } + + /** + * Load cookies from a JSON formatted file. + * + * Old cookies are kept unless overwritten by newly loaded ones. + * + * @param string $filename Cookie file to load. + * @throws \RuntimeException if the file cannot be loaded. + */ + public function load($filename) + { + $json = file_get_contents($filename); + if (false === $json) { + throw new \RuntimeException("Unable to load file {$filename}"); + } elseif ($json === '') { + return; + } + + $data = \GuzzleHttp\json_decode($json, true); + if (is_array($data)) { + foreach (json_decode($json, true) as $cookie) { + $this->setCookie(new SetCookie($cookie)); + } + } elseif (strlen($data)) { + throw new \RuntimeException("Invalid cookie file: {$filename}"); + } + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php b/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php new file mode 100644 index 0000000..0224a24 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php @@ -0,0 +1,72 @@ +sessionKey = $sessionKey; + $this->storeSessionCookies = $storeSessionCookies; + $this->load(); + } + + /** + * Saves cookies to session when shutting down + */ + public function __destruct() + { + $this->save(); + } + + /** + * Save cookies to the client session + */ + public function save() + { + $json = []; + foreach ($this as $cookie) { + /** @var SetCookie $cookie */ + if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) { + $json[] = $cookie->toArray(); + } + } + + $_SESSION[$this->sessionKey] = json_encode($json); + } + + /** + * Load the contents of the client session into the data array + */ + protected function load() + { + if (!isset($_SESSION[$this->sessionKey])) { + return; + } + $data = json_decode($_SESSION[$this->sessionKey], true); + if (is_array($data)) { + foreach ($data as $cookie) { + $this->setCookie(new SetCookie($cookie)); + } + } elseif (strlen($data)) { + throw new \RuntimeException("Invalid cookie data"); + } + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php b/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php new file mode 100644 index 0000000..55f6901 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php @@ -0,0 +1,410 @@ + null, + 'Value' => null, + 'Domain' => null, + 'Path' => '/', + 'Max-Age' => null, + 'Expires' => null, + 'Secure' => false, + 'Discard' => false, + 'HttpOnly' => false + ]; + + /** @var array Cookie data */ + private $data; + + /** + * Create a new SetCookie object from a string + * + * @param string $cookie Set-Cookie header string + * + * @return self + */ + public static function fromString($cookie) + { + // Create the default return array + $data = self::$defaults; + // Explode the cookie string using a series of semicolons + $pieces = array_filter(array_map('trim', explode(';', $cookie))); + // The name of the cookie (first kvp) must exist and include an equal sign. + if (empty($pieces[0]) || !strpos($pieces[0], '=')) { + return new self($data); + } + + // Add the cookie pieces into the parsed data array + foreach ($pieces as $part) { + $cookieParts = explode('=', $part, 2); + $key = trim($cookieParts[0]); + $value = isset($cookieParts[1]) + ? trim($cookieParts[1], " \n\r\t\0\x0B") + : true; + + // Only check for non-cookies when cookies have been found + if (empty($data['Name'])) { + $data['Name'] = $key; + $data['Value'] = $value; + } else { + foreach (array_keys(self::$defaults) as $search) { + if (!strcasecmp($search, $key)) { + $data[$search] = $value; + continue 2; + } + } + $data[$key] = $value; + } + } + + return new self($data); + } + + /** + * @param array $data Array of cookie data provided by a Cookie parser + */ + public function __construct(array $data = []) + { + $this->data = array_replace(self::$defaults, $data); + // Extract the Expires value and turn it into a UNIX timestamp if needed + if (!$this->getExpires() && $this->getMaxAge()) { + // Calculate the Expires date + $this->setExpires(time() + $this->getMaxAge()); + } elseif ($this->getExpires() && !is_numeric($this->getExpires())) { + $this->setExpires($this->getExpires()); + } + } + + public function __toString() + { + $str = $this->data['Name'] . '=' . $this->data['Value'] . '; '; + foreach ($this->data as $k => $v) { + if ($k !== 'Name' && $k !== 'Value' && $v !== null && $v !== false) { + if ($k === 'Expires') { + $str .= 'Expires=' . gmdate('D, d M Y H:i:s \G\M\T', $v) . '; '; + } else { + $str .= ($v === true ? $k : "{$k}={$v}") . '; '; + } + } + } + + return rtrim($str, '; '); + } + + public function toArray() + { + return $this->data; + } + + /** + * Get the cookie name + * + * @return string + */ + public function getName() + { + return $this->data['Name']; + } + + /** + * Set the cookie name + * + * @param string $name Cookie name + */ + public function setName($name) + { + $this->data['Name'] = $name; + } + + /** + * Get the cookie value + * + * @return string + */ + public function getValue() + { + return $this->data['Value']; + } + + /** + * Set the cookie value + * + * @param string $value Cookie value + */ + public function setValue($value) + { + $this->data['Value'] = $value; + } + + /** + * Get the domain + * + * @return string|null + */ + public function getDomain() + { + return $this->data['Domain']; + } + + /** + * Set the domain of the cookie + * + * @param string $domain + */ + public function setDomain($domain) + { + $this->data['Domain'] = $domain; + } + + /** + * Get the path + * + * @return string + */ + public function getPath() + { + return $this->data['Path']; + } + + /** + * Set the path of the cookie + * + * @param string $path Path of the cookie + */ + public function setPath($path) + { + $this->data['Path'] = $path; + } + + /** + * Maximum lifetime of the cookie in seconds + * + * @return int|null + */ + public function getMaxAge() + { + return $this->data['Max-Age']; + } + + /** + * Set the max-age of the cookie + * + * @param int $maxAge Max age of the cookie in seconds + */ + public function setMaxAge($maxAge) + { + $this->data['Max-Age'] = $maxAge; + } + + /** + * The UNIX timestamp when the cookie Expires + * + * @return mixed + */ + public function getExpires() + { + return $this->data['Expires']; + } + + /** + * Set the unix timestamp for which the cookie will expire + * + * @param int $timestamp Unix timestamp + */ + public function setExpires($timestamp) + { + $this->data['Expires'] = is_numeric($timestamp) + ? (int) $timestamp + : strtotime($timestamp); + } + + /** + * Get whether or not this is a secure cookie + * + * @return bool|null + */ + public function getSecure() + { + return $this->data['Secure']; + } + + /** + * Set whether or not the cookie is secure + * + * @param bool $secure Set to true or false if secure + */ + public function setSecure($secure) + { + $this->data['Secure'] = $secure; + } + + /** + * Get whether or not this is a session cookie + * + * @return bool|null + */ + public function getDiscard() + { + return $this->data['Discard']; + } + + /** + * Set whether or not this is a session cookie + * + * @param bool $discard Set to true or false if this is a session cookie + */ + public function setDiscard($discard) + { + $this->data['Discard'] = $discard; + } + + /** + * Get whether or not this is an HTTP only cookie + * + * @return bool + */ + public function getHttpOnly() + { + return $this->data['HttpOnly']; + } + + /** + * Set whether or not this is an HTTP only cookie + * + * @param bool $httpOnly Set to true or false if this is HTTP only + */ + public function setHttpOnly($httpOnly) + { + $this->data['HttpOnly'] = $httpOnly; + } + + /** + * Check if the cookie matches a path value. + * + * A request-path path-matches a given cookie-path if at least one of + * the following conditions holds: + * + * - The cookie-path and the request-path are identical. + * - The cookie-path is a prefix of the request-path, and the last + * character of the cookie-path is %x2F ("/"). + * - The cookie-path is a prefix of the request-path, and the first + * character of the request-path that is not included in the cookie- + * path is a %x2F ("/") character. + * + * @param string $requestPath Path to check against + * + * @return bool + */ + public function matchesPath($requestPath) + { + $cookiePath = $this->getPath(); + + // Match on exact matches or when path is the default empty "/" + if ($cookiePath === '/' || $cookiePath == $requestPath) { + return true; + } + + // Ensure that the cookie-path is a prefix of the request path. + if (0 !== strpos($requestPath, $cookiePath)) { + return false; + } + + // Match if the last character of the cookie-path is "/" + if (substr($cookiePath, -1, 1) === '/') { + return true; + } + + // Match if the first character not included in cookie path is "/" + return substr($requestPath, strlen($cookiePath), 1) === '/'; + } + + /** + * Check if the cookie matches a domain value + * + * @param string $domain Domain to check against + * + * @return bool + */ + public function matchesDomain($domain) + { + $cookieDomain = $this->getDomain(); + if (null === $cookieDomain) { + return true; + } + + // Remove the leading '.' as per spec in RFC 6265. + // http://tools.ietf.org/html/rfc6265#section-5.2.3 + $cookieDomain = ltrim(strtolower($cookieDomain), '.'); + + $domain = strtolower($domain); + + // Domain not set or exact match. + if ('' === $cookieDomain || $domain === $cookieDomain) { + return true; + } + + // Matching the subdomain according to RFC 6265. + // http://tools.ietf.org/html/rfc6265#section-5.1.3 + if (filter_var($domain, FILTER_VALIDATE_IP)) { + return false; + } + + return (bool) preg_match('/\.' . preg_quote($cookieDomain, '/') . '$/', $domain); + } + + /** + * Check if the cookie is expired + * + * @return bool + */ + public function isExpired() + { + return $this->getExpires() !== null && time() > $this->getExpires(); + } + + /** + * Check if the cookie is valid according to RFC 6265 + * + * @return bool|string Returns true if valid or an error message if invalid + */ + public function validate() + { + // Names must not be empty, but can be 0 + $name = $this->getName(); + if (empty($name) && !is_numeric($name)) { + return 'The cookie name must not be empty'; + } + + // Check if any of the invalid characters are present in the cookie name + if (preg_match( + '/[\x00-\x20\x22\x28-\x29\x2c\x2f\x3a-\x40\x5c\x7b\x7d\x7f]/', + $name + )) { + return 'Cookie name must not contain invalid characters: ASCII ' + . 'Control characters (0-31;127), space, tab and the ' + . 'following characters: ()<>@,;:\"/?={}'; + } + + // Value must not be empty, but can be 0 + $value = $this->getValue(); + if (empty($value) && !is_numeric($value)) { + return 'The cookie value must not be empty'; + } + + // Domains must not be empty, but can be 0 + // A "0" is not a valid internet domain, but may be used as server name + // in a private network. + $domain = $this->getDomain(); + if (empty($domain) && !is_numeric($domain)) { + return 'The cookie domain must not be empty'; + } + + return true; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Exception/BadResponseException.php b/vendor/guzzlehttp/guzzle/src/Exception/BadResponseException.php new file mode 100644 index 0000000..427d896 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Exception/BadResponseException.php @@ -0,0 +1,27 @@ +getStatusCode() + : 0; + parent::__construct($message, $code, $previous); + $this->request = $request; + $this->response = $response; + $this->handlerContext = $handlerContext; + } + + /** + * Wrap non-RequestExceptions with a RequestException + * + * @param RequestInterface $request + * @param \Exception $e + * + * @return RequestException + */ + public static function wrapException(RequestInterface $request, \Exception $e) + { + return $e instanceof RequestException + ? $e + : new RequestException($e->getMessage(), $request, null, $e); + } + + /** + * Factory method to create a new exception with a normalized error message + * + * @param RequestInterface $request Request + * @param ResponseInterface $response Response received + * @param \Exception $previous Previous exception + * @param array $ctx Optional handler context. + * + * @return self + */ + public static function create( + RequestInterface $request, + ResponseInterface $response = null, + \Exception $previous = null, + array $ctx = [] + ) { + if (!$response) { + return new self( + 'Error completing request', + $request, + null, + $previous, + $ctx + ); + } + + $level = (int) floor($response->getStatusCode() / 100); + if ($level === 4) { + $label = 'Client error'; + $className = ClientException::class; + } elseif ($level === 5) { + $label = 'Server error'; + $className = ServerException::class; + } else { + $label = 'Unsuccessful request'; + $className = __CLASS__; + } + + $uri = $request->getUri(); + $uri = static::obfuscateUri($uri); + + // Client Error: `GET /` resulted in a `404 Not Found` response: + // ... (truncated) + $message = sprintf( + '%s: `%s %s` resulted in a `%s %s` response', + $label, + $request->getMethod(), + $uri, + $response->getStatusCode(), + $response->getReasonPhrase() + ); + + $summary = static::getResponseBodySummary($response); + + if ($summary !== null) { + $message .= ":\n{$summary}\n"; + } + + return new $className($message, $request, $response, $previous, $ctx); + } + + /** + * Get a short summary of the response + * + * Will return `null` if the response is not printable. + * + * @param ResponseInterface $response + * + * @return string|null + */ + public static function getResponseBodySummary(ResponseInterface $response) + { + return \GuzzleHttp\Psr7\get_message_body_summary($response); + } + + /** + * Obfuscates URI if there is a username and a password present + * + * @param UriInterface $uri + * + * @return UriInterface + */ + private static function obfuscateUri(UriInterface $uri) + { + $userInfo = $uri->getUserInfo(); + + if (false !== ($pos = strpos($userInfo, ':'))) { + return $uri->withUserInfo(substr($userInfo, 0, $pos), '***'); + } + + return $uri; + } + + /** + * Get the request that caused the exception + * + * @return RequestInterface + */ + public function getRequest() + { + return $this->request; + } + + /** + * Get the associated response + * + * @return ResponseInterface|null + */ + public function getResponse() + { + return $this->response; + } + + /** + * Check if a response was received + * + * @return bool + */ + public function hasResponse() + { + return $this->response !== null; + } + + /** + * Get contextual information about the error from the underlying handler. + * + * The contents of this array will vary depending on which handler you are + * using. It may also be just an empty array. Relying on this data will + * couple you to a specific handler, but can give more debug information + * when needed. + * + * @return array + */ + public function getHandlerContext() + { + return $this->handlerContext; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Exception/SeekException.php b/vendor/guzzlehttp/guzzle/src/Exception/SeekException.php new file mode 100644 index 0000000..a77c289 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Exception/SeekException.php @@ -0,0 +1,27 @@ +stream = $stream; + $msg = $msg ?: 'Could not seek the stream to position ' . $pos; + parent::__construct($msg); + } + + /** + * @return StreamInterface + */ + public function getStream() + { + return $this->stream; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Exception/ServerException.php b/vendor/guzzlehttp/guzzle/src/Exception/ServerException.php new file mode 100644 index 0000000..127094c --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Exception/ServerException.php @@ -0,0 +1,9 @@ +maxHandles = $maxHandles; + } + + public function create(RequestInterface $request, array $options) + { + if (isset($options['curl']['body_as_string'])) { + $options['_body_as_string'] = $options['curl']['body_as_string']; + unset($options['curl']['body_as_string']); + } + + $easy = new EasyHandle; + $easy->request = $request; + $easy->options = $options; + $conf = $this->getDefaultConf($easy); + $this->applyMethod($easy, $conf); + $this->applyHandlerOptions($easy, $conf); + $this->applyHeaders($easy, $conf); + unset($conf['_headers']); + + // Add handler options from the request configuration options + if (isset($options['curl'])) { + $conf = array_replace($conf, $options['curl']); + } + + $conf[CURLOPT_HEADERFUNCTION] = $this->createHeaderFn($easy); + $easy->handle = $this->handles + ? array_pop($this->handles) + : curl_init(); + curl_setopt_array($easy->handle, $conf); + + return $easy; + } + + public function release(EasyHandle $easy) + { + $resource = $easy->handle; + unset($easy->handle); + + if (count($this->handles) >= $this->maxHandles) { + curl_close($resource); + } else { + // Remove all callback functions as they can hold onto references + // and are not cleaned up by curl_reset. Using curl_setopt_array + // does not work for some reason, so removing each one + // individually. + curl_setopt($resource, CURLOPT_HEADERFUNCTION, null); + curl_setopt($resource, CURLOPT_READFUNCTION, null); + curl_setopt($resource, CURLOPT_WRITEFUNCTION, null); + curl_setopt($resource, CURLOPT_PROGRESSFUNCTION, null); + curl_reset($resource); + $this->handles[] = $resource; + } + } + + /** + * Completes a cURL transaction, either returning a response promise or a + * rejected promise. + * + * @param callable $handler + * @param EasyHandle $easy + * @param CurlFactoryInterface $factory Dictates how the handle is released + * + * @return \GuzzleHttp\Promise\PromiseInterface + */ + public static function finish( + callable $handler, + EasyHandle $easy, + CurlFactoryInterface $factory + ) { + if (isset($easy->options['on_stats'])) { + self::invokeStats($easy); + } + + if (!$easy->response || $easy->errno) { + return self::finishError($handler, $easy, $factory); + } + + // Return the response if it is present and there is no error. + $factory->release($easy); + + // Rewind the body of the response if possible. + $body = $easy->response->getBody(); + if ($body->isSeekable()) { + $body->rewind(); + } + + return new FulfilledPromise($easy->response); + } + + private static function invokeStats(EasyHandle $easy) + { + $curlStats = curl_getinfo($easy->handle); + $curlStats['appconnect_time'] = curl_getinfo($easy->handle, CURLINFO_APPCONNECT_TIME); + $stats = new TransferStats( + $easy->request, + $easy->response, + $curlStats['total_time'], + $easy->errno, + $curlStats + ); + call_user_func($easy->options['on_stats'], $stats); + } + + private static function finishError( + callable $handler, + EasyHandle $easy, + CurlFactoryInterface $factory + ) { + // Get error information and release the handle to the factory. + $ctx = [ + 'errno' => $easy->errno, + 'error' => curl_error($easy->handle), + 'appconnect_time' => curl_getinfo($easy->handle, CURLINFO_APPCONNECT_TIME), + ] + curl_getinfo($easy->handle); + $ctx[self::CURL_VERSION_STR] = curl_version()['version']; + $factory->release($easy); + + // Retry when nothing is present or when curl failed to rewind. + if (empty($easy->options['_err_message']) + && (!$easy->errno || $easy->errno == 65) + ) { + return self::retryFailedRewind($handler, $easy, $ctx); + } + + return self::createRejection($easy, $ctx); + } + + private static function createRejection(EasyHandle $easy, array $ctx) + { + static $connectionErrors = [ + CURLE_OPERATION_TIMEOUTED => true, + CURLE_COULDNT_RESOLVE_HOST => true, + CURLE_COULDNT_CONNECT => true, + CURLE_SSL_CONNECT_ERROR => true, + CURLE_GOT_NOTHING => true, + ]; + + // If an exception was encountered during the onHeaders event, then + // return a rejected promise that wraps that exception. + if ($easy->onHeadersException) { + return \GuzzleHttp\Promise\rejection_for( + new RequestException( + 'An error was encountered during the on_headers event', + $easy->request, + $easy->response, + $easy->onHeadersException, + $ctx + ) + ); + } + if (version_compare($ctx[self::CURL_VERSION_STR], self::LOW_CURL_VERSION_NUMBER)) { + $message = sprintf( + 'cURL error %s: %s (%s)', + $ctx['errno'], + $ctx['error'], + 'see https://curl.haxx.se/libcurl/c/libcurl-errors.html' + ); + } else { + $message = sprintf( + 'cURL error %s: %s (%s) for %s', + $ctx['errno'], + $ctx['error'], + 'see https://curl.haxx.se/libcurl/c/libcurl-errors.html', + $easy->request->getUri() + ); + } + + // Create a connection exception if it was a specific error code. + $error = isset($connectionErrors[$easy->errno]) + ? new ConnectException($message, $easy->request, null, $ctx) + : new RequestException($message, $easy->request, $easy->response, null, $ctx); + + return \GuzzleHttp\Promise\rejection_for($error); + } + + private function getDefaultConf(EasyHandle $easy) + { + $conf = [ + '_headers' => $easy->request->getHeaders(), + CURLOPT_CUSTOMREQUEST => $easy->request->getMethod(), + CURLOPT_URL => (string) $easy->request->getUri()->withFragment(''), + CURLOPT_RETURNTRANSFER => false, + CURLOPT_HEADER => false, + CURLOPT_CONNECTTIMEOUT => 150, + ]; + + if (defined('CURLOPT_PROTOCOLS')) { + $conf[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; + } + + $version = $easy->request->getProtocolVersion(); + if ($version == 1.1) { + $conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_1; + } elseif ($version == 2.0) { + $conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_2_0; + } else { + $conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0; + } + + return $conf; + } + + private function applyMethod(EasyHandle $easy, array &$conf) + { + $body = $easy->request->getBody(); + $size = $body->getSize(); + + if ($size === null || $size > 0) { + $this->applyBody($easy->request, $easy->options, $conf); + return; + } + + $method = $easy->request->getMethod(); + if ($method === 'PUT' || $method === 'POST') { + // See http://tools.ietf.org/html/rfc7230#section-3.3.2 + if (!$easy->request->hasHeader('Content-Length')) { + $conf[CURLOPT_HTTPHEADER][] = 'Content-Length: 0'; + } + } elseif ($method === 'HEAD') { + $conf[CURLOPT_NOBODY] = true; + unset( + $conf[CURLOPT_WRITEFUNCTION], + $conf[CURLOPT_READFUNCTION], + $conf[CURLOPT_FILE], + $conf[CURLOPT_INFILE] + ); + } + } + + private function applyBody(RequestInterface $request, array $options, array &$conf) + { + $size = $request->hasHeader('Content-Length') + ? (int) $request->getHeaderLine('Content-Length') + : null; + + // Send the body as a string if the size is less than 1MB OR if the + // [curl][body_as_string] request value is set. + if (($size !== null && $size < 1000000) || + !empty($options['_body_as_string']) + ) { + $conf[CURLOPT_POSTFIELDS] = (string) $request->getBody(); + // Don't duplicate the Content-Length header + $this->removeHeader('Content-Length', $conf); + $this->removeHeader('Transfer-Encoding', $conf); + } else { + $conf[CURLOPT_UPLOAD] = true; + if ($size !== null) { + $conf[CURLOPT_INFILESIZE] = $size; + $this->removeHeader('Content-Length', $conf); + } + $body = $request->getBody(); + if ($body->isSeekable()) { + $body->rewind(); + } + $conf[CURLOPT_READFUNCTION] = function ($ch, $fd, $length) use ($body) { + return $body->read($length); + }; + } + + // If the Expect header is not present, prevent curl from adding it + if (!$request->hasHeader('Expect')) { + $conf[CURLOPT_HTTPHEADER][] = 'Expect:'; + } + + // cURL sometimes adds a content-type by default. Prevent this. + if (!$request->hasHeader('Content-Type')) { + $conf[CURLOPT_HTTPHEADER][] = 'Content-Type:'; + } + } + + private function applyHeaders(EasyHandle $easy, array &$conf) + { + foreach ($conf['_headers'] as $name => $values) { + foreach ($values as $value) { + $value = (string) $value; + if ($value === '') { + // cURL requires a special format for empty headers. + // See https://github.com/guzzle/guzzle/issues/1882 for more details. + $conf[CURLOPT_HTTPHEADER][] = "$name;"; + } else { + $conf[CURLOPT_HTTPHEADER][] = "$name: $value"; + } + } + } + + // Remove the Accept header if one was not set + if (!$easy->request->hasHeader('Accept')) { + $conf[CURLOPT_HTTPHEADER][] = 'Accept:'; + } + } + + /** + * Remove a header from the options array. + * + * @param string $name Case-insensitive header to remove + * @param array $options Array of options to modify + */ + private function removeHeader($name, array &$options) + { + foreach (array_keys($options['_headers']) as $key) { + if (!strcasecmp($key, $name)) { + unset($options['_headers'][$key]); + return; + } + } + } + + private function applyHandlerOptions(EasyHandle $easy, array &$conf) + { + $options = $easy->options; + if (isset($options['verify'])) { + if ($options['verify'] === false) { + unset($conf[CURLOPT_CAINFO]); + $conf[CURLOPT_SSL_VERIFYHOST] = 0; + $conf[CURLOPT_SSL_VERIFYPEER] = false; + } else { + $conf[CURLOPT_SSL_VERIFYHOST] = 2; + $conf[CURLOPT_SSL_VERIFYPEER] = true; + if (is_string($options['verify'])) { + // Throw an error if the file/folder/link path is not valid or doesn't exist. + if (!file_exists($options['verify'])) { + throw new \InvalidArgumentException( + "SSL CA bundle not found: {$options['verify']}" + ); + } + // If it's a directory or a link to a directory use CURLOPT_CAPATH. + // If not, it's probably a file, or a link to a file, so use CURLOPT_CAINFO. + if (is_dir($options['verify']) || + (is_link($options['verify']) && is_dir(readlink($options['verify'])))) { + $conf[CURLOPT_CAPATH] = $options['verify']; + } else { + $conf[CURLOPT_CAINFO] = $options['verify']; + } + } + } + } + + if (!empty($options['decode_content'])) { + $accept = $easy->request->getHeaderLine('Accept-Encoding'); + if ($accept) { + $conf[CURLOPT_ENCODING] = $accept; + } else { + $conf[CURLOPT_ENCODING] = ''; + // Don't let curl send the header over the wire + $conf[CURLOPT_HTTPHEADER][] = 'Accept-Encoding:'; + } + } + + if (isset($options['sink'])) { + $sink = $options['sink']; + if (!is_string($sink)) { + $sink = \GuzzleHttp\Psr7\stream_for($sink); + } elseif (!is_dir(dirname($sink))) { + // Ensure that the directory exists before failing in curl. + throw new \RuntimeException(sprintf( + 'Directory %s does not exist for sink value of %s', + dirname($sink), + $sink + )); + } else { + $sink = new LazyOpenStream($sink, 'w+'); + } + $easy->sink = $sink; + $conf[CURLOPT_WRITEFUNCTION] = function ($ch, $write) use ($sink) { + return $sink->write($write); + }; + } else { + // Use a default temp stream if no sink was set. + $conf[CURLOPT_FILE] = fopen('php://temp', 'w+'); + $easy->sink = Psr7\stream_for($conf[CURLOPT_FILE]); + } + $timeoutRequiresNoSignal = false; + if (isset($options['timeout'])) { + $timeoutRequiresNoSignal |= $options['timeout'] < 1; + $conf[CURLOPT_TIMEOUT_MS] = $options['timeout'] * 1000; + } + + // CURL default value is CURL_IPRESOLVE_WHATEVER + if (isset($options['force_ip_resolve'])) { + if ('v4' === $options['force_ip_resolve']) { + $conf[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V4; + } elseif ('v6' === $options['force_ip_resolve']) { + $conf[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V6; + } + } + + if (isset($options['connect_timeout'])) { + $timeoutRequiresNoSignal |= $options['connect_timeout'] < 1; + $conf[CURLOPT_CONNECTTIMEOUT_MS] = $options['connect_timeout'] * 1000; + } + + if ($timeoutRequiresNoSignal && strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') { + $conf[CURLOPT_NOSIGNAL] = true; + } + + if (isset($options['proxy'])) { + if (!is_array($options['proxy'])) { + $conf[CURLOPT_PROXY] = $options['proxy']; + } else { + $scheme = $easy->request->getUri()->getScheme(); + if (isset($options['proxy'][$scheme])) { + $host = $easy->request->getUri()->getHost(); + if (!isset($options['proxy']['no']) || + !\GuzzleHttp\is_host_in_noproxy($host, $options['proxy']['no']) + ) { + $conf[CURLOPT_PROXY] = $options['proxy'][$scheme]; + } + } + } + } + + if (isset($options['cert'])) { + $cert = $options['cert']; + if (is_array($cert)) { + $conf[CURLOPT_SSLCERTPASSWD] = $cert[1]; + $cert = $cert[0]; + } + if (!file_exists($cert)) { + throw new \InvalidArgumentException( + "SSL certificate not found: {$cert}" + ); + } + $conf[CURLOPT_SSLCERT] = $cert; + } + + if (isset($options['ssl_key'])) { + if (is_array($options['ssl_key'])) { + if (count($options['ssl_key']) === 2) { + list($sslKey, $conf[CURLOPT_SSLKEYPASSWD]) = $options['ssl_key']; + } else { + list($sslKey) = $options['ssl_key']; + } + } + + $sslKey = isset($sslKey) ? $sslKey: $options['ssl_key']; + + if (!file_exists($sslKey)) { + throw new \InvalidArgumentException( + "SSL private key not found: {$sslKey}" + ); + } + $conf[CURLOPT_SSLKEY] = $sslKey; + } + + if (isset($options['progress'])) { + $progress = $options['progress']; + if (!is_callable($progress)) { + throw new \InvalidArgumentException( + 'progress client option must be callable' + ); + } + $conf[CURLOPT_NOPROGRESS] = false; + $conf[CURLOPT_PROGRESSFUNCTION] = function () use ($progress) { + $args = func_get_args(); + // PHP 5.5 pushed the handle onto the start of the args + if (is_resource($args[0])) { + array_shift($args); + } + call_user_func_array($progress, $args); + }; + } + + if (!empty($options['debug'])) { + $conf[CURLOPT_STDERR] = \GuzzleHttp\debug_resource($options['debug']); + $conf[CURLOPT_VERBOSE] = true; + } + } + + /** + * This function ensures that a response was set on a transaction. If one + * was not set, then the request is retried if possible. This error + * typically means you are sending a payload, curl encountered a + * "Connection died, retrying a fresh connect" error, tried to rewind the + * stream, and then encountered a "necessary data rewind wasn't possible" + * error, causing the request to be sent through curl_multi_info_read() + * without an error status. + */ + private static function retryFailedRewind( + callable $handler, + EasyHandle $easy, + array $ctx + ) { + try { + // Only rewind if the body has been read from. + $body = $easy->request->getBody(); + if ($body->tell() > 0) { + $body->rewind(); + } + } catch (\RuntimeException $e) { + $ctx['error'] = 'The connection unexpectedly failed without ' + . 'providing an error. The request would have been retried, ' + . 'but attempting to rewind the request body failed. ' + . 'Exception: ' . $e; + return self::createRejection($easy, $ctx); + } + + // Retry no more than 3 times before giving up. + if (!isset($easy->options['_curl_retries'])) { + $easy->options['_curl_retries'] = 1; + } elseif ($easy->options['_curl_retries'] == 2) { + $ctx['error'] = 'The cURL request was retried 3 times ' + . 'and did not succeed. The most likely reason for the failure ' + . 'is that cURL was unable to rewind the body of the request ' + . 'and subsequent retries resulted in the same error. Turn on ' + . 'the debug option to see what went wrong. See ' + . 'https://bugs.php.net/bug.php?id=47204 for more information.'; + return self::createRejection($easy, $ctx); + } else { + $easy->options['_curl_retries']++; + } + + return $handler($easy->request, $easy->options); + } + + private function createHeaderFn(EasyHandle $easy) + { + if (isset($easy->options['on_headers'])) { + $onHeaders = $easy->options['on_headers']; + + if (!is_callable($onHeaders)) { + throw new \InvalidArgumentException('on_headers must be callable'); + } + } else { + $onHeaders = null; + } + + return function ($ch, $h) use ( + $onHeaders, + $easy, + &$startingResponse + ) { + $value = trim($h); + if ($value === '') { + $startingResponse = true; + $easy->createResponse(); + if ($onHeaders !== null) { + try { + $onHeaders($easy->response); + } catch (\Exception $e) { + // Associate the exception with the handle and trigger + // a curl header write error by returning 0. + $easy->onHeadersException = $e; + return -1; + } + } + } elseif ($startingResponse) { + $startingResponse = false; + $easy->headers = [$value]; + } else { + $easy->headers[] = $value; + } + return strlen($h); + }; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php b/vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php new file mode 100644 index 0000000..b0fc236 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php @@ -0,0 +1,27 @@ +factory = isset($options['handle_factory']) + ? $options['handle_factory'] + : new CurlFactory(3); + } + + public function __invoke(RequestInterface $request, array $options) + { + if (isset($options['delay'])) { + usleep($options['delay'] * 1000); + } + + $easy = $this->factory->create($request, $options); + curl_exec($easy->handle); + $easy->errno = curl_errno($easy->handle); + + return CurlFactory::finish($this, $easy, $this->factory); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php b/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php new file mode 100644 index 0000000..564c95f --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php @@ -0,0 +1,219 @@ +factory = isset($options['handle_factory']) + ? $options['handle_factory'] : new CurlFactory(50); + + if (isset($options['select_timeout'])) { + $this->selectTimeout = $options['select_timeout']; + } elseif ($selectTimeout = getenv('GUZZLE_CURL_SELECT_TIMEOUT')) { + $this->selectTimeout = $selectTimeout; + } else { + $this->selectTimeout = 1; + } + + $this->options = isset($options['options']) ? $options['options'] : []; + } + + public function __get($name) + { + if ($name === '_mh') { + $this->_mh = curl_multi_init(); + + foreach ($this->options as $option => $value) { + // A warning is raised in case of a wrong option. + curl_multi_setopt($this->_mh, $option, $value); + } + + // Further calls to _mh will return the value directly, without entering the + // __get() method at all. + return $this->_mh; + } + + throw new \BadMethodCallException(); + } + + public function __destruct() + { + if (isset($this->_mh)) { + curl_multi_close($this->_mh); + unset($this->_mh); + } + } + + public function __invoke(RequestInterface $request, array $options) + { + $easy = $this->factory->create($request, $options); + $id = (int) $easy->handle; + + $promise = new Promise( + [$this, 'execute'], + function () use ($id) { + return $this->cancel($id); + } + ); + + $this->addRequest(['easy' => $easy, 'deferred' => $promise]); + + return $promise; + } + + /** + * Ticks the curl event loop. + */ + public function tick() + { + // Add any delayed handles if needed. + if ($this->delays) { + $currentTime = Utils::currentTime(); + foreach ($this->delays as $id => $delay) { + if ($currentTime >= $delay) { + unset($this->delays[$id]); + curl_multi_add_handle( + $this->_mh, + $this->handles[$id]['easy']->handle + ); + } + } + } + + // Step through the task queue which may add additional requests. + P\queue()->run(); + + if ($this->active && + curl_multi_select($this->_mh, $this->selectTimeout) === -1 + ) { + // Perform a usleep if a select returns -1. + // See: https://bugs.php.net/bug.php?id=61141 + usleep(250); + } + + while (curl_multi_exec($this->_mh, $this->active) === CURLM_CALL_MULTI_PERFORM); + + $this->processMessages(); + } + + /** + * Runs until all outstanding connections have completed. + */ + public function execute() + { + $queue = P\queue(); + + while ($this->handles || !$queue->isEmpty()) { + // If there are no transfers, then sleep for the next delay + if (!$this->active && $this->delays) { + usleep($this->timeToNext()); + } + $this->tick(); + } + } + + private function addRequest(array $entry) + { + $easy = $entry['easy']; + $id = (int) $easy->handle; + $this->handles[$id] = $entry; + if (empty($easy->options['delay'])) { + curl_multi_add_handle($this->_mh, $easy->handle); + } else { + $this->delays[$id] = Utils::currentTime() + ($easy->options['delay'] / 1000); + } + } + + /** + * Cancels a handle from sending and removes references to it. + * + * @param int $id Handle ID to cancel and remove. + * + * @return bool True on success, false on failure. + */ + private function cancel($id) + { + // Cannot cancel if it has been processed. + if (!isset($this->handles[$id])) { + return false; + } + + $handle = $this->handles[$id]['easy']->handle; + unset($this->delays[$id], $this->handles[$id]); + curl_multi_remove_handle($this->_mh, $handle); + curl_close($handle); + + return true; + } + + private function processMessages() + { + while ($done = curl_multi_info_read($this->_mh)) { + $id = (int) $done['handle']; + curl_multi_remove_handle($this->_mh, $done['handle']); + + if (!isset($this->handles[$id])) { + // Probably was cancelled. + continue; + } + + $entry = $this->handles[$id]; + unset($this->handles[$id], $this->delays[$id]); + $entry['easy']->errno = $done['result']; + $entry['deferred']->resolve( + CurlFactory::finish( + $this, + $entry['easy'], + $this->factory + ) + ); + } + } + + private function timeToNext() + { + $currentTime = Utils::currentTime(); + $nextTime = PHP_INT_MAX; + foreach ($this->delays as $time) { + if ($time < $nextTime) { + $nextTime = $time; + } + } + + return max(0, $nextTime - $currentTime) * 1000000; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php b/vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php new file mode 100644 index 0000000..7754e91 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php @@ -0,0 +1,92 @@ +headers)) { + throw new \RuntimeException('No headers have been received'); + } + + // HTTP-version SP status-code SP reason-phrase + $startLine = explode(' ', array_shift($this->headers), 3); + $headers = \GuzzleHttp\headers_from_lines($this->headers); + $normalizedKeys = \GuzzleHttp\normalize_header_keys($headers); + + if (!empty($this->options['decode_content']) + && isset($normalizedKeys['content-encoding']) + ) { + $headers['x-encoded-content-encoding'] + = $headers[$normalizedKeys['content-encoding']]; + unset($headers[$normalizedKeys['content-encoding']]); + if (isset($normalizedKeys['content-length'])) { + $headers['x-encoded-content-length'] + = $headers[$normalizedKeys['content-length']]; + + $bodyLength = (int) $this->sink->getSize(); + if ($bodyLength) { + $headers[$normalizedKeys['content-length']] = $bodyLength; + } else { + unset($headers[$normalizedKeys['content-length']]); + } + } + } + + // Attach a response to the easy handle with the parsed headers. + $this->response = new Response( + $startLine[1], + $headers, + $this->sink, + substr($startLine[0], 5), + isset($startLine[2]) ? (string) $startLine[2] : null + ); + } + + public function __get($name) + { + $msg = $name === 'handle' + ? 'The EasyHandle has been released' + : 'Invalid property: ' . $name; + throw new \BadMethodCallException($msg); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Handler/MockHandler.php b/vendor/guzzlehttp/guzzle/src/Handler/MockHandler.php new file mode 100644 index 0000000..5b312bc --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Handler/MockHandler.php @@ -0,0 +1,195 @@ +onFulfilled = $onFulfilled; + $this->onRejected = $onRejected; + + if ($queue) { + call_user_func_array([$this, 'append'], $queue); + } + } + + public function __invoke(RequestInterface $request, array $options) + { + if (!$this->queue) { + throw new \OutOfBoundsException('Mock queue is empty'); + } + + if (isset($options['delay']) && is_numeric($options['delay'])) { + usleep($options['delay'] * 1000); + } + + $this->lastRequest = $request; + $this->lastOptions = $options; + $response = array_shift($this->queue); + + if (isset($options['on_headers'])) { + if (!is_callable($options['on_headers'])) { + throw new \InvalidArgumentException('on_headers must be callable'); + } + try { + $options['on_headers']($response); + } catch (\Exception $e) { + $msg = 'An error was encountered during the on_headers event'; + $response = new RequestException($msg, $request, $response, $e); + } + } + + if (is_callable($response)) { + $response = call_user_func($response, $request, $options); + } + + $response = $response instanceof \Exception + ? \GuzzleHttp\Promise\rejection_for($response) + : \GuzzleHttp\Promise\promise_for($response); + + return $response->then( + function ($value) use ($request, $options) { + $this->invokeStats($request, $options, $value); + if ($this->onFulfilled) { + call_user_func($this->onFulfilled, $value); + } + if (isset($options['sink'])) { + $contents = (string) $value->getBody(); + $sink = $options['sink']; + + if (is_resource($sink)) { + fwrite($sink, $contents); + } elseif (is_string($sink)) { + file_put_contents($sink, $contents); + } elseif ($sink instanceof \Psr\Http\Message\StreamInterface) { + $sink->write($contents); + } + } + + return $value; + }, + function ($reason) use ($request, $options) { + $this->invokeStats($request, $options, null, $reason); + if ($this->onRejected) { + call_user_func($this->onRejected, $reason); + } + return \GuzzleHttp\Promise\rejection_for($reason); + } + ); + } + + /** + * Adds one or more variadic requests, exceptions, callables, or promises + * to the queue. + */ + public function append() + { + foreach (func_get_args() as $value) { + if ($value instanceof ResponseInterface + || $value instanceof \Exception + || $value instanceof PromiseInterface + || is_callable($value) + ) { + $this->queue[] = $value; + } else { + throw new \InvalidArgumentException('Expected a response or ' + . 'exception. Found ' . \GuzzleHttp\describe_type($value)); + } + } + } + + /** + * Get the last received request. + * + * @return RequestInterface + */ + public function getLastRequest() + { + return $this->lastRequest; + } + + /** + * Get the last received request options. + * + * @return array + */ + public function getLastOptions() + { + return $this->lastOptions; + } + + /** + * Returns the number of remaining items in the queue. + * + * @return int + */ + public function count() + { + return count($this->queue); + } + + public function reset() + { + $this->queue = []; + } + + private function invokeStats( + RequestInterface $request, + array $options, + ResponseInterface $response = null, + $reason = null + ) { + if (isset($options['on_stats'])) { + $transferTime = isset($options['transfer_time']) ? $options['transfer_time'] : 0; + $stats = new TransferStats($request, $response, $transferTime, $reason); + call_user_func($options['on_stats'], $stats); + } + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php b/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php new file mode 100644 index 0000000..f8b00be --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php @@ -0,0 +1,55 @@ +withoutHeader('Expect'); + + // Append a content-length header if body size is zero to match + // cURL's behavior. + if (0 === $request->getBody()->getSize()) { + $request = $request->withHeader('Content-Length', '0'); + } + + return $this->createResponse( + $request, + $options, + $this->createStream($request, $options), + $startTime + ); + } catch (\InvalidArgumentException $e) { + throw $e; + } catch (\Exception $e) { + // Determine if the error was a networking error. + $message = $e->getMessage(); + // This list can probably get more comprehensive. + if (strpos($message, 'getaddrinfo') // DNS lookup failed + || strpos($message, 'Connection refused') + || strpos($message, "couldn't connect to host") // error on HHVM + || strpos($message, "connection attempt failed") + ) { + $e = new ConnectException($e->getMessage(), $request, $e); + } + $e = RequestException::wrapException($request, $e); + $this->invokeStats($options, $request, $startTime, null, $e); + + return \GuzzleHttp\Promise\rejection_for($e); + } + } + + private function invokeStats( + array $options, + RequestInterface $request, + $startTime, + ResponseInterface $response = null, + $error = null + ) { + if (isset($options['on_stats'])) { + $stats = new TransferStats( + $request, + $response, + Utils::currentTime() - $startTime, + $error, + [] + ); + call_user_func($options['on_stats'], $stats); + } + } + + private function createResponse( + RequestInterface $request, + array $options, + $stream, + $startTime + ) { + $hdrs = $this->lastHeaders; + $this->lastHeaders = []; + $parts = explode(' ', array_shift($hdrs), 3); + $ver = explode('/', $parts[0])[1]; + $status = $parts[1]; + $reason = isset($parts[2]) ? $parts[2] : null; + $headers = \GuzzleHttp\headers_from_lines($hdrs); + list($stream, $headers) = $this->checkDecode($options, $headers, $stream); + $stream = Psr7\stream_for($stream); + $sink = $stream; + + if (strcasecmp('HEAD', $request->getMethod())) { + $sink = $this->createSink($stream, $options); + } + + $response = new Psr7\Response($status, $headers, $sink, $ver, $reason); + + if (isset($options['on_headers'])) { + try { + $options['on_headers']($response); + } catch (\Exception $e) { + $msg = 'An error was encountered during the on_headers event'; + $ex = new RequestException($msg, $request, $response, $e); + return \GuzzleHttp\Promise\rejection_for($ex); + } + } + + // Do not drain when the request is a HEAD request because they have + // no body. + if ($sink !== $stream) { + $this->drain( + $stream, + $sink, + $response->getHeaderLine('Content-Length') + ); + } + + $this->invokeStats($options, $request, $startTime, $response, null); + + return new FulfilledPromise($response); + } + + private function createSink(StreamInterface $stream, array $options) + { + if (!empty($options['stream'])) { + return $stream; + } + + $sink = isset($options['sink']) + ? $options['sink'] + : fopen('php://temp', 'r+'); + + return is_string($sink) + ? new Psr7\LazyOpenStream($sink, 'w+') + : Psr7\stream_for($sink); + } + + private function checkDecode(array $options, array $headers, $stream) + { + // Automatically decode responses when instructed. + if (!empty($options['decode_content'])) { + $normalizedKeys = \GuzzleHttp\normalize_header_keys($headers); + if (isset($normalizedKeys['content-encoding'])) { + $encoding = $headers[$normalizedKeys['content-encoding']]; + if ($encoding[0] === 'gzip' || $encoding[0] === 'deflate') { + $stream = new Psr7\InflateStream( + Psr7\stream_for($stream) + ); + $headers['x-encoded-content-encoding'] + = $headers[$normalizedKeys['content-encoding']]; + // Remove content-encoding header + unset($headers[$normalizedKeys['content-encoding']]); + // Fix content-length header + if (isset($normalizedKeys['content-length'])) { + $headers['x-encoded-content-length'] + = $headers[$normalizedKeys['content-length']]; + + $length = (int) $stream->getSize(); + if ($length === 0) { + unset($headers[$normalizedKeys['content-length']]); + } else { + $headers[$normalizedKeys['content-length']] = [$length]; + } + } + } + } + } + + return [$stream, $headers]; + } + + /** + * Drains the source stream into the "sink" client option. + * + * @param StreamInterface $source + * @param StreamInterface $sink + * @param string $contentLength Header specifying the amount of + * data to read. + * + * @return StreamInterface + * @throws \RuntimeException when the sink option is invalid. + */ + private function drain( + StreamInterface $source, + StreamInterface $sink, + $contentLength + ) { + // If a content-length header is provided, then stop reading once + // that number of bytes has been read. This can prevent infinitely + // reading from a stream when dealing with servers that do not honor + // Connection: Close headers. + Psr7\copy_to_stream( + $source, + $sink, + (strlen($contentLength) > 0 && (int) $contentLength > 0) ? (int) $contentLength : -1 + ); + + $sink->seek(0); + $source->close(); + + return $sink; + } + + /** + * Create a resource and check to ensure it was created successfully + * + * @param callable $callback Callable that returns stream resource + * + * @return resource + * @throws \RuntimeException on error + */ + private function createResource(callable $callback) + { + $errors = null; + set_error_handler(function ($_, $msg, $file, $line) use (&$errors) { + $errors[] = [ + 'message' => $msg, + 'file' => $file, + 'line' => $line + ]; + return true; + }); + + $resource = $callback(); + restore_error_handler(); + + if (!$resource) { + $message = 'Error creating resource: '; + foreach ($errors as $err) { + foreach ($err as $key => $value) { + $message .= "[$key] $value" . PHP_EOL; + } + } + throw new \RuntimeException(trim($message)); + } + + return $resource; + } + + private function createStream(RequestInterface $request, array $options) + { + static $methods; + if (!$methods) { + $methods = array_flip(get_class_methods(__CLASS__)); + } + + // HTTP/1.1 streams using the PHP stream wrapper require a + // Connection: close header + if ($request->getProtocolVersion() == '1.1' + && !$request->hasHeader('Connection') + ) { + $request = $request->withHeader('Connection', 'close'); + } + + // Ensure SSL is verified by default + if (!isset($options['verify'])) { + $options['verify'] = true; + } + + $params = []; + $context = $this->getDefaultContext($request); + + if (isset($options['on_headers']) && !is_callable($options['on_headers'])) { + throw new \InvalidArgumentException('on_headers must be callable'); + } + + if (!empty($options)) { + foreach ($options as $key => $value) { + $method = "add_{$key}"; + if (isset($methods[$method])) { + $this->{$method}($request, $context, $value, $params); + } + } + } + + if (isset($options['stream_context'])) { + if (!is_array($options['stream_context'])) { + throw new \InvalidArgumentException('stream_context must be an array'); + } + $context = array_replace_recursive( + $context, + $options['stream_context'] + ); + } + + // Microsoft NTLM authentication only supported with curl handler + if (isset($options['auth']) + && is_array($options['auth']) + && isset($options['auth'][2]) + && 'ntlm' == $options['auth'][2] + ) { + throw new \InvalidArgumentException('Microsoft NTLM authentication only supported with curl handler'); + } + + $uri = $this->resolveHost($request, $options); + + $context = $this->createResource( + function () use ($context, $params) { + return stream_context_create($context, $params); + } + ); + + return $this->createResource( + function () use ($uri, &$http_response_header, $context, $options) { + $resource = fopen((string) $uri, 'r', null, $context); + $this->lastHeaders = $http_response_header; + + if (isset($options['read_timeout'])) { + $readTimeout = $options['read_timeout']; + $sec = (int) $readTimeout; + $usec = ($readTimeout - $sec) * 100000; + stream_set_timeout($resource, $sec, $usec); + } + + return $resource; + } + ); + } + + private function resolveHost(RequestInterface $request, array $options) + { + $uri = $request->getUri(); + + if (isset($options['force_ip_resolve']) && !filter_var($uri->getHost(), FILTER_VALIDATE_IP)) { + if ('v4' === $options['force_ip_resolve']) { + $records = dns_get_record($uri->getHost(), DNS_A); + if (!isset($records[0]['ip'])) { + throw new ConnectException( + sprintf( + "Could not resolve IPv4 address for host '%s'", + $uri->getHost() + ), + $request + ); + } + $uri = $uri->withHost($records[0]['ip']); + } elseif ('v6' === $options['force_ip_resolve']) { + $records = dns_get_record($uri->getHost(), DNS_AAAA); + if (!isset($records[0]['ipv6'])) { + throw new ConnectException( + sprintf( + "Could not resolve IPv6 address for host '%s'", + $uri->getHost() + ), + $request + ); + } + $uri = $uri->withHost('[' . $records[0]['ipv6'] . ']'); + } + } + + return $uri; + } + + private function getDefaultContext(RequestInterface $request) + { + $headers = ''; + foreach ($request->getHeaders() as $name => $value) { + foreach ($value as $val) { + $headers .= "$name: $val\r\n"; + } + } + + $context = [ + 'http' => [ + 'method' => $request->getMethod(), + 'header' => $headers, + 'protocol_version' => $request->getProtocolVersion(), + 'ignore_errors' => true, + 'follow_location' => 0, + ], + ]; + + $body = (string) $request->getBody(); + + if (!empty($body)) { + $context['http']['content'] = $body; + // Prevent the HTTP handler from adding a Content-Type header. + if (!$request->hasHeader('Content-Type')) { + $context['http']['header'] .= "Content-Type:\r\n"; + } + } + + $context['http']['header'] = rtrim($context['http']['header']); + + return $context; + } + + private function add_proxy(RequestInterface $request, &$options, $value, &$params) + { + if (!is_array($value)) { + $options['http']['proxy'] = $value; + } else { + $scheme = $request->getUri()->getScheme(); + if (isset($value[$scheme])) { + if (!isset($value['no']) + || !\GuzzleHttp\is_host_in_noproxy( + $request->getUri()->getHost(), + $value['no'] + ) + ) { + $options['http']['proxy'] = $value[$scheme]; + } + } + } + } + + private function add_timeout(RequestInterface $request, &$options, $value, &$params) + { + if ($value > 0) { + $options['http']['timeout'] = $value; + } + } + + private function add_verify(RequestInterface $request, &$options, $value, &$params) + { + if ($value === true) { + // PHP 5.6 or greater will find the system cert by default. When + // < 5.6, use the Guzzle bundled cacert. + if (PHP_VERSION_ID < 50600) { + $options['ssl']['cafile'] = \GuzzleHttp\default_ca_bundle(); + } + } elseif (is_string($value)) { + $options['ssl']['cafile'] = $value; + if (!file_exists($value)) { + throw new \RuntimeException("SSL CA bundle not found: $value"); + } + } elseif ($value === false) { + $options['ssl']['verify_peer'] = false; + $options['ssl']['verify_peer_name'] = false; + return; + } else { + throw new \InvalidArgumentException('Invalid verify request option'); + } + + $options['ssl']['verify_peer'] = true; + $options['ssl']['verify_peer_name'] = true; + $options['ssl']['allow_self_signed'] = false; + } + + private function add_cert(RequestInterface $request, &$options, $value, &$params) + { + if (is_array($value)) { + $options['ssl']['passphrase'] = $value[1]; + $value = $value[0]; + } + + if (!file_exists($value)) { + throw new \RuntimeException("SSL certificate not found: {$value}"); + } + + $options['ssl']['local_cert'] = $value; + } + + private function add_progress(RequestInterface $request, &$options, $value, &$params) + { + $this->addNotification( + $params, + function ($code, $a, $b, $c, $transferred, $total) use ($value) { + if ($code == STREAM_NOTIFY_PROGRESS) { + $value($total, $transferred, null, null); + } + } + ); + } + + private function add_debug(RequestInterface $request, &$options, $value, &$params) + { + if ($value === false) { + return; + } + + static $map = [ + STREAM_NOTIFY_CONNECT => 'CONNECT', + STREAM_NOTIFY_AUTH_REQUIRED => 'AUTH_REQUIRED', + STREAM_NOTIFY_AUTH_RESULT => 'AUTH_RESULT', + STREAM_NOTIFY_MIME_TYPE_IS => 'MIME_TYPE_IS', + STREAM_NOTIFY_FILE_SIZE_IS => 'FILE_SIZE_IS', + STREAM_NOTIFY_REDIRECTED => 'REDIRECTED', + STREAM_NOTIFY_PROGRESS => 'PROGRESS', + STREAM_NOTIFY_FAILURE => 'FAILURE', + STREAM_NOTIFY_COMPLETED => 'COMPLETED', + STREAM_NOTIFY_RESOLVE => 'RESOLVE', + ]; + static $args = ['severity', 'message', 'message_code', + 'bytes_transferred', 'bytes_max']; + + $value = \GuzzleHttp\debug_resource($value); + $ident = $request->getMethod() . ' ' . $request->getUri()->withFragment(''); + $this->addNotification( + $params, + function () use ($ident, $value, $map, $args) { + $passed = func_get_args(); + $code = array_shift($passed); + fprintf($value, '<%s> [%s] ', $ident, $map[$code]); + foreach (array_filter($passed) as $i => $v) { + fwrite($value, $args[$i] . ': "' . $v . '" '); + } + fwrite($value, "\n"); + } + ); + } + + private function addNotification(array &$params, callable $notify) + { + // Wrap the existing function if needed. + if (!isset($params['notification'])) { + $params['notification'] = $notify; + } else { + $params['notification'] = $this->callArray([ + $params['notification'], + $notify + ]); + } + } + + private function callArray(array $functions) + { + return function () use ($functions) { + $args = func_get_args(); + foreach ($functions as $fn) { + call_user_func_array($fn, $args); + } + }; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/HandlerStack.php b/vendor/guzzlehttp/guzzle/src/HandlerStack.php new file mode 100644 index 0000000..6a49cc0 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/HandlerStack.php @@ -0,0 +1,277 @@ +push(Middleware::httpErrors(), 'http_errors'); + $stack->push(Middleware::redirect(), 'allow_redirects'); + $stack->push(Middleware::cookies(), 'cookies'); + $stack->push(Middleware::prepareBody(), 'prepare_body'); + + return $stack; + } + + /** + * @param callable $handler Underlying HTTP handler. + */ + public function __construct(callable $handler = null) + { + $this->handler = $handler; + } + + /** + * Invokes the handler stack as a composed handler + * + * @param RequestInterface $request + * @param array $options + * + * @return ResponseInterface|PromiseInterface + */ + public function __invoke(RequestInterface $request, array $options) + { + $handler = $this->resolve(); + + return $handler($request, $options); + } + + /** + * Dumps a string representation of the stack. + * + * @return string + */ + public function __toString() + { + $depth = 0; + $stack = []; + if ($this->handler) { + $stack[] = "0) Handler: " . $this->debugCallable($this->handler); + } + + $result = ''; + foreach (array_reverse($this->stack) as $tuple) { + $depth++; + $str = "{$depth}) Name: '{$tuple[1]}', "; + $str .= "Function: " . $this->debugCallable($tuple[0]); + $result = "> {$str}\n{$result}"; + $stack[] = $str; + } + + foreach (array_keys($stack) as $k) { + $result .= "< {$stack[$k]}\n"; + } + + return $result; + } + + /** + * Set the HTTP handler that actually returns a promise. + * + * @param callable $handler Accepts a request and array of options and + * returns a Promise. + */ + public function setHandler(callable $handler) + { + $this->handler = $handler; + $this->cached = null; + } + + /** + * Returns true if the builder has a handler. + * + * @return bool + */ + public function hasHandler() + { + return (bool) $this->handler; + } + + /** + * Unshift a middleware to the bottom of the stack. + * + * @param callable $middleware Middleware function + * @param string $name Name to register for this middleware. + */ + public function unshift(callable $middleware, $name = null) + { + array_unshift($this->stack, [$middleware, $name]); + $this->cached = null; + } + + /** + * Push a middleware to the top of the stack. + * + * @param callable $middleware Middleware function + * @param string $name Name to register for this middleware. + */ + public function push(callable $middleware, $name = '') + { + $this->stack[] = [$middleware, $name]; + $this->cached = null; + } + + /** + * Add a middleware before another middleware by name. + * + * @param string $findName Middleware to find + * @param callable $middleware Middleware function + * @param string $withName Name to register for this middleware. + */ + public function before($findName, callable $middleware, $withName = '') + { + $this->splice($findName, $withName, $middleware, true); + } + + /** + * Add a middleware after another middleware by name. + * + * @param string $findName Middleware to find + * @param callable $middleware Middleware function + * @param string $withName Name to register for this middleware. + */ + public function after($findName, callable $middleware, $withName = '') + { + $this->splice($findName, $withName, $middleware, false); + } + + /** + * Remove a middleware by instance or name from the stack. + * + * @param callable|string $remove Middleware to remove by instance or name. + */ + public function remove($remove) + { + $this->cached = null; + $idx = is_callable($remove) ? 0 : 1; + $this->stack = array_values(array_filter( + $this->stack, + function ($tuple) use ($idx, $remove) { + return $tuple[$idx] !== $remove; + } + )); + } + + /** + * Compose the middleware and handler into a single callable function. + * + * @return callable + */ + public function resolve() + { + if (!$this->cached) { + if (!($prev = $this->handler)) { + throw new \LogicException('No handler has been specified'); + } + + foreach (array_reverse($this->stack) as $fn) { + $prev = $fn[0]($prev); + } + + $this->cached = $prev; + } + + return $this->cached; + } + + /** + * @param string $name + * @return int + */ + private function findByName($name) + { + foreach ($this->stack as $k => $v) { + if ($v[1] === $name) { + return $k; + } + } + + throw new \InvalidArgumentException("Middleware not found: $name"); + } + + /** + * Splices a function into the middleware list at a specific position. + * + * @param string $findName + * @param string $withName + * @param callable $middleware + * @param bool $before + */ + private function splice($findName, $withName, callable $middleware, $before) + { + $this->cached = null; + $idx = $this->findByName($findName); + $tuple = [$middleware, $withName]; + + if ($before) { + if ($idx === 0) { + array_unshift($this->stack, $tuple); + } else { + $replacement = [$tuple, $this->stack[$idx]]; + array_splice($this->stack, $idx, 1, $replacement); + } + } elseif ($idx === count($this->stack) - 1) { + $this->stack[] = $tuple; + } else { + $replacement = [$this->stack[$idx], $tuple]; + array_splice($this->stack, $idx, 1, $replacement); + } + } + + /** + * Provides a debug string for a given callable. + * + * @param array|callable $fn Function to write as a string. + * + * @return string + */ + private function debugCallable($fn) + { + if (is_string($fn)) { + return "callable({$fn})"; + } + + if (is_array($fn)) { + return is_string($fn[0]) + ? "callable({$fn[0]}::{$fn[1]})" + : "callable(['" . get_class($fn[0]) . "', '{$fn[1]}'])"; + } + + return 'callable(' . spl_object_hash($fn) . ')'; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/MessageFormatter.php b/vendor/guzzlehttp/guzzle/src/MessageFormatter.php new file mode 100644 index 0000000..dc36bb5 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/MessageFormatter.php @@ -0,0 +1,185 @@ +>>>>>>>\n{request}\n<<<<<<<<\n{response}\n--------\n{error}"; + const SHORT = '[{ts}] "{method} {target} HTTP/{version}" {code}'; + + /** @var string Template used to format log messages */ + private $template; + + /** + * @param string $template Log message template + */ + public function __construct($template = self::CLF) + { + $this->template = $template ?: self::CLF; + } + + /** + * Returns a formatted message string. + * + * @param RequestInterface $request Request that was sent + * @param ResponseInterface $response Response that was received + * @param \Exception $error Exception that was received + * + * @return string + */ + public function format( + RequestInterface $request, + ResponseInterface $response = null, + \Exception $error = null + ) { + $cache = []; + + return preg_replace_callback( + '/{\s*([A-Za-z_\-\.0-9]+)\s*}/', + function (array $matches) use ($request, $response, $error, &$cache) { + if (isset($cache[$matches[1]])) { + return $cache[$matches[1]]; + } + + $result = ''; + switch ($matches[1]) { + case 'request': + $result = Psr7\str($request); + break; + case 'response': + $result = $response ? Psr7\str($response) : ''; + break; + case 'req_headers': + $result = trim($request->getMethod() + . ' ' . $request->getRequestTarget()) + . ' HTTP/' . $request->getProtocolVersion() . "\r\n" + . $this->headers($request); + break; + case 'res_headers': + $result = $response ? + sprintf( + 'HTTP/%s %d %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() + ) . "\r\n" . $this->headers($response) + : 'NULL'; + break; + case 'req_body': + $result = $request->getBody(); + break; + case 'res_body': + $result = $response ? $response->getBody() : 'NULL'; + break; + case 'ts': + case 'date_iso_8601': + $result = gmdate('c'); + break; + case 'date_common_log': + $result = date('d/M/Y:H:i:s O'); + break; + case 'method': + $result = $request->getMethod(); + break; + case 'version': + $result = $request->getProtocolVersion(); + break; + case 'uri': + case 'url': + $result = $request->getUri(); + break; + case 'target': + $result = $request->getRequestTarget(); + break; + case 'req_version': + $result = $request->getProtocolVersion(); + break; + case 'res_version': + $result = $response + ? $response->getProtocolVersion() + : 'NULL'; + break; + case 'host': + $result = $request->getHeaderLine('Host'); + break; + case 'hostname': + $result = gethostname(); + break; + case 'code': + $result = $response ? $response->getStatusCode() : 'NULL'; + break; + case 'phrase': + $result = $response ? $response->getReasonPhrase() : 'NULL'; + break; + case 'error': + $result = $error ? $error->getMessage() : 'NULL'; + break; + default: + // handle prefixed dynamic headers + if (strpos($matches[1], 'req_header_') === 0) { + $result = $request->getHeaderLine(substr($matches[1], 11)); + } elseif (strpos($matches[1], 'res_header_') === 0) { + $result = $response + ? $response->getHeaderLine(substr($matches[1], 11)) + : 'NULL'; + } + } + + $cache[$matches[1]] = $result; + return $result; + }, + $this->template + ); + } + + /** + * Get headers from message as string + * + * @return string + */ + private function headers(MessageInterface $message) + { + $result = ''; + foreach ($message->getHeaders() as $name => $values) { + $result .= $name . ': ' . implode(', ', $values) . "\r\n"; + } + + return trim($result); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Middleware.php b/vendor/guzzlehttp/guzzle/src/Middleware.php new file mode 100644 index 0000000..bffc197 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Middleware.php @@ -0,0 +1,254 @@ +withCookieHeader($request); + return $handler($request, $options) + ->then( + function ($response) use ($cookieJar, $request) { + $cookieJar->extractCookies($request, $response); + return $response; + } + ); + }; + }; + } + + /** + * Middleware that throws exceptions for 4xx or 5xx responses when the + * "http_error" request option is set to true. + * + * @return callable Returns a function that accepts the next handler. + */ + public static function httpErrors() + { + return function (callable $handler) { + return function ($request, array $options) use ($handler) { + if (empty($options['http_errors'])) { + return $handler($request, $options); + } + return $handler($request, $options)->then( + function (ResponseInterface $response) use ($request) { + $code = $response->getStatusCode(); + if ($code < 400) { + return $response; + } + throw RequestException::create($request, $response); + } + ); + }; + }; + } + + /** + * Middleware that pushes history data to an ArrayAccess container. + * + * @param array|\ArrayAccess $container Container to hold the history (by reference). + * + * @return callable Returns a function that accepts the next handler. + * @throws \InvalidArgumentException if container is not an array or ArrayAccess. + */ + public static function history(&$container) + { + if (!is_array($container) && !$container instanceof \ArrayAccess) { + throw new \InvalidArgumentException('history container must be an array or object implementing ArrayAccess'); + } + + return function (callable $handler) use (&$container) { + return function ($request, array $options) use ($handler, &$container) { + return $handler($request, $options)->then( + function ($value) use ($request, &$container, $options) { + $container[] = [ + 'request' => $request, + 'response' => $value, + 'error' => null, + 'options' => $options + ]; + return $value; + }, + function ($reason) use ($request, &$container, $options) { + $container[] = [ + 'request' => $request, + 'response' => null, + 'error' => $reason, + 'options' => $options + ]; + return \GuzzleHttp\Promise\rejection_for($reason); + } + ); + }; + }; + } + + /** + * Middleware that invokes a callback before and after sending a request. + * + * The provided listener cannot modify or alter the response. It simply + * "taps" into the chain to be notified before returning the promise. The + * before listener accepts a request and options array, and the after + * listener accepts a request, options array, and response promise. + * + * @param callable $before Function to invoke before forwarding the request. + * @param callable $after Function invoked after forwarding. + * + * @return callable Returns a function that accepts the next handler. + */ + public static function tap(callable $before = null, callable $after = null) + { + return function (callable $handler) use ($before, $after) { + return function ($request, array $options) use ($handler, $before, $after) { + if ($before) { + $before($request, $options); + } + $response = $handler($request, $options); + if ($after) { + $after($request, $options, $response); + } + return $response; + }; + }; + } + + /** + * Middleware that handles request redirects. + * + * @return callable Returns a function that accepts the next handler. + */ + public static function redirect() + { + return function (callable $handler) { + return new RedirectMiddleware($handler); + }; + } + + /** + * Middleware that retries requests based on the boolean result of + * invoking the provided "decider" function. + * + * If no delay function is provided, a simple implementation of exponential + * backoff will be utilized. + * + * @param callable $decider Function that accepts the number of retries, + * a request, [response], and [exception] and + * returns true if the request is to be retried. + * @param callable $delay Function that accepts the number of retries and + * returns the number of milliseconds to delay. + * + * @return callable Returns a function that accepts the next handler. + */ + public static function retry(callable $decider, callable $delay = null) + { + return function (callable $handler) use ($decider, $delay) { + return new RetryMiddleware($decider, $handler, $delay); + }; + } + + /** + * Middleware that logs requests, responses, and errors using a message + * formatter. + * + * @param LoggerInterface $logger Logs messages. + * @param MessageFormatter $formatter Formatter used to create message strings. + * @param string $logLevel Level at which to log requests. + * + * @return callable Returns a function that accepts the next handler. + */ + public static function log(LoggerInterface $logger, MessageFormatter $formatter, $logLevel = 'info' /* \Psr\Log\LogLevel::INFO */) + { + return function (callable $handler) use ($logger, $formatter, $logLevel) { + return function ($request, array $options) use ($handler, $logger, $formatter, $logLevel) { + return $handler($request, $options)->then( + function ($response) use ($logger, $request, $formatter, $logLevel) { + $message = $formatter->format($request, $response); + $logger->log($logLevel, $message); + return $response; + }, + function ($reason) use ($logger, $request, $formatter) { + $response = $reason instanceof RequestException + ? $reason->getResponse() + : null; + $message = $formatter->format($request, $response, $reason); + $logger->notice($message); + return \GuzzleHttp\Promise\rejection_for($reason); + } + ); + }; + }; + } + + /** + * This middleware adds a default content-type if possible, a default + * content-length or transfer-encoding header, and the expect header. + * + * @return callable + */ + public static function prepareBody() + { + return function (callable $handler) { + return new PrepareBodyMiddleware($handler); + }; + } + + /** + * Middleware that applies a map function to the request before passing to + * the next handler. + * + * @param callable $fn Function that accepts a RequestInterface and returns + * a RequestInterface. + * @return callable + */ + public static function mapRequest(callable $fn) + { + return function (callable $handler) use ($fn) { + return function ($request, array $options) use ($handler, $fn) { + return $handler($fn($request), $options); + }; + }; + } + + /** + * Middleware that applies a map function to the resolved promise's + * response. + * + * @param callable $fn Function that accepts a ResponseInterface and + * returns a ResponseInterface. + * @return callable + */ + public static function mapResponse(callable $fn) + { + return function (callable $handler) use ($fn) { + return function ($request, array $options) use ($handler, $fn) { + return $handler($request, $options)->then($fn); + }; + }; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Pool.php b/vendor/guzzlehttp/guzzle/src/Pool.php new file mode 100644 index 0000000..5838db4 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Pool.php @@ -0,0 +1,134 @@ + $rfn) { + if ($rfn instanceof RequestInterface) { + yield $key => $client->sendAsync($rfn, $opts); + } elseif (is_callable($rfn)) { + yield $key => $rfn($opts); + } else { + throw new \InvalidArgumentException('Each value yielded by ' + . 'the iterator must be a Psr7\Http\Message\RequestInterface ' + . 'or a callable that returns a promise that fulfills ' + . 'with a Psr7\Message\Http\ResponseInterface object.'); + } + } + }; + + $this->each = new EachPromise($requests(), $config); + } + + /** + * Get promise + * + * @return PromiseInterface + */ + public function promise() + { + return $this->each->promise(); + } + + /** + * Sends multiple requests concurrently and returns an array of responses + * and exceptions that uses the same ordering as the provided requests. + * + * IMPORTANT: This method keeps every request and response in memory, and + * as such, is NOT recommended when sending a large number or an + * indeterminate number of requests concurrently. + * + * @param ClientInterface $client Client used to send the requests + * @param array|\Iterator $requests Requests to send concurrently. + * @param array $options Passes through the options available in + * {@see GuzzleHttp\Pool::__construct} + * + * @return array Returns an array containing the response or an exception + * in the same order that the requests were sent. + * @throws \InvalidArgumentException if the event format is incorrect. + */ + public static function batch( + ClientInterface $client, + $requests, + array $options = [] + ) { + $res = []; + self::cmpCallback($options, 'fulfilled', $res); + self::cmpCallback($options, 'rejected', $res); + $pool = new static($client, $requests, $options); + $pool->promise()->wait(); + ksort($res); + + return $res; + } + + /** + * Execute callback(s) + * + * @return void + */ + private static function cmpCallback(array &$options, $name, array &$results) + { + if (!isset($options[$name])) { + $options[$name] = function ($v, $k) use (&$results) { + $results[$k] = $v; + }; + } else { + $currentFn = $options[$name]; + $options[$name] = function ($v, $k) use (&$results, $currentFn) { + $currentFn($v, $k); + $results[$k] = $v; + }; + } + } +} diff --git a/vendor/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php b/vendor/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php new file mode 100644 index 0000000..568a1e9 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php @@ -0,0 +1,111 @@ +nextHandler = $nextHandler; + } + + /** + * @param RequestInterface $request + * @param array $options + * + * @return PromiseInterface + */ + public function __invoke(RequestInterface $request, array $options) + { + $fn = $this->nextHandler; + + // Don't do anything if the request has no body. + if ($request->getBody()->getSize() === 0) { + return $fn($request, $options); + } + + $modify = []; + + // Add a default content-type if possible. + if (!$request->hasHeader('Content-Type')) { + if ($uri = $request->getBody()->getMetadata('uri')) { + if ($type = Psr7\mimetype_from_filename($uri)) { + $modify['set_headers']['Content-Type'] = $type; + } + } + } + + // Add a default content-length or transfer-encoding header. + if (!$request->hasHeader('Content-Length') + && !$request->hasHeader('Transfer-Encoding') + ) { + $size = $request->getBody()->getSize(); + if ($size !== null) { + $modify['set_headers']['Content-Length'] = $size; + } else { + $modify['set_headers']['Transfer-Encoding'] = 'chunked'; + } + } + + // Add the expect header if needed. + $this->addExpectHeader($request, $options, $modify); + + return $fn(Psr7\modify_request($request, $modify), $options); + } + + /** + * Add expect header + * + * @return void + */ + private function addExpectHeader( + RequestInterface $request, + array $options, + array &$modify + ) { + // Determine if the Expect header should be used + if ($request->hasHeader('Expect')) { + return; + } + + $expect = isset($options['expect']) ? $options['expect'] : null; + + // Return if disabled or if you're not using HTTP/1.1 or HTTP/2.0 + if ($expect === false || $request->getProtocolVersion() < 1.1) { + return; + } + + // The expect header is unconditionally enabled + if ($expect === true) { + $modify['set_headers']['Expect'] = '100-Continue'; + return; + } + + // By default, send the expect header when the payload is > 1mb + if ($expect === null) { + $expect = 1048576; + } + + // Always add if the body cannot be rewound, the size cannot be + // determined, or the size is greater than the cutoff threshold + $body = $request->getBody(); + $size = $body->getSize(); + + if ($size === null || $size >= (int) $expect || !$body->isSeekable()) { + $modify['set_headers']['Expect'] = '100-Continue'; + } + } +} diff --git a/vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php b/vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php new file mode 100644 index 0000000..e4644b7 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php @@ -0,0 +1,255 @@ + 5, + 'protocols' => ['http', 'https'], + 'strict' => false, + 'referer' => false, + 'track_redirects' => false, + ]; + + /** @var callable */ + private $nextHandler; + + /** + * @param callable $nextHandler Next handler to invoke. + */ + public function __construct(callable $nextHandler) + { + $this->nextHandler = $nextHandler; + } + + /** + * @param RequestInterface $request + * @param array $options + * + * @return PromiseInterface + */ + public function __invoke(RequestInterface $request, array $options) + { + $fn = $this->nextHandler; + + if (empty($options['allow_redirects'])) { + return $fn($request, $options); + } + + if ($options['allow_redirects'] === true) { + $options['allow_redirects'] = self::$defaultSettings; + } elseif (!is_array($options['allow_redirects'])) { + throw new \InvalidArgumentException('allow_redirects must be true, false, or array'); + } else { + // Merge the default settings with the provided settings + $options['allow_redirects'] += self::$defaultSettings; + } + + if (empty($options['allow_redirects']['max'])) { + return $fn($request, $options); + } + + return $fn($request, $options) + ->then(function (ResponseInterface $response) use ($request, $options) { + return $this->checkRedirect($request, $options, $response); + }); + } + + /** + * @param RequestInterface $request + * @param array $options + * @param ResponseInterface $response + * + * @return ResponseInterface|PromiseInterface + */ + public function checkRedirect( + RequestInterface $request, + array $options, + ResponseInterface $response + ) { + if (substr($response->getStatusCode(), 0, 1) != '3' + || !$response->hasHeader('Location') + ) { + return $response; + } + + $this->guardMax($request, $options); + $nextRequest = $this->modifyRequest($request, $options, $response); + + if (isset($options['allow_redirects']['on_redirect'])) { + call_user_func( + $options['allow_redirects']['on_redirect'], + $request, + $response, + $nextRequest->getUri() + ); + } + + /** @var PromiseInterface|ResponseInterface $promise */ + $promise = $this($nextRequest, $options); + + // Add headers to be able to track history of redirects. + if (!empty($options['allow_redirects']['track_redirects'])) { + return $this->withTracking( + $promise, + (string) $nextRequest->getUri(), + $response->getStatusCode() + ); + } + + return $promise; + } + + /** + * Enable tracking on promise. + * + * @return PromiseInterface + */ + private function withTracking(PromiseInterface $promise, $uri, $statusCode) + { + return $promise->then( + function (ResponseInterface $response) use ($uri, $statusCode) { + // Note that we are pushing to the front of the list as this + // would be an earlier response than what is currently present + // in the history header. + $historyHeader = $response->getHeader(self::HISTORY_HEADER); + $statusHeader = $response->getHeader(self::STATUS_HISTORY_HEADER); + array_unshift($historyHeader, $uri); + array_unshift($statusHeader, $statusCode); + return $response->withHeader(self::HISTORY_HEADER, $historyHeader) + ->withHeader(self::STATUS_HISTORY_HEADER, $statusHeader); + } + ); + } + + /** + * Check for too many redirects + * + * @return void + * + * @throws TooManyRedirectsException Too many redirects. + */ + private function guardMax(RequestInterface $request, array &$options) + { + $current = isset($options['__redirect_count']) + ? $options['__redirect_count'] + : 0; + $options['__redirect_count'] = $current + 1; + $max = $options['allow_redirects']['max']; + + if ($options['__redirect_count'] > $max) { + throw new TooManyRedirectsException( + "Will not follow more than {$max} redirects", + $request + ); + } + } + + /** + * @param RequestInterface $request + * @param array $options + * @param ResponseInterface $response + * + * @return RequestInterface + */ + public function modifyRequest( + RequestInterface $request, + array $options, + ResponseInterface $response + ) { + // Request modifications to apply. + $modify = []; + $protocols = $options['allow_redirects']['protocols']; + + // Use a GET request if this is an entity enclosing request and we are + // not forcing RFC compliance, but rather emulating what all browsers + // would do. + $statusCode = $response->getStatusCode(); + if ($statusCode == 303 || + ($statusCode <= 302 && !$options['allow_redirects']['strict']) + ) { + $modify['method'] = 'GET'; + $modify['body'] = ''; + } + + $uri = $this->redirectUri($request, $response, $protocols); + if (isset($options['idn_conversion']) && ($options['idn_conversion'] !== false)) { + $idnOptions = ($options['idn_conversion'] === true) ? IDNA_DEFAULT : $options['idn_conversion']; + $uri = Utils::idnUriConvert($uri, $idnOptions); + } + + $modify['uri'] = $uri; + Psr7\rewind_body($request); + + // Add the Referer header if it is told to do so and only + // add the header if we are not redirecting from https to http. + if ($options['allow_redirects']['referer'] + && $modify['uri']->getScheme() === $request->getUri()->getScheme() + ) { + $uri = $request->getUri()->withUserInfo(''); + $modify['set_headers']['Referer'] = (string) $uri; + } else { + $modify['remove_headers'][] = 'Referer'; + } + + // Remove Authorization header if host is different. + if ($request->getUri()->getHost() !== $modify['uri']->getHost()) { + $modify['remove_headers'][] = 'Authorization'; + } + + return Psr7\modify_request($request, $modify); + } + + /** + * Set the appropriate URL on the request based on the location header + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @param array $protocols + * + * @return UriInterface + */ + private function redirectUri( + RequestInterface $request, + ResponseInterface $response, + array $protocols + ) { + $location = Psr7\UriResolver::resolve( + $request->getUri(), + new Psr7\Uri($response->getHeaderLine('Location')) + ); + + // Ensure that the redirect URI is allowed based on the protocols. + if (!in_array($location->getScheme(), $protocols)) { + throw new BadResponseException( + sprintf( + 'Redirect URI, %s, does not use one of the allowed redirect protocols: %s', + $location, + implode(', ', $protocols) + ), + $request, + $response + ); + } + + return $location; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/RequestOptions.php b/vendor/guzzlehttp/guzzle/src/RequestOptions.php new file mode 100644 index 0000000..355f658 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/RequestOptions.php @@ -0,0 +1,263 @@ +decider = $decider; + $this->nextHandler = $nextHandler; + $this->delay = $delay ?: __CLASS__ . '::exponentialDelay'; + } + + /** + * Default exponential backoff delay function. + * + * @param int $retries + * + * @return int milliseconds. + */ + public static function exponentialDelay($retries) + { + return (int) pow(2, $retries - 1) * 1000; + } + + /** + * @param RequestInterface $request + * @param array $options + * + * @return PromiseInterface + */ + public function __invoke(RequestInterface $request, array $options) + { + if (!isset($options['retries'])) { + $options['retries'] = 0; + } + + $fn = $this->nextHandler; + return $fn($request, $options) + ->then( + $this->onFulfilled($request, $options), + $this->onRejected($request, $options) + ); + } + + /** + * Execute fulfilled closure + * + * @return mixed + */ + private function onFulfilled(RequestInterface $req, array $options) + { + return function ($value) use ($req, $options) { + if (!call_user_func( + $this->decider, + $options['retries'], + $req, + $value, + null + )) { + return $value; + } + return $this->doRetry($req, $options, $value); + }; + } + + /** + * Execute rejected closure + * + * @return callable + */ + private function onRejected(RequestInterface $req, array $options) + { + return function ($reason) use ($req, $options) { + if (!call_user_func( + $this->decider, + $options['retries'], + $req, + null, + $reason + )) { + return \GuzzleHttp\Promise\rejection_for($reason); + } + return $this->doRetry($req, $options); + }; + } + + /** + * @return self + */ + private function doRetry(RequestInterface $request, array $options, ResponseInterface $response = null) + { + $options['delay'] = call_user_func($this->delay, ++$options['retries'], $response); + + return $this($request, $options); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/TransferStats.php b/vendor/guzzlehttp/guzzle/src/TransferStats.php new file mode 100644 index 0000000..87fb3c0 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/TransferStats.php @@ -0,0 +1,126 @@ +request = $request; + $this->response = $response; + $this->transferTime = $transferTime; + $this->handlerErrorData = $handlerErrorData; + $this->handlerStats = $handlerStats; + } + + /** + * @return RequestInterface + */ + public function getRequest() + { + return $this->request; + } + + /** + * Returns the response that was received (if any). + * + * @return ResponseInterface|null + */ + public function getResponse() + { + return $this->response; + } + + /** + * Returns true if a response was received. + * + * @return bool + */ + public function hasResponse() + { + return $this->response !== null; + } + + /** + * Gets handler specific error data. + * + * This might be an exception, a integer representing an error code, or + * anything else. Relying on this value assumes that you know what handler + * you are using. + * + * @return mixed + */ + public function getHandlerErrorData() + { + return $this->handlerErrorData; + } + + /** + * Get the effective URI the request was sent to. + * + * @return UriInterface + */ + public function getEffectiveUri() + { + return $this->request->getUri(); + } + + /** + * Get the estimated time the request was being transferred by the handler. + * + * @return float|null Time in seconds. + */ + public function getTransferTime() + { + return $this->transferTime; + } + + /** + * Gets an array of all of the handler specific transfer data. + * + * @return array + */ + public function getHandlerStats() + { + return $this->handlerStats; + } + + /** + * Get a specific handler statistic from the handler by name. + * + * @param string $stat Handler specific transfer stat to retrieve. + * + * @return mixed|null + */ + public function getHandlerStat($stat) + { + return isset($this->handlerStats[$stat]) + ? $this->handlerStats[$stat] + : null; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/UriTemplate.php b/vendor/guzzlehttp/guzzle/src/UriTemplate.php new file mode 100644 index 0000000..96dcfd0 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/UriTemplate.php @@ -0,0 +1,237 @@ + ['prefix' => '', 'joiner' => ',', 'query' => false], + '+' => ['prefix' => '', 'joiner' => ',', 'query' => false], + '#' => ['prefix' => '#', 'joiner' => ',', 'query' => false], + '.' => ['prefix' => '.', 'joiner' => '.', 'query' => false], + '/' => ['prefix' => '/', 'joiner' => '/', 'query' => false], + ';' => ['prefix' => ';', 'joiner' => ';', 'query' => true], + '?' => ['prefix' => '?', 'joiner' => '&', 'query' => true], + '&' => ['prefix' => '&', 'joiner' => '&', 'query' => true] + ]; + + /** @var array Delimiters */ + private static $delims = [':', '/', '?', '#', '[', ']', '@', '!', '$', + '&', '\'', '(', ')', '*', '+', ',', ';', '=']; + + /** @var array Percent encoded delimiters */ + private static $delimsPct = ['%3A', '%2F', '%3F', '%23', '%5B', '%5D', + '%40', '%21', '%24', '%26', '%27', '%28', '%29', '%2A', '%2B', '%2C', + '%3B', '%3D']; + + public function expand($template, array $variables) + { + if (false === strpos($template, '{')) { + return $template; + } + + $this->template = $template; + $this->variables = $variables; + + return preg_replace_callback( + '/\{([^\}]+)\}/', + [$this, 'expandMatch'], + $this->template + ); + } + + /** + * Parse an expression into parts + * + * @param string $expression Expression to parse + * + * @return array Returns an associative array of parts + */ + private function parseExpression($expression) + { + $result = []; + + if (isset(self::$operatorHash[$expression[0]])) { + $result['operator'] = $expression[0]; + $expression = substr($expression, 1); + } else { + $result['operator'] = ''; + } + + foreach (explode(',', $expression) as $value) { + $value = trim($value); + $varspec = []; + if ($colonPos = strpos($value, ':')) { + $varspec['value'] = substr($value, 0, $colonPos); + $varspec['modifier'] = ':'; + $varspec['position'] = (int) substr($value, $colonPos + 1); + } elseif (substr($value, -1) === '*') { + $varspec['modifier'] = '*'; + $varspec['value'] = substr($value, 0, -1); + } else { + $varspec['value'] = (string) $value; + $varspec['modifier'] = ''; + } + $result['values'][] = $varspec; + } + + return $result; + } + + /** + * Process an expansion + * + * @param array $matches Matches met in the preg_replace_callback + * + * @return string Returns the replacement string + */ + private function expandMatch(array $matches) + { + static $rfc1738to3986 = ['+' => '%20', '%7e' => '~']; + + $replacements = []; + $parsed = self::parseExpression($matches[1]); + $prefix = self::$operatorHash[$parsed['operator']]['prefix']; + $joiner = self::$operatorHash[$parsed['operator']]['joiner']; + $useQuery = self::$operatorHash[$parsed['operator']]['query']; + + foreach ($parsed['values'] as $value) { + if (!isset($this->variables[$value['value']])) { + continue; + } + + $variable = $this->variables[$value['value']]; + $actuallyUseQuery = $useQuery; + $expanded = ''; + + if (is_array($variable)) { + $isAssoc = $this->isAssoc($variable); + $kvp = []; + foreach ($variable as $key => $var) { + if ($isAssoc) { + $key = rawurlencode($key); + $isNestedArray = is_array($var); + } else { + $isNestedArray = false; + } + + if (!$isNestedArray) { + $var = rawurlencode($var); + if ($parsed['operator'] === '+' || + $parsed['operator'] === '#' + ) { + $var = $this->decodeReserved($var); + } + } + + if ($value['modifier'] === '*') { + if ($isAssoc) { + if ($isNestedArray) { + // Nested arrays must allow for deeply nested + // structures. + $var = strtr( + http_build_query([$key => $var]), + $rfc1738to3986 + ); + } else { + $var = $key . '=' . $var; + } + } elseif ($key > 0 && $actuallyUseQuery) { + $var = $value['value'] . '=' . $var; + } + } + + $kvp[$key] = $var; + } + + if (empty($variable)) { + $actuallyUseQuery = false; + } elseif ($value['modifier'] === '*') { + $expanded = implode($joiner, $kvp); + if ($isAssoc) { + // Don't prepend the value name when using the explode + // modifier with an associative array. + $actuallyUseQuery = false; + } + } else { + if ($isAssoc) { + // When an associative array is encountered and the + // explode modifier is not set, then the result must be + // a comma separated list of keys followed by their + // respective values. + foreach ($kvp as $k => &$v) { + $v = $k . ',' . $v; + } + } + $expanded = implode(',', $kvp); + } + } else { + if ($value['modifier'] === ':') { + $variable = substr($variable, 0, $value['position']); + } + $expanded = rawurlencode($variable); + if ($parsed['operator'] === '+' || $parsed['operator'] === '#') { + $expanded = $this->decodeReserved($expanded); + } + } + + if ($actuallyUseQuery) { + if (!$expanded && $joiner !== '&') { + $expanded = $value['value']; + } else { + $expanded = $value['value'] . '=' . $expanded; + } + } + + $replacements[] = $expanded; + } + + $ret = implode($joiner, $replacements); + if ($ret && $prefix) { + return $prefix . $ret; + } + + return $ret; + } + + /** + * Determines if an array is associative. + * + * This makes the assumption that input arrays are sequences or hashes. + * This assumption is a tradeoff for accuracy in favor of speed, but it + * should work in almost every case where input is supplied for a URI + * template. + * + * @param array $array Array to check + * + * @return bool + */ + private function isAssoc(array $array) + { + return $array && array_keys($array)[0] !== 0; + } + + /** + * Removes percent encoding on reserved characters (used with + and # + * modifiers). + * + * @param string $string String to fix + * + * @return string + */ + private function decodeReserved($string) + { + return str_replace(self::$delimsPct, self::$delims, $string); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Utils.php b/vendor/guzzlehttp/guzzle/src/Utils.php new file mode 100644 index 0000000..c698acb --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Utils.php @@ -0,0 +1,92 @@ +getHost()) { + $asciiHost = self::idnToAsci($uri->getHost(), $options, $info); + if ($asciiHost === false) { + $errorBitSet = isset($info['errors']) ? $info['errors'] : 0; + + $errorConstants = array_filter(array_keys(get_defined_constants()), function ($name) { + return substr($name, 0, 11) === 'IDNA_ERROR_'; + }); + + $errors = []; + foreach ($errorConstants as $errorConstant) { + if ($errorBitSet & constant($errorConstant)) { + $errors[] = $errorConstant; + } + } + + $errorMessage = 'IDN conversion failed'; + if ($errors) { + $errorMessage .= ' (errors: ' . implode(', ', $errors) . ')'; + } + + throw new InvalidArgumentException($errorMessage); + } else { + if ($uri->getHost() !== $asciiHost) { + // Replace URI only if the ASCII version is different + $uri = $uri->withHost($asciiHost); + } + } + } + + return $uri; + } + + /** + * @param string $domain + * @param int $options + * @param array $info + * + * @return string|false + */ + private static function idnToAsci($domain, $options, &$info = []) + { + if (\preg_match('%^[ -~]+$%', $domain) === 1) { + return $domain; + } + + if (\extension_loaded('intl') && defined('INTL_IDNA_VARIANT_UTS46')) { + return \idn_to_ascii($domain, $options, INTL_IDNA_VARIANT_UTS46, $info); + } + + /* + * The Idn class is marked as @internal. Verify that class and method exists. + */ + if (method_exists(Idn::class, 'idn_to_ascii')) { + return Idn::idn_to_ascii($domain, $options, Idn::INTL_IDNA_VARIANT_UTS46, $info); + } + + throw new \RuntimeException('ext-intl or symfony/polyfill-intl-idn not loaded or too old'); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/functions.php b/vendor/guzzlehttp/guzzle/src/functions.php new file mode 100644 index 0000000..c2afd8c --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/functions.php @@ -0,0 +1,334 @@ +expand($template, $variables); +} + +/** + * Debug function used to describe the provided value type and class. + * + * @param mixed $input + * + * @return string Returns a string containing the type of the variable and + * if a class is provided, the class name. + */ +function describe_type($input) +{ + switch (gettype($input)) { + case 'object': + return 'object(' . get_class($input) . ')'; + case 'array': + return 'array(' . count($input) . ')'; + default: + ob_start(); + var_dump($input); + // normalize float vs double + return str_replace('double(', 'float(', rtrim(ob_get_clean())); + } +} + +/** + * Parses an array of header lines into an associative array of headers. + * + * @param iterable $lines Header lines array of strings in the following + * format: "Name: Value" + * @return array + */ +function headers_from_lines($lines) +{ + $headers = []; + + foreach ($lines as $line) { + $parts = explode(':', $line, 2); + $headers[trim($parts[0])][] = isset($parts[1]) + ? trim($parts[1]) + : null; + } + + return $headers; +} + +/** + * Returns a debug stream based on the provided variable. + * + * @param mixed $value Optional value + * + * @return resource + */ +function debug_resource($value = null) +{ + if (is_resource($value)) { + return $value; + } elseif (defined('STDOUT')) { + return STDOUT; + } + + return fopen('php://output', 'w'); +} + +/** + * Chooses and creates a default handler to use based on the environment. + * + * The returned handler is not wrapped by any default middlewares. + * + * @return callable Returns the best handler for the given system. + * @throws \RuntimeException if no viable Handler is available. + */ +function choose_handler() +{ + $handler = null; + if (function_exists('curl_multi_exec') && function_exists('curl_exec')) { + $handler = Proxy::wrapSync(new CurlMultiHandler(), new CurlHandler()); + } elseif (function_exists('curl_exec')) { + $handler = new CurlHandler(); + } elseif (function_exists('curl_multi_exec')) { + $handler = new CurlMultiHandler(); + } + + if (ini_get('allow_url_fopen')) { + $handler = $handler + ? Proxy::wrapStreaming($handler, new StreamHandler()) + : new StreamHandler(); + } elseif (!$handler) { + throw new \RuntimeException('GuzzleHttp requires cURL, the ' + . 'allow_url_fopen ini setting, or a custom HTTP handler.'); + } + + return $handler; +} + +/** + * Get the default User-Agent string to use with Guzzle + * + * @return string + */ +function default_user_agent() +{ + static $defaultAgent = ''; + + if (!$defaultAgent) { + $defaultAgent = 'GuzzleHttp/' . Client::VERSION; + if (extension_loaded('curl') && function_exists('curl_version')) { + $defaultAgent .= ' curl/' . \curl_version()['version']; + } + $defaultAgent .= ' PHP/' . PHP_VERSION; + } + + return $defaultAgent; +} + +/** + * Returns the default cacert bundle for the current system. + * + * First, the openssl.cafile and curl.cainfo php.ini settings are checked. + * If those settings are not configured, then the common locations for + * bundles found on Red Hat, CentOS, Fedora, Ubuntu, Debian, FreeBSD, OS X + * and Windows are checked. If any of these file locations are found on + * disk, they will be utilized. + * + * Note: the result of this function is cached for subsequent calls. + * + * @return string + * @throws \RuntimeException if no bundle can be found. + */ +function default_ca_bundle() +{ + static $cached = null; + static $cafiles = [ + // Red Hat, CentOS, Fedora (provided by the ca-certificates package) + '/etc/pki/tls/certs/ca-bundle.crt', + // Ubuntu, Debian (provided by the ca-certificates package) + '/etc/ssl/certs/ca-certificates.crt', + // FreeBSD (provided by the ca_root_nss package) + '/usr/local/share/certs/ca-root-nss.crt', + // SLES 12 (provided by the ca-certificates package) + '/var/lib/ca-certificates/ca-bundle.pem', + // OS X provided by homebrew (using the default path) + '/usr/local/etc/openssl/cert.pem', + // Google app engine + '/etc/ca-certificates.crt', + // Windows? + 'C:\\windows\\system32\\curl-ca-bundle.crt', + 'C:\\windows\\curl-ca-bundle.crt', + ]; + + if ($cached) { + return $cached; + } + + if ($ca = ini_get('openssl.cafile')) { + return $cached = $ca; + } + + if ($ca = ini_get('curl.cainfo')) { + return $cached = $ca; + } + + foreach ($cafiles as $filename) { + if (file_exists($filename)) { + return $cached = $filename; + } + } + + throw new \RuntimeException( + <<< EOT +No system CA bundle could be found in any of the the common system locations. +PHP versions earlier than 5.6 are not properly configured to use the system's +CA bundle by default. In order to verify peer certificates, you will need to +supply the path on disk to a certificate bundle to the 'verify' request +option: http://docs.guzzlephp.org/en/latest/clients.html#verify. If you do not +need a specific certificate bundle, then Mozilla provides a commonly used CA +bundle which can be downloaded here (provided by the maintainer of cURL): +https://raw.githubusercontent.com/bagder/ca-bundle/master/ca-bundle.crt. Once +you have a CA bundle available on disk, you can set the 'openssl.cafile' PHP +ini setting to point to the path to the file, allowing you to omit the 'verify' +request option. See http://curl.haxx.se/docs/sslcerts.html for more +information. +EOT + ); +} + +/** + * Creates an associative array of lowercase header names to the actual + * header casing. + * + * @param array $headers + * + * @return array + */ +function normalize_header_keys(array $headers) +{ + $result = []; + foreach (array_keys($headers) as $key) { + $result[strtolower($key)] = $key; + } + + return $result; +} + +/** + * Returns true if the provided host matches any of the no proxy areas. + * + * This method will strip a port from the host if it is present. Each pattern + * can be matched with an exact match (e.g., "foo.com" == "foo.com") or a + * partial match: (e.g., "foo.com" == "baz.foo.com" and ".foo.com" == + * "baz.foo.com", but ".foo.com" != "foo.com"). + * + * Areas are matched in the following cases: + * 1. "*" (without quotes) always matches any hosts. + * 2. An exact match. + * 3. The area starts with "." and the area is the last part of the host. e.g. + * '.mit.edu' will match any host that ends with '.mit.edu'. + * + * @param string $host Host to check against the patterns. + * @param array $noProxyArray An array of host patterns. + * + * @return bool + */ +function is_host_in_noproxy($host, array $noProxyArray) +{ + if (strlen($host) === 0) { + throw new \InvalidArgumentException('Empty host provided'); + } + + // Strip port if present. + if (strpos($host, ':')) { + $host = explode($host, ':', 2)[0]; + } + + foreach ($noProxyArray as $area) { + // Always match on wildcards. + if ($area === '*') { + return true; + } elseif (empty($area)) { + // Don't match on empty values. + continue; + } elseif ($area === $host) { + // Exact matches. + return true; + } else { + // Special match if the area when prefixed with ".". Remove any + // existing leading "." and add a new leading ".". + $area = '.' . ltrim($area, '.'); + if (substr($host, -(strlen($area))) === $area) { + return true; + } + } + } + + return false; +} + +/** + * Wrapper for json_decode that throws when an error occurs. + * + * @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. + * @param int $options Bitmask of JSON decode options. + * + * @return mixed + * @throws Exception\InvalidArgumentException if the JSON cannot be decoded. + * @link http://www.php.net/manual/en/function.json-decode.php + */ +function json_decode($json, $assoc = false, $depth = 512, $options = 0) +{ + $data = \json_decode($json, $assoc, $depth, $options); + if (JSON_ERROR_NONE !== json_last_error()) { + throw new Exception\InvalidArgumentException( + 'json_decode error: ' . json_last_error_msg() + ); + } + + return $data; +} + +/** + * Wrapper for JSON encoding that throws when an error occurs. + * + * @param mixed $value The value being encoded + * @param int $options JSON encode option bitmask + * @param int $depth Set the maximum depth. Must be greater than zero. + * + * @return string + * @throws Exception\InvalidArgumentException if the JSON cannot be encoded. + * @link http://www.php.net/manual/en/function.json-encode.php + */ +function json_encode($value, $options = 0, $depth = 512) +{ + $json = \json_encode($value, $options, $depth); + if (JSON_ERROR_NONE !== json_last_error()) { + throw new Exception\InvalidArgumentException( + 'json_encode error: ' . json_last_error_msg() + ); + } + + return $json; +} diff --git a/vendor/guzzlehttp/guzzle/src/functions_include.php b/vendor/guzzlehttp/guzzle/src/functions_include.php new file mode 100644 index 0000000..a93393a --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/functions_include.php @@ -0,0 +1,6 @@ + +Copyright (c) 2015 Graham Campbell +Copyright (c) 2017 Tobias Schultze +Copyright (c) 2020 Tobias Nyholm + +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/guzzlehttp/promises/Makefile b/vendor/guzzlehttp/promises/Makefile new file mode 100644 index 0000000..8d5b3ef --- /dev/null +++ b/vendor/guzzlehttp/promises/Makefile @@ -0,0 +1,13 @@ +all: clean test + +test: + vendor/bin/phpunit + +coverage: + vendor/bin/phpunit --coverage-html=artifacts/coverage + +view-coverage: + open artifacts/coverage/index.html + +clean: + rm -rf artifacts/* diff --git a/vendor/guzzlehttp/promises/README.md b/vendor/guzzlehttp/promises/README.md new file mode 100644 index 0000000..c175fec --- /dev/null +++ b/vendor/guzzlehttp/promises/README.md @@ -0,0 +1,547 @@ +# Guzzle Promises + +[Promises/A+](https://promisesaplus.com/) implementation that handles promise +chaining and resolution iteratively, allowing for "infinite" promise chaining +while keeping the stack size constant. Read [this blog post](https://blog.domenic.me/youre-missing-the-point-of-promises/) +for a general introduction to promises. + +- [Features](#features) +- [Quick start](#quick-start) +- [Synchronous wait](#synchronous-wait) +- [Cancellation](#cancellation) +- [API](#api) + - [Promise](#promise) + - [FulfilledPromise](#fulfilledpromise) + - [RejectedPromise](#rejectedpromise) +- [Promise interop](#promise-interop) +- [Implementation notes](#implementation-notes) + + +# Features + +- [Promises/A+](https://promisesaplus.com/) implementation. +- Promise resolution and chaining is handled iteratively, allowing for + "infinite" promise chaining. +- Promises have a synchronous `wait` method. +- Promises can be cancelled. +- Works with any object that has a `then` function. +- C# style async/await coroutine promises using + `GuzzleHttp\Promise\Coroutine::of()`. + + +# Quick start + +A *promise* represents the eventual result of an asynchronous operation. The +primary way of interacting with a promise is through its `then` method, which +registers callbacks to receive either a promise's eventual value or the reason +why the promise cannot be fulfilled. + + +## Callbacks + +Callbacks are registered with the `then` method by providing an optional +`$onFulfilled` followed by an optional `$onRejected` function. + + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$promise->then( + // $onFulfilled + function ($value) { + echo 'The promise was fulfilled.'; + }, + // $onRejected + function ($reason) { + echo 'The promise was rejected.'; + } +); +``` + +*Resolving* a promise means that you either fulfill a promise with a *value* or +reject a promise with a *reason*. Resolving a promises triggers callbacks +registered with the promises's `then` method. These callbacks are triggered +only once and in the order in which they were added. + + +## Resolving a promise + +Promises are fulfilled using the `resolve($value)` method. Resolving a promise +with any value other than a `GuzzleHttp\Promise\RejectedPromise` will trigger +all of the onFulfilled callbacks (resolving a promise with a rejected promise +will reject the promise and trigger the `$onRejected` callbacks). + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$promise + ->then(function ($value) { + // Return a value and don't break the chain + return "Hello, " . $value; + }) + // This then is executed after the first then and receives the value + // returned from the first then. + ->then(function ($value) { + echo $value; + }); + +// Resolving the promise triggers the $onFulfilled callbacks and outputs +// "Hello, reader." +$promise->resolve('reader.'); +``` + + +## Promise forwarding + +Promises can be chained one after the other. Each then in the chain is a new +promise. The return value of a promise is what's forwarded to the next +promise in the chain. Returning a promise in a `then` callback will cause the +subsequent promises in the chain to only be fulfilled when the returned promise +has been fulfilled. The next promise in the chain will be invoked with the +resolved value of the promise. + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$nextPromise = new Promise(); + +$promise + ->then(function ($value) use ($nextPromise) { + echo $value; + return $nextPromise; + }) + ->then(function ($value) { + echo $value; + }); + +// Triggers the first callback and outputs "A" +$promise->resolve('A'); +// Triggers the second callback and outputs "B" +$nextPromise->resolve('B'); +``` + +## Promise rejection + +When a promise is rejected, the `$onRejected` callbacks are invoked with the +rejection reason. + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$promise->then(null, function ($reason) { + echo $reason; +}); + +$promise->reject('Error!'); +// Outputs "Error!" +``` + +## Rejection forwarding + +If an exception is thrown in an `$onRejected` callback, subsequent +`$onRejected` callbacks are invoked with the thrown exception as the reason. + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$promise->then(null, function ($reason) { + throw new Exception($reason); +})->then(null, function ($reason) { + assert($reason->getMessage() === 'Error!'); +}); + +$promise->reject('Error!'); +``` + +You can also forward a rejection down the promise chain by returning a +`GuzzleHttp\Promise\RejectedPromise` in either an `$onFulfilled` or +`$onRejected` callback. + +```php +use GuzzleHttp\Promise\Promise; +use GuzzleHttp\Promise\RejectedPromise; + +$promise = new Promise(); +$promise->then(null, function ($reason) { + return new RejectedPromise($reason); +})->then(null, function ($reason) { + assert($reason === 'Error!'); +}); + +$promise->reject('Error!'); +``` + +If an exception is not thrown in a `$onRejected` callback and the callback +does not return a rejected promise, downstream `$onFulfilled` callbacks are +invoked using the value returned from the `$onRejected` callback. + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$promise + ->then(null, function ($reason) { + return "It's ok"; + }) + ->then(function ($value) { + assert($value === "It's ok"); + }); + +$promise->reject('Error!'); +``` + +# Synchronous wait + +You can synchronously force promises to complete using a promise's `wait` +method. When creating a promise, you can provide a wait function that is used +to synchronously force a promise to complete. When a wait function is invoked +it is expected to deliver a value to the promise or reject the promise. If the +wait function does not deliver a value, then an exception is thrown. The wait +function provided to a promise constructor is invoked when the `wait` function +of the promise is called. + +```php +$promise = new Promise(function () use (&$promise) { + $promise->resolve('foo'); +}); + +// Calling wait will return the value of the promise. +echo $promise->wait(); // outputs "foo" +``` + +If an exception is encountered while invoking the wait function of a promise, +the promise is rejected with the exception and the exception is thrown. + +```php +$promise = new Promise(function () use (&$promise) { + throw new Exception('foo'); +}); + +$promise->wait(); // throws the exception. +``` + +Calling `wait` on a promise that has been fulfilled will not trigger the wait +function. It will simply return the previously resolved value. + +```php +$promise = new Promise(function () { die('this is not called!'); }); +$promise->resolve('foo'); +echo $promise->wait(); // outputs "foo" +``` + +Calling `wait` on a promise that has been rejected will throw an exception. If +the rejection reason is an instance of `\Exception` the reason is thrown. +Otherwise, a `GuzzleHttp\Promise\RejectionException` is thrown and the reason +can be obtained by calling the `getReason` method of the exception. + +```php +$promise = new Promise(); +$promise->reject('foo'); +$promise->wait(); +``` + +> PHP Fatal error: Uncaught exception 'GuzzleHttp\Promise\RejectionException' with message 'The promise was rejected with value: foo' + + +## Unwrapping a promise + +When synchronously waiting on a promise, you are joining the state of the +promise into the current state of execution (i.e., return the value of the +promise if it was fulfilled or throw an exception if it was rejected). This is +called "unwrapping" the promise. Waiting on a promise will by default unwrap +the promise state. + +You can force a promise to resolve and *not* unwrap the state of the promise +by passing `false` to the first argument of the `wait` function: + +```php +$promise = new Promise(); +$promise->reject('foo'); +// This will not throw an exception. It simply ensures the promise has +// been resolved. +$promise->wait(false); +``` + +When unwrapping a promise, the resolved value of the promise will be waited +upon until the unwrapped value is not a promise. This means that if you resolve +promise A with a promise B and unwrap promise A, the value returned by the +wait function will be the value delivered to promise B. + +**Note**: when you do not unwrap the promise, no value is returned. + + +# Cancellation + +You can cancel a promise that has not yet been fulfilled using the `cancel()` +method of a promise. When creating a promise you can provide an optional +cancel function that when invoked cancels the action of computing a resolution +of the promise. + + +# API + + +## Promise + +When creating a promise object, you can provide an optional `$waitFn` and +`$cancelFn`. `$waitFn` is a function that is invoked with no arguments and is +expected to resolve the promise. `$cancelFn` is a function with no arguments +that is expected to cancel the computation of a promise. It is invoked when the +`cancel()` method of a promise is called. + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise( + function () use (&$promise) { + $promise->resolve('waited'); + }, + function () { + // do something that will cancel the promise computation (e.g., close + // a socket, cancel a database query, etc...) + } +); + +assert('waited' === $promise->wait()); +``` + +A promise has the following methods: + +- `then(callable $onFulfilled, callable $onRejected) : PromiseInterface` + + Appends fulfillment and rejection handlers to the promise, and returns a new promise resolving to the return value of the called handler. + +- `otherwise(callable $onRejected) : PromiseInterface` + + Appends a rejection handler callback to the promise, and returns a new promise resolving to the return value of the callback if it is called, or to its original fulfillment value if the promise is instead fulfilled. + +- `wait($unwrap = true) : mixed` + + Synchronously waits on the promise to complete. + + `$unwrap` controls whether or not the value of the promise is returned for a + fulfilled promise or if an exception is thrown if the promise is rejected. + This is set to `true` by default. + +- `cancel()` + + Attempts to cancel the promise if possible. The promise being cancelled and + the parent most ancestor that has not yet been resolved will also be + cancelled. Any promises waiting on the cancelled promise to resolve will also + be cancelled. + +- `getState() : string` + + Returns the state of the promise. One of `pending`, `fulfilled`, or + `rejected`. + +- `resolve($value)` + + Fulfills the promise with the given `$value`. + +- `reject($reason)` + + Rejects the promise with the given `$reason`. + + +## FulfilledPromise + +A fulfilled promise can be created to represent a promise that has been +fulfilled. + +```php +use GuzzleHttp\Promise\FulfilledPromise; + +$promise = new FulfilledPromise('value'); + +// Fulfilled callbacks are immediately invoked. +$promise->then(function ($value) { + echo $value; +}); +``` + + +## RejectedPromise + +A rejected promise can be created to represent a promise that has been +rejected. + +```php +use GuzzleHttp\Promise\RejectedPromise; + +$promise = new RejectedPromise('Error'); + +// Rejected callbacks are immediately invoked. +$promise->then(null, function ($reason) { + echo $reason; +}); +``` + + +# Promise interop + +This library works with foreign promises that have a `then` method. This means +you can use Guzzle promises with [React promises](https://github.com/reactphp/promise) +for example. When a foreign promise is returned inside of a then method +callback, promise resolution will occur recursively. + +```php +// Create a React promise +$deferred = new React\Promise\Deferred(); +$reactPromise = $deferred->promise(); + +// Create a Guzzle promise that is fulfilled with a React promise. +$guzzlePromise = new GuzzleHttp\Promise\Promise(); +$guzzlePromise->then(function ($value) use ($reactPromise) { + // Do something something with the value... + // Return the React promise + return $reactPromise; +}); +``` + +Please note that wait and cancel chaining is no longer possible when forwarding +a foreign promise. You will need to wrap a third-party promise with a Guzzle +promise in order to utilize wait and cancel functions with foreign promises. + + +## Event Loop Integration + +In order to keep the stack size constant, Guzzle promises are resolved +asynchronously using a task queue. When waiting on promises synchronously, the +task queue will be automatically run to ensure that the blocking promise and +any forwarded promises are resolved. When using promises asynchronously in an +event loop, you will need to run the task queue on each tick of the loop. If +you do not run the task queue, then promises will not be resolved. + +You can run the task queue using the `run()` method of the global task queue +instance. + +```php +// Get the global task queue +$queue = GuzzleHttp\Promise\Utils::queue(); +$queue->run(); +``` + +For example, you could use Guzzle promises with React using a periodic timer: + +```php +$loop = React\EventLoop\Factory::create(); +$loop->addPeriodicTimer(0, [$queue, 'run']); +``` + +*TODO*: Perhaps adding a `futureTick()` on each tick would be faster? + + +# Implementation notes + + +## Promise resolution and chaining is handled iteratively + +By shuffling pending handlers from one owner to another, promises are +resolved iteratively, allowing for "infinite" then chaining. + +```php +then(function ($v) { + // The stack size remains constant (a good thing) + echo xdebug_get_stack_depth() . ', '; + return $v + 1; + }); +} + +$parent->resolve(0); +var_dump($p->wait()); // int(1000) + +``` + +When a promise is fulfilled or rejected with a non-promise value, the promise +then takes ownership of the handlers of each child promise and delivers values +down the chain without using recursion. + +When a promise is resolved with another promise, the original promise transfers +all of its pending handlers to the new promise. When the new promise is +eventually resolved, all of the pending handlers are delivered the forwarded +value. + + +## A promise is the deferred. + +Some promise libraries implement promises using a deferred object to represent +a computation and a promise object to represent the delivery of the result of +the computation. This is a nice separation of computation and delivery because +consumers of the promise cannot modify the value that will be eventually +delivered. + +One side effect of being able to implement promise resolution and chaining +iteratively is that you need to be able for one promise to reach into the state +of another promise to shuffle around ownership of handlers. In order to achieve +this without making the handlers of a promise publicly mutable, a promise is +also the deferred value, allowing promises of the same parent class to reach +into and modify the private properties of promises of the same type. While this +does allow consumers of the value to modify the resolution or rejection of the +deferred, it is a small price to pay for keeping the stack size constant. + +```php +$promise = new Promise(); +$promise->then(function ($value) { echo $value; }); +// The promise is the deferred value, so you can deliver a value to it. +$promise->resolve('foo'); +// prints "foo" +``` + + +## Upgrading from Function API + +A static API was first introduced in 1.4.0, in order to mitigate problems with functions conflicting between global and local copies of the package. The function API will be removed in 2.0.0. A migration table has been provided here for your convenience: + +| Original Function | Replacement Method | +|----------------|----------------| +| `queue` | `Utils::queue` | +| `task` | `Utils::task` | +| `promise_for` | `Create::promiseFor` | +| `rejection_for` | `Create::rejectionFor` | +| `exception_for` | `Create::exceptionFor` | +| `iter_for` | `Create::iterFor` | +| `inspect` | `Utils::inspect` | +| `inspect_all` | `Utils::inspectAll` | +| `unwrap` | `Utils::unwrap` | +| `all` | `Utils::all` | +| `some` | `Utils::some` | +| `any` | `Utils::any` | +| `settle` | `Utils::settle` | +| `each` | `Each::of` | +| `each_limit` | `Each::ofLimit` | +| `each_limit_all` | `Each::ofLimitAll` | +| `!is_fulfilled` | `Is::pending` | +| `is_fulfilled` | `Is::fulfilled` | +| `is_rejected` | `Is::rejected` | +| `is_settled` | `Is::settled` | +| `coroutine` | `Coroutine::of` | + + +## Security + +If you discover a security vulnerability within this package, please send an email to security@tidelift.com. All security vulnerabilities will be promptly addressed. Please do not disclose security-related issues publicly until a fix has been announced. Please see [Security Policy](https://github.com/guzzle/promises/security/policy) for more information. + +## License + +Guzzle is made available under the MIT License (MIT). Please see [License File](LICENSE) for more information. + +## For Enterprise + +Available as part of the Tidelift Subscription + +The maintainers of Guzzle and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/packagist-guzzlehttp-promises?utm_source=packagist-guzzlehttp-promises&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) diff --git a/vendor/guzzlehttp/promises/composer.json b/vendor/guzzlehttp/promises/composer.json new file mode 100644 index 0000000..c959fb3 --- /dev/null +++ b/vendor/guzzlehttp/promises/composer.json @@ -0,0 +1,58 @@ +{ + "name": "guzzlehttp/promises", + "description": "Guzzle promises library", + "keywords": ["promise"], + "license": "MIT", + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "require": { + "php": ">=5.5" + }, + "require-dev": { + "symfony/phpunit-bridge": "^4.4 || ^5.1" + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": ["src/functions_include.php"] + }, + "autoload-dev": { + "psr-4": { + "GuzzleHttp\\Promise\\Tests\\": "tests/" + } + }, + "scripts": { + "test": "vendor/bin/simple-phpunit", + "test-ci": "vendor/bin/simple-phpunit --coverage-text" + }, + "extra": { + "branch-alias": { + "dev-master": "1.5-dev" + } + }, + "config": { + "preferred-install": "dist", + "sort-packages": true + } +} diff --git a/vendor/guzzlehttp/promises/src/AggregateException.php b/vendor/guzzlehttp/promises/src/AggregateException.php new file mode 100644 index 0000000..d2b5712 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/AggregateException.php @@ -0,0 +1,17 @@ +then(function ($v) { echo $v; }); + * + * @param callable $generatorFn Generator function to wrap into a promise. + * + * @return Promise + * + * @link https://github.com/petkaantonov/bluebird/blob/master/API.md#generators inspiration + */ +final class Coroutine implements PromiseInterface +{ + /** + * @var PromiseInterface|null + */ + private $currentPromise; + + /** + * @var Generator + */ + private $generator; + + /** + * @var Promise + */ + private $result; + + public function __construct(callable $generatorFn) + { + $this->generator = $generatorFn(); + $this->result = new Promise(function () { + while (isset($this->currentPromise)) { + $this->currentPromise->wait(); + } + }); + try { + $this->nextCoroutine($this->generator->current()); + } catch (\Exception $exception) { + $this->result->reject($exception); + } catch (Throwable $throwable) { + $this->result->reject($throwable); + } + } + + /** + * Create a new coroutine. + * + * @return self + */ + public static function of(callable $generatorFn) + { + return new self($generatorFn); + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null + ) { + return $this->result->then($onFulfilled, $onRejected); + } + + public function otherwise(callable $onRejected) + { + return $this->result->otherwise($onRejected); + } + + public function wait($unwrap = true) + { + return $this->result->wait($unwrap); + } + + public function getState() + { + return $this->result->getState(); + } + + public function resolve($value) + { + $this->result->resolve($value); + } + + public function reject($reason) + { + $this->result->reject($reason); + } + + public function cancel() + { + $this->currentPromise->cancel(); + $this->result->cancel(); + } + + private function nextCoroutine($yielded) + { + $this->currentPromise = Create::promiseFor($yielded) + ->then([$this, '_handleSuccess'], [$this, '_handleFailure']); + } + + /** + * @internal + */ + public function _handleSuccess($value) + { + unset($this->currentPromise); + try { + $next = $this->generator->send($value); + if ($this->generator->valid()) { + $this->nextCoroutine($next); + } else { + $this->result->resolve($value); + } + } catch (Exception $exception) { + $this->result->reject($exception); + } catch (Throwable $throwable) { + $this->result->reject($throwable); + } + } + + /** + * @internal + */ + public function _handleFailure($reason) + { + unset($this->currentPromise); + try { + $nextYield = $this->generator->throw(Create::exceptionFor($reason)); + // The throw was caught, so keep iterating on the coroutine + $this->nextCoroutine($nextYield); + } catch (Exception $exception) { + $this->result->reject($exception); + } catch (Throwable $throwable) { + $this->result->reject($throwable); + } + } +} diff --git a/vendor/guzzlehttp/promises/src/Create.php b/vendor/guzzlehttp/promises/src/Create.php new file mode 100644 index 0000000..8d038e9 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/Create.php @@ -0,0 +1,84 @@ +then([$promise, 'resolve'], [$promise, 'reject']); + return $promise; + } + + return new FulfilledPromise($value); + } + + /** + * Creates a rejected promise for a reason if the reason is not a promise. + * If the provided reason is a promise, then it is returned as-is. + * + * @param mixed $reason Promise or reason. + * + * @return PromiseInterface + */ + public static function rejectionFor($reason) + { + if ($reason instanceof PromiseInterface) { + return $reason; + } + + return new RejectedPromise($reason); + } + + /** + * Create an exception for a rejected promise value. + * + * @param mixed $reason + * + * @return \Exception|\Throwable + */ + public static function exceptionFor($reason) + { + if ($reason instanceof \Exception || $reason instanceof \Throwable) { + return $reason; + } + + return new RejectionException($reason); + } + + /** + * Returns an iterator for the given value. + * + * @param mixed $value + * + * @return \Iterator + */ + public static function iterFor($value) + { + if ($value instanceof \Iterator) { + return $value; + } + + if (is_array($value)) { + return new \ArrayIterator($value); + } + + return new \ArrayIterator([$value]); + } +} diff --git a/vendor/guzzlehttp/promises/src/Each.php b/vendor/guzzlehttp/promises/src/Each.php new file mode 100644 index 0000000..1dda354 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/Each.php @@ -0,0 +1,90 @@ + $onFulfilled, + 'rejected' => $onRejected + ]))->promise(); + } + + /** + * Like of, but only allows a certain number of outstanding promises at any + * given time. + * + * $concurrency may be an integer or a function that accepts the number of + * pending promises and returns a numeric concurrency limit value to allow + * for dynamic a concurrency size. + * + * @param mixed $iterable + * @param int|callable $concurrency + * @param callable $onFulfilled + * @param callable $onRejected + * + * @return PromiseInterface + */ + public static function ofLimit( + $iterable, + $concurrency, + callable $onFulfilled = null, + callable $onRejected = null + ) { + return (new EachPromise($iterable, [ + 'fulfilled' => $onFulfilled, + 'rejected' => $onRejected, + 'concurrency' => $concurrency + ]))->promise(); + } + + /** + * Like limit, but ensures that no promise in the given $iterable argument + * is rejected. If any promise is rejected, then the aggregate promise is + * rejected with the encountered rejection. + * + * @param mixed $iterable + * @param int|callable $concurrency + * @param callable $onFulfilled + * + * @return PromiseInterface + */ + public static function ofLimitAll( + $iterable, + $concurrency, + callable $onFulfilled = null + ) { + return each_limit( + $iterable, + $concurrency, + $onFulfilled, + function ($reason, $idx, PromiseInterface $aggregate) { + $aggregate->reject($reason); + } + ); + } +} diff --git a/vendor/guzzlehttp/promises/src/EachPromise.php b/vendor/guzzlehttp/promises/src/EachPromise.php new file mode 100644 index 0000000..38ecb59 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/EachPromise.php @@ -0,0 +1,255 @@ +iterable = Create::iterFor($iterable); + + if (isset($config['concurrency'])) { + $this->concurrency = $config['concurrency']; + } + + if (isset($config['fulfilled'])) { + $this->onFulfilled = $config['fulfilled']; + } + + if (isset($config['rejected'])) { + $this->onRejected = $config['rejected']; + } + } + + /** @psalm-suppress InvalidNullableReturnType */ + public function promise() + { + if ($this->aggregate) { + return $this->aggregate; + } + + try { + $this->createPromise(); + /** @psalm-assert Promise $this->aggregate */ + $this->iterable->rewind(); + $this->refillPending(); + } catch (\Throwable $e) { + /** + * @psalm-suppress NullReference + * @phpstan-ignore-next-line + */ + $this->aggregate->reject($e); + } catch (\Exception $e) { + /** + * @psalm-suppress NullReference + * @phpstan-ignore-next-line + */ + $this->aggregate->reject($e); + } + + /** + * @psalm-suppress NullableReturnStatement + * @phpstan-ignore-next-line + */ + return $this->aggregate; + } + + private function createPromise() + { + $this->mutex = false; + $this->aggregate = new Promise(function () { + if ($this->checkIfFinished()) { + return; + } + reset($this->pending); + // Consume a potentially fluctuating list of promises while + // ensuring that indexes are maintained (precluding array_shift). + while ($promise = current($this->pending)) { + next($this->pending); + $promise->wait(); + if (Is::settled($this->aggregate)) { + return; + } + } + }); + + // Clear the references when the promise is resolved. + $clearFn = function () { + $this->iterable = $this->concurrency = $this->pending = null; + $this->onFulfilled = $this->onRejected = null; + $this->nextPendingIndex = 0; + }; + + $this->aggregate->then($clearFn, $clearFn); + } + + private function refillPending() + { + if (!$this->concurrency) { + // Add all pending promises. + while ($this->addPending() && $this->advanceIterator()); + return; + } + + // Add only up to N pending promises. + $concurrency = is_callable($this->concurrency) + ? call_user_func($this->concurrency, count($this->pending)) + : $this->concurrency; + $concurrency = max($concurrency - count($this->pending), 0); + // Concurrency may be set to 0 to disallow new promises. + if (!$concurrency) { + return; + } + // Add the first pending promise. + $this->addPending(); + // Note this is special handling for concurrency=1 so that we do + // not advance the iterator after adding the first promise. This + // helps work around issues with generators that might not have the + // next value to yield until promise callbacks are called. + while (--$concurrency + && $this->advanceIterator() + && $this->addPending()); + } + + private function addPending() + { + if (!$this->iterable || !$this->iterable->valid()) { + return false; + } + + $promise = Create::promiseFor($this->iterable->current()); + $key = $this->iterable->key(); + + // Iterable keys may not be unique, so we use a counter to + // guarantee uniqueness + $idx = $this->nextPendingIndex++; + + $this->pending[$idx] = $promise->then( + function ($value) use ($idx, $key) { + if ($this->onFulfilled) { + call_user_func( + $this->onFulfilled, + $value, + $key, + $this->aggregate + ); + } + $this->step($idx); + }, + function ($reason) use ($idx, $key) { + if ($this->onRejected) { + call_user_func( + $this->onRejected, + $reason, + $key, + $this->aggregate + ); + } + $this->step($idx); + } + ); + + return true; + } + + private function advanceIterator() + { + // Place a lock on the iterator so that we ensure to not recurse, + // preventing fatal generator errors. + if ($this->mutex) { + return false; + } + + $this->mutex = true; + + try { + $this->iterable->next(); + $this->mutex = false; + return true; + } catch (\Throwable $e) { + $this->aggregate->reject($e); + $this->mutex = false; + return false; + } catch (\Exception $e) { + $this->aggregate->reject($e); + $this->mutex = false; + return false; + } + } + + private function step($idx) + { + // If the promise was already resolved, then ignore this step. + if (Is::settled($this->aggregate)) { + return; + } + + unset($this->pending[$idx]); + + // Only refill pending promises if we are not locked, preventing the + // EachPromise to recursively invoke the provided iterator, which + // cause a fatal error: "Cannot resume an already running generator" + if ($this->advanceIterator() && !$this->checkIfFinished()) { + // Add more pending promises if possible. + $this->refillPending(); + } + } + + private function checkIfFinished() + { + if (!$this->pending && !$this->iterable->valid()) { + // Resolve the promise if there's nothing left to do. + $this->aggregate->resolve(null); + return true; + } + + return false; + } +} diff --git a/vendor/guzzlehttp/promises/src/FulfilledPromise.php b/vendor/guzzlehttp/promises/src/FulfilledPromise.php new file mode 100644 index 0000000..98f72a6 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/FulfilledPromise.php @@ -0,0 +1,84 @@ +value = $value; + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null + ) { + // Return itself if there is no onFulfilled function. + if (!$onFulfilled) { + return $this; + } + + $queue = Utils::queue(); + $p = new Promise([$queue, 'run']); + $value = $this->value; + $queue->add(static function () use ($p, $value, $onFulfilled) { + if (Is::pending($p)) { + try { + $p->resolve($onFulfilled($value)); + } catch (\Throwable $e) { + $p->reject($e); + } catch (\Exception $e) { + $p->reject($e); + } + } + }); + + return $p; + } + + public function otherwise(callable $onRejected) + { + return $this->then(null, $onRejected); + } + + public function wait($unwrap = true, $defaultDelivery = null) + { + return $unwrap ? $this->value : null; + } + + public function getState() + { + return self::FULFILLED; + } + + public function resolve($value) + { + if ($value !== $this->value) { + throw new \LogicException("Cannot resolve a fulfilled promise"); + } + } + + public function reject($reason) + { + throw new \LogicException("Cannot reject a fulfilled promise"); + } + + public function cancel() + { + // pass + } +} diff --git a/vendor/guzzlehttp/promises/src/Is.php b/vendor/guzzlehttp/promises/src/Is.php new file mode 100644 index 0000000..c3ed8d0 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/Is.php @@ -0,0 +1,46 @@ +getState() === PromiseInterface::PENDING; + } + + /** + * Returns true if a promise is fulfilled or rejected. + * + * @return bool + */ + public static function settled(PromiseInterface $promise) + { + return $promise->getState() !== PromiseInterface::PENDING; + } + + /** + * Returns true if a promise is fulfilled. + * + * @return bool + */ + public static function fulfilled(PromiseInterface $promise) + { + return $promise->getState() === PromiseInterface::FULFILLED; + } + + /** + * Returns true if a promise is rejected. + * + * @return bool + */ + public static function rejected(PromiseInterface $promise) + { + return $promise->getState() === PromiseInterface::REJECTED; + } +} diff --git a/vendor/guzzlehttp/promises/src/Promise.php b/vendor/guzzlehttp/promises/src/Promise.php new file mode 100644 index 0000000..7593905 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/Promise.php @@ -0,0 +1,278 @@ +waitFn = $waitFn; + $this->cancelFn = $cancelFn; + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null + ) { + if ($this->state === self::PENDING) { + $p = new Promise(null, [$this, 'cancel']); + $this->handlers[] = [$p, $onFulfilled, $onRejected]; + $p->waitList = $this->waitList; + $p->waitList[] = $this; + return $p; + } + + // Return a fulfilled promise and immediately invoke any callbacks. + if ($this->state === self::FULFILLED) { + $promise = Create::promiseFor($this->result); + return $onFulfilled ? $promise->then($onFulfilled) : $promise; + } + + // It's either cancelled or rejected, so return a rejected promise + // and immediately invoke any callbacks. + $rejection = Create::rejectionFor($this->result); + return $onRejected ? $rejection->then(null, $onRejected) : $rejection; + } + + public function otherwise(callable $onRejected) + { + return $this->then(null, $onRejected); + } + + public function wait($unwrap = true) + { + $this->waitIfPending(); + + if ($this->result instanceof PromiseInterface) { + return $this->result->wait($unwrap); + } + if ($unwrap) { + if ($this->state === self::FULFILLED) { + return $this->result; + } + // It's rejected so "unwrap" and throw an exception. + throw Create::exceptionFor($this->result); + } + } + + public function getState() + { + return $this->state; + } + + public function cancel() + { + if ($this->state !== self::PENDING) { + return; + } + + $this->waitFn = $this->waitList = null; + + if ($this->cancelFn) { + $fn = $this->cancelFn; + $this->cancelFn = null; + try { + $fn(); + } catch (\Throwable $e) { + $this->reject($e); + } catch (\Exception $e) { + $this->reject($e); + } + } + + // Reject the promise only if it wasn't rejected in a then callback. + /** @psalm-suppress RedundantCondition */ + if ($this->state === self::PENDING) { + $this->reject(new CancellationException('Promise has been cancelled')); + } + } + + public function resolve($value) + { + $this->settle(self::FULFILLED, $value); + } + + public function reject($reason) + { + $this->settle(self::REJECTED, $reason); + } + + private function settle($state, $value) + { + if ($this->state !== self::PENDING) { + // Ignore calls with the same resolution. + if ($state === $this->state && $value === $this->result) { + return; + } + throw $this->state === $state + ? new \LogicException("The promise is already {$state}.") + : new \LogicException("Cannot change a {$this->state} promise to {$state}"); + } + + if ($value === $this) { + throw new \LogicException('Cannot fulfill or reject a promise with itself'); + } + + // Clear out the state of the promise but stash the handlers. + $this->state = $state; + $this->result = $value; + $handlers = $this->handlers; + $this->handlers = null; + $this->waitList = $this->waitFn = null; + $this->cancelFn = null; + + if (!$handlers) { + return; + } + + // If the value was not a settled promise or a thenable, then resolve + // it in the task queue using the correct ID. + if (!is_object($value) || !method_exists($value, 'then')) { + $id = $state === self::FULFILLED ? 1 : 2; + // It's a success, so resolve the handlers in the queue. + Utils::queue()->add(static function () use ($id, $value, $handlers) { + foreach ($handlers as $handler) { + self::callHandler($id, $value, $handler); + } + }); + } elseif ($value instanceof Promise && Is::pending($value)) { + // We can just merge our handlers onto the next promise. + $value->handlers = array_merge($value->handlers, $handlers); + } else { + // Resolve the handlers when the forwarded promise is resolved. + $value->then( + static function ($value) use ($handlers) { + foreach ($handlers as $handler) { + self::callHandler(1, $value, $handler); + } + }, + static function ($reason) use ($handlers) { + foreach ($handlers as $handler) { + self::callHandler(2, $reason, $handler); + } + } + ); + } + } + + /** + * Call a stack of handlers using a specific callback index and value. + * + * @param int $index 1 (resolve) or 2 (reject). + * @param mixed $value Value to pass to the callback. + * @param array $handler Array of handler data (promise and callbacks). + */ + private static function callHandler($index, $value, array $handler) + { + /** @var PromiseInterface $promise */ + $promise = $handler[0]; + + // The promise may have been cancelled or resolved before placing + // this thunk in the queue. + if (Is::settled($promise)) { + return; + } + + try { + if (isset($handler[$index])) { + /* + * If $f throws an exception, then $handler will be in the exception + * stack trace. Since $handler contains a reference to the callable + * itself we get a circular reference. We clear the $handler + * here to avoid that memory leak. + */ + $f = $handler[$index]; + unset($handler); + $promise->resolve($f($value)); + } elseif ($index === 1) { + // Forward resolution values as-is. + $promise->resolve($value); + } else { + // Forward rejections down the chain. + $promise->reject($value); + } + } catch (\Throwable $reason) { + $promise->reject($reason); + } catch (\Exception $reason) { + $promise->reject($reason); + } + } + + private function waitIfPending() + { + if ($this->state !== self::PENDING) { + return; + } elseif ($this->waitFn) { + $this->invokeWaitFn(); + } elseif ($this->waitList) { + $this->invokeWaitList(); + } else { + // If there's no wait function, then reject the promise. + $this->reject('Cannot wait on a promise that has ' + . 'no internal wait function. You must provide a wait ' + . 'function when constructing the promise to be able to ' + . 'wait on a promise.'); + } + + Utils::queue()->run(); + + /** @psalm-suppress RedundantCondition */ + if ($this->state === self::PENDING) { + $this->reject('Invoking the wait callback did not resolve the promise'); + } + } + + private function invokeWaitFn() + { + try { + $wfn = $this->waitFn; + $this->waitFn = null; + $wfn(true); + } catch (\Exception $reason) { + if ($this->state === self::PENDING) { + // The promise has not been resolved yet, so reject the promise + // with the exception. + $this->reject($reason); + } else { + // The promise was already resolved, so there's a problem in + // the application. + throw $reason; + } + } + } + + private function invokeWaitList() + { + $waitList = $this->waitList; + $this->waitList = null; + + foreach ($waitList as $result) { + do { + $result->waitIfPending(); + $result = $result->result; + } while ($result instanceof Promise); + + if ($result instanceof PromiseInterface) { + $result->wait(false); + } + } + } +} diff --git a/vendor/guzzlehttp/promises/src/PromiseInterface.php b/vendor/guzzlehttp/promises/src/PromiseInterface.php new file mode 100644 index 0000000..e598331 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/PromiseInterface.php @@ -0,0 +1,97 @@ +reason = $reason; + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null + ) { + // If there's no onRejected callback then just return self. + if (!$onRejected) { + return $this; + } + + $queue = Utils::queue(); + $reason = $this->reason; + $p = new Promise([$queue, 'run']); + $queue->add(static function () use ($p, $reason, $onRejected) { + if (Is::pending($p)) { + try { + // Return a resolved promise if onRejected does not throw. + $p->resolve($onRejected($reason)); + } catch (\Throwable $e) { + // onRejected threw, so return a rejected promise. + $p->reject($e); + } catch (\Exception $e) { + // onRejected threw, so return a rejected promise. + $p->reject($e); + } + } + }); + + return $p; + } + + public function otherwise(callable $onRejected) + { + return $this->then(null, $onRejected); + } + + public function wait($unwrap = true, $defaultDelivery = null) + { + if ($unwrap) { + throw Create::exceptionFor($this->reason); + } + + return null; + } + + public function getState() + { + return self::REJECTED; + } + + public function resolve($value) + { + throw new \LogicException("Cannot resolve a rejected promise"); + } + + public function reject($reason) + { + if ($reason !== $this->reason) { + throw new \LogicException("Cannot reject a rejected promise"); + } + } + + public function cancel() + { + // pass + } +} diff --git a/vendor/guzzlehttp/promises/src/RejectionException.php b/vendor/guzzlehttp/promises/src/RejectionException.php new file mode 100644 index 0000000..e2f1377 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/RejectionException.php @@ -0,0 +1,48 @@ +reason = $reason; + + $message = 'The promise was rejected'; + + if ($description) { + $message .= ' with reason: ' . $description; + } elseif (is_string($reason) + || (is_object($reason) && method_exists($reason, '__toString')) + ) { + $message .= ' with reason: ' . $this->reason; + } elseif ($reason instanceof \JsonSerializable) { + $message .= ' with reason: ' + . json_encode($this->reason, JSON_PRETTY_PRINT); + } + + parent::__construct($message); + } + + /** + * Returns the rejection reason. + * + * @return mixed + */ + public function getReason() + { + return $this->reason; + } +} diff --git a/vendor/guzzlehttp/promises/src/TaskQueue.php b/vendor/guzzlehttp/promises/src/TaskQueue.php new file mode 100644 index 0000000..f0fba2c --- /dev/null +++ b/vendor/guzzlehttp/promises/src/TaskQueue.php @@ -0,0 +1,67 @@ +run(); + */ +class TaskQueue implements TaskQueueInterface +{ + private $enableShutdown = true; + private $queue = []; + + public function __construct($withShutdown = true) + { + if ($withShutdown) { + register_shutdown_function(function () { + if ($this->enableShutdown) { + // Only run the tasks if an E_ERROR didn't occur. + $err = error_get_last(); + if (!$err || ($err['type'] ^ E_ERROR)) { + $this->run(); + } + } + }); + } + } + + public function isEmpty() + { + return !$this->queue; + } + + public function add(callable $task) + { + $this->queue[] = $task; + } + + public function run() + { + while ($task = array_shift($this->queue)) { + /** @var callable $task */ + $task(); + } + } + + /** + * The task queue will be run and exhausted by default when the process + * exits IFF the exit is not the result of a PHP E_ERROR error. + * + * You can disable running the automatic shutdown of the queue by calling + * this function. If you disable the task queue shutdown process, then you + * MUST either run the task queue (as a result of running your event loop + * or manually using the run() method) or wait on each outstanding promise. + * + * Note: This shutdown will occur before any destructors are triggered. + */ + public function disableShutdown() + { + $this->enableShutdown = false; + } +} diff --git a/vendor/guzzlehttp/promises/src/TaskQueueInterface.php b/vendor/guzzlehttp/promises/src/TaskQueueInterface.php new file mode 100644 index 0000000..723d4d5 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/TaskQueueInterface.php @@ -0,0 +1,24 @@ + + * while ($eventLoop->isRunning()) { + * GuzzleHttp\Promise\Utils::queue()->run(); + * } + * + * + * @param TaskQueueInterface $assign Optionally specify a new queue instance. + * + * @return TaskQueueInterface + */ + public static function queue(TaskQueueInterface $assign = null) + { + static $queue; + + if ($assign) { + $queue = $assign; + } elseif (!$queue) { + $queue = new TaskQueue(); + } + + return $queue; + } + + /** + * Adds a function to run in the task queue when it is next `run()` and + * returns a promise that is fulfilled or rejected with the result. + * + * @param callable $task Task function to run. + * + * @return PromiseInterface + */ + public static function task(callable $task) + { + $queue = self::queue(); + $promise = new Promise([$queue, 'run']); + $queue->add(function () use ($task, $promise) { + try { + if (Is::pending($promise)) { + $promise->resolve($task()); + } + } catch (\Throwable $e) { + $promise->reject($e); + } catch (\Exception $e) { + $promise->reject($e); + } + }); + + return $promise; + } + + /** + * Synchronously waits on a promise to resolve and returns an inspection + * state array. + * + * Returns a state associative array containing a "state" key mapping to a + * valid promise state. If the state of the promise is "fulfilled", the + * array will contain a "value" key mapping to the fulfilled value of the + * promise. If the promise is rejected, the array will contain a "reason" + * key mapping to the rejection reason of the promise. + * + * @param PromiseInterface $promise Promise or value. + * + * @return array + */ + public static function inspect(PromiseInterface $promise) + { + try { + return [ + 'state' => PromiseInterface::FULFILLED, + 'value' => $promise->wait() + ]; + } catch (RejectionException $e) { + return ['state' => PromiseInterface::REJECTED, 'reason' => $e->getReason()]; + } catch (\Throwable $e) { + return ['state' => PromiseInterface::REJECTED, 'reason' => $e]; + } catch (\Exception $e) { + return ['state' => PromiseInterface::REJECTED, 'reason' => $e]; + } + } + + /** + * Waits on all of the provided promises, but does not unwrap rejected + * promises as thrown exception. + * + * Returns an array of inspection state arrays. + * + * @see inspect for the inspection state array format. + * + * @param PromiseInterface[] $promises Traversable of promises to wait upon. + * + * @return array + */ + public static function inspectAll($promises) + { + $results = []; + foreach ($promises as $key => $promise) { + $results[$key] = inspect($promise); + } + + return $results; + } + + /** + * Waits on all of the provided promises and returns the fulfilled values. + * + * Returns an array that contains the value of each promise (in the same + * order the promises were provided). An exception is thrown if any of the + * promises are rejected. + * + * @param iterable $promises Iterable of PromiseInterface objects to wait on. + * + * @return array + * + * @throws \Exception on error + * @throws \Throwable on error in PHP >=7 + */ + public static function unwrap($promises) + { + $results = []; + foreach ($promises as $key => $promise) { + $results[$key] = $promise->wait(); + } + + return $results; + } + + /** + * Given an array of promises, return a promise that is fulfilled when all + * the items in the array are fulfilled. + * + * The promise's fulfillment value is an array with fulfillment values at + * respective positions to the original array. If any promise in the array + * rejects, the returned promise is rejected with the rejection reason. + * + * @param mixed $promises Promises or values. + * @param bool $recursive If true, resolves new promises that might have been added to the stack during its own resolution. + * + * @return PromiseInterface + */ + public static function all($promises, $recursive = false) + { + $results = []; + $promise = Each::of( + $promises, + function ($value, $idx) use (&$results) { + $results[$idx] = $value; + }, + function ($reason, $idx, Promise $aggregate) { + $aggregate->reject($reason); + } + )->then(function () use (&$results) { + ksort($results); + return $results; + }); + + if (true === $recursive) { + $promise = $promise->then(function ($results) use ($recursive, &$promises) { + foreach ($promises as $promise) { + if (Is::pending($promise)) { + return self::all($promises, $recursive); + } + } + return $results; + }); + } + + return $promise; + } + + /** + * Initiate a competitive race between multiple promises or values (values + * will become immediately fulfilled promises). + * + * When count amount of promises have been fulfilled, the returned promise + * is fulfilled with an array that contains the fulfillment values of the + * winners in order of resolution. + * + * This promise is rejected with a {@see AggregateException} if the number + * of fulfilled promises is less than the desired $count. + * + * @param int $count Total number of promises. + * @param mixed $promises Promises or values. + * + * @return PromiseInterface + */ + public static function some($count, $promises) + { + $results = []; + $rejections = []; + + return Each::of( + $promises, + function ($value, $idx, PromiseInterface $p) use (&$results, $count) { + if (Is::settled($p)) { + return; + } + $results[$idx] = $value; + if (count($results) >= $count) { + $p->resolve(null); + } + }, + function ($reason) use (&$rejections) { + $rejections[] = $reason; + } + )->then( + function () use (&$results, &$rejections, $count) { + if (count($results) !== $count) { + throw new AggregateException( + 'Not enough promises to fulfill count', + $rejections + ); + } + ksort($results); + return array_values($results); + } + ); + } + + /** + * Like some(), with 1 as count. However, if the promise fulfills, the + * fulfillment value is not an array of 1 but the value directly. + * + * @param mixed $promises Promises or values. + * + * @return PromiseInterface + */ + public static function any($promises) + { + return self::some(1, $promises)->then(function ($values) { + return $values[0]; + }); + } + + /** + * Returns a promise that is fulfilled when all of the provided promises have + * been fulfilled or rejected. + * + * The returned promise is fulfilled with an array of inspection state arrays. + * + * @see inspect for the inspection state array format. + * + * @param mixed $promises Promises or values. + * + * @return PromiseInterface + */ + public static function settle($promises) + { + $results = []; + + return Each::of( + $promises, + function ($value, $idx) use (&$results) { + $results[$idx] = ['state' => PromiseInterface::FULFILLED, 'value' => $value]; + }, + function ($reason, $idx) use (&$results) { + $results[$idx] = ['state' => PromiseInterface::REJECTED, 'reason' => $reason]; + } + )->then(function () use (&$results) { + ksort($results); + return $results; + }); + } +} diff --git a/vendor/guzzlehttp/promises/src/functions.php b/vendor/guzzlehttp/promises/src/functions.php new file mode 100644 index 0000000..c03d39d --- /dev/null +++ b/vendor/guzzlehttp/promises/src/functions.php @@ -0,0 +1,363 @@ + + * while ($eventLoop->isRunning()) { + * GuzzleHttp\Promise\queue()->run(); + * } + * + * + * @param TaskQueueInterface $assign Optionally specify a new queue instance. + * + * @return TaskQueueInterface + * + * @deprecated queue will be removed in guzzlehttp/promises:2.0. Use Utils::queue instead. + */ +function queue(TaskQueueInterface $assign = null) +{ + return Utils::queue($assign); +} + +/** + * Adds a function to run in the task queue when it is next `run()` and returns + * a promise that is fulfilled or rejected with the result. + * + * @param callable $task Task function to run. + * + * @return PromiseInterface + * + * @deprecated task will be removed in guzzlehttp/promises:2.0. Use Utils::task instead. + */ +function task(callable $task) +{ + return Utils::task($task); +} + +/** + * Creates a promise for a value if the value is not a promise. + * + * @param mixed $value Promise or value. + * + * @return PromiseInterface + * + * @deprecated promise_for will be removed in guzzlehttp/promises:2.0. Use Create::promiseFor instead. + */ +function promise_for($value) +{ + return Create::promiseFor($value); +} + +/** + * Creates a rejected promise for a reason if the reason is not a promise. If + * the provided reason is a promise, then it is returned as-is. + * + * @param mixed $reason Promise or reason. + * + * @return PromiseInterface + * + * @deprecated rejection_for will be removed in guzzlehttp/promises:2.0. Use Create::rejectionFor instead. + */ +function rejection_for($reason) +{ + return Create::rejectionFor($reason); +} + +/** + * Create an exception for a rejected promise value. + * + * @param mixed $reason + * + * @return \Exception|\Throwable + * + * @deprecated exception_for will be removed in guzzlehttp/promises:2.0. Use Create::exceptionFor instead. + */ +function exception_for($reason) +{ + return Create::exceptionFor($reason); +} + +/** + * Returns an iterator for the given value. + * + * @param mixed $value + * + * @return \Iterator + * + * @deprecated iter_for will be removed in guzzlehttp/promises:2.0. Use Create::iterFor instead. + */ +function iter_for($value) +{ + return Create::iterFor($value); +} + +/** + * Synchronously waits on a promise to resolve and returns an inspection state + * array. + * + * Returns a state associative array containing a "state" key mapping to a + * valid promise state. If the state of the promise is "fulfilled", the array + * will contain a "value" key mapping to the fulfilled value of the promise. If + * the promise is rejected, the array will contain a "reason" key mapping to + * the rejection reason of the promise. + * + * @param PromiseInterface $promise Promise or value. + * + * @return array + * + * @deprecated inspect will be removed in guzzlehttp/promises:2.0. Use Utils::inspect instead. + */ +function inspect(PromiseInterface $promise) +{ + return Utils::inspect($promise); +} + +/** + * Waits on all of the provided promises, but does not unwrap rejected promises + * as thrown exception. + * + * Returns an array of inspection state arrays. + * + * @see inspect for the inspection state array format. + * + * @param PromiseInterface[] $promises Traversable of promises to wait upon. + * + * @return array + * + * @deprecated inspect will be removed in guzzlehttp/promises:2.0. Use Utils::inspectAll instead. + */ +function inspect_all($promises) +{ + return Utils::inspectAll($promises); +} + +/** + * Waits on all of the provided promises and returns the fulfilled values. + * + * Returns an array that contains the value of each promise (in the same order + * the promises were provided). An exception is thrown if any of the promises + * are rejected. + * + * @param iterable $promises Iterable of PromiseInterface objects to wait on. + * + * @return array + * + * @throws \Exception on error + * @throws \Throwable on error in PHP >=7 + * + * @deprecated unwrap will be removed in guzzlehttp/promises:2.0. Use Utils::unwrap instead. + */ +function unwrap($promises) +{ + return Utils::unwrap($promises); +} + +/** + * Given an array of promises, return a promise that is fulfilled when all the + * items in the array are fulfilled. + * + * The promise's fulfillment value is an array with fulfillment values at + * respective positions to the original array. If any promise in the array + * rejects, the returned promise is rejected with the rejection reason. + * + * @param mixed $promises Promises or values. + * @param bool $recursive If true, resolves new promises that might have been added to the stack during its own resolution. + * + * @return PromiseInterface + * + * @deprecated all will be removed in guzzlehttp/promises:2.0. Use Utils::all instead. + */ +function all($promises, $recursive = false) +{ + return Utils::all($promises, $recursive); +} + +/** + * Initiate a competitive race between multiple promises or values (values will + * become immediately fulfilled promises). + * + * When count amount of promises have been fulfilled, the returned promise is + * fulfilled with an array that contains the fulfillment values of the winners + * in order of resolution. + * + * This promise is rejected with a {@see AggregateException} if the number of + * fulfilled promises is less than the desired $count. + * + * @param int $count Total number of promises. + * @param mixed $promises Promises or values. + * + * @return PromiseInterface + * + * @deprecated some will be removed in guzzlehttp/promises:2.0. Use Utils::some instead. + */ +function some($count, $promises) +{ + return Utils::some($count, $promises); +} + +/** + * Like some(), with 1 as count. However, if the promise fulfills, the + * fulfillment value is not an array of 1 but the value directly. + * + * @param mixed $promises Promises or values. + * + * @return PromiseInterface + * + * @deprecated any will be removed in guzzlehttp/promises:2.0. Use Utils::any instead. + */ +function any($promises) +{ + return Utils::any($promises); +} + +/** + * Returns a promise that is fulfilled when all of the provided promises have + * been fulfilled or rejected. + * + * The returned promise is fulfilled with an array of inspection state arrays. + * + * @see inspect for the inspection state array format. + * + * @param mixed $promises Promises or values. + * + * @return PromiseInterface + * + * @deprecated settle will be removed in guzzlehttp/promises:2.0. Use Utils::settle instead. + */ +function settle($promises) +{ + return Utils::settle($promises); +} + +/** + * Given an iterator that yields promises or values, returns a promise that is + * fulfilled with a null value when the iterator has been consumed or the + * aggregate promise has been fulfilled or rejected. + * + * $onFulfilled is a function that accepts the fulfilled value, iterator index, + * and the aggregate promise. The callback can invoke any necessary side + * effects and choose to resolve or reject the aggregate if needed. + * + * $onRejected is a function that accepts the rejection reason, iterator index, + * and the aggregate promise. The callback can invoke any necessary side + * effects and choose to resolve or reject the aggregate if needed. + * + * @param mixed $iterable Iterator or array to iterate over. + * @param callable $onFulfilled + * @param callable $onRejected + * + * @return PromiseInterface + * + * @deprecated each will be removed in guzzlehttp/promises:2.0. Use Each::of instead. + */ +function each( + $iterable, + callable $onFulfilled = null, + callable $onRejected = null +) { + return Each::of($iterable, $onFulfilled, $onRejected); +} + +/** + * Like each, but only allows a certain number of outstanding promises at any + * given time. + * + * $concurrency may be an integer or a function that accepts the number of + * pending promises and returns a numeric concurrency limit value to allow for + * dynamic a concurrency size. + * + * @param mixed $iterable + * @param int|callable $concurrency + * @param callable $onFulfilled + * @param callable $onRejected + * + * @return PromiseInterface + * + * @deprecated each_limit will be removed in guzzlehttp/promises:2.0. Use Each::ofLimit instead. + */ +function each_limit( + $iterable, + $concurrency, + callable $onFulfilled = null, + callable $onRejected = null +) { + return Each::ofLimit($iterable, $concurrency, $onFulfilled, $onRejected); +} + +/** + * Like each_limit, but ensures that no promise in the given $iterable argument + * is rejected. If any promise is rejected, then the aggregate promise is + * rejected with the encountered rejection. + * + * @param mixed $iterable + * @param int|callable $concurrency + * @param callable $onFulfilled + * + * @return PromiseInterface + * + * @deprecated each_limit_all will be removed in guzzlehttp/promises:2.0. Use Each::ofLimitAll instead. + */ +function each_limit_all( + $iterable, + $concurrency, + callable $onFulfilled = null +) { + return Each::ofLimitAll($iterable, $concurrency, $onFulfilled); +} + +/** + * Returns true if a promise is fulfilled. + * + * @return bool + * + * @deprecated is_fulfilled will be removed in guzzlehttp/promises:2.0. Use Is::fulfilled instead. + */ +function is_fulfilled(PromiseInterface $promise) +{ + return Is::fulfilled($promise); +} + +/** + * Returns true if a promise is rejected. + * + * @return bool + * + * @deprecated is_rejected will be removed in guzzlehttp/promises:2.0. Use Is::rejected instead. + */ +function is_rejected(PromiseInterface $promise) +{ + return Is::rejected($promise); +} + +/** + * Returns true if a promise is fulfilled or rejected. + * + * @return bool + * + * @deprecated is_settled will be removed in guzzlehttp/promises:2.0. Use Is::settled instead. + */ +function is_settled(PromiseInterface $promise) +{ + return Is::settled($promise); +} + +/** + * Create a new coroutine. + * + * @see Coroutine + * + * @return PromiseInterface + * + * @deprecated coroutine will be removed in guzzlehttp/promises:2.0. Use Coroutine::of instead. + */ +function coroutine(callable $generatorFn) +{ + return Coroutine::of($generatorFn); +} diff --git a/vendor/guzzlehttp/promises/src/functions_include.php b/vendor/guzzlehttp/promises/src/functions_include.php new file mode 100644 index 0000000..34cd171 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/functions_include.php @@ -0,0 +1,6 @@ + + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed after 2 weeks if no further activity occurs. Thank you + for your contributions. +# Comment to post when closing a stale issue. Set to `false` to disable +closeComment: false diff --git a/vendor/guzzlehttp/psr7/.github/workflows/ci.yml b/vendor/guzzlehttp/psr7/.github/workflows/ci.yml new file mode 100644 index 0000000..eda7dce --- /dev/null +++ b/vendor/guzzlehttp/psr7/.github/workflows/ci.yml @@ -0,0 +1,34 @@ +name: CI + +on: + pull_request: + +jobs: + build: + name: Build + runs-on: ubuntu-latest + strategy: + max-parallel: 10 + matrix: + php: ['5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1'] + + steps: + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + coverage: 'none' + extensions: mbstring + + - name: Checkout code + uses: actions/checkout@v2 + + - name: Mimic PHP 8.0 + run: composer config platform.php 8.0.999 + if: matrix.php > 8 + + - name: Install dependencies + run: composer update --no-interaction --no-progress + + - name: Run tests + run: make test diff --git a/vendor/guzzlehttp/psr7/.github/workflows/integration.yml b/vendor/guzzlehttp/psr7/.github/workflows/integration.yml new file mode 100644 index 0000000..3c31f9e --- /dev/null +++ b/vendor/guzzlehttp/psr7/.github/workflows/integration.yml @@ -0,0 +1,37 @@ +name: Integration + +on: + pull_request: + +jobs: + + build: + name: Test + runs-on: ubuntu-latest + strategy: + max-parallel: 10 + matrix: + php: ['7.2', '7.3', '7.4', '8.0'] + + steps: + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + coverage: none + + - name: Checkout code + uses: actions/checkout@v2 + + - name: Download dependencies + uses: ramsey/composer-install@v1 + with: + composer-options: --no-interaction --optimize-autoloader + + - name: Start server + run: php -S 127.0.0.1:10002 tests/Integration/server.php & + + - name: Run tests + env: + TEST_SERVER: 127.0.0.1:10002 + run: ./vendor/bin/phpunit --testsuite Integration diff --git a/vendor/guzzlehttp/psr7/.github/workflows/static.yml b/vendor/guzzlehttp/psr7/.github/workflows/static.yml new file mode 100644 index 0000000..ab4d68b --- /dev/null +++ b/vendor/guzzlehttp/psr7/.github/workflows/static.yml @@ -0,0 +1,29 @@ +name: Static analysis + +on: + pull_request: + +jobs: + php-cs-fixer: + name: PHP-CS-Fixer + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '7.4' + coverage: none + extensions: mbstring + + - name: Download dependencies + run: composer update --no-interaction --no-progress + + - name: Download PHP CS Fixer + run: composer require "friendsofphp/php-cs-fixer:2.18.4" + + - name: Execute PHP CS Fixer + run: vendor/bin/php-cs-fixer fix --diff-format udiff --dry-run diff --git a/vendor/guzzlehttp/psr7/.php_cs.dist b/vendor/guzzlehttp/psr7/.php_cs.dist new file mode 100644 index 0000000..e4f0bd5 --- /dev/null +++ b/vendor/guzzlehttp/psr7/.php_cs.dist @@ -0,0 +1,56 @@ +setRiskyAllowed(true) + ->setRules([ + '@PSR2' => true, + 'array_syntax' => ['syntax' => 'short'], + 'concat_space' => ['spacing' => 'one'], + 'declare_strict_types' => false, + 'final_static_access' => true, + 'fully_qualified_strict_types' => true, + 'header_comment' => false, + 'is_null' => ['use_yoda_style' => true], + 'list_syntax' => ['syntax' => 'long'], + 'lowercase_cast' => true, + 'magic_method_casing' => true, + 'modernize_types_casting' => true, + 'multiline_comment_opening_closing' => true, + 'no_alias_functions' => true, + 'no_alternative_syntax' => true, + 'no_blank_lines_after_phpdoc' => true, + 'no_empty_comment' => true, + 'no_empty_phpdoc' => true, + 'no_empty_statement' => true, + 'no_extra_blank_lines' => true, + 'no_leading_import_slash' => true, + 'no_trailing_comma_in_singleline_array' => true, + 'no_unset_cast' => true, + 'no_unused_imports' => true, + 'no_whitespace_in_blank_line' => true, + 'ordered_imports' => true, + 'php_unit_ordered_covers' => true, + 'php_unit_test_annotation' => ['style' => 'prefix'], + 'php_unit_test_case_static_method_calls' => ['call_type' => 'self'], + 'phpdoc_align' => ['align' => 'vertical'], + 'phpdoc_no_useless_inheritdoc' => true, + 'phpdoc_scalar' => true, + 'phpdoc_separation' => true, + 'phpdoc_single_line_var_spacing' => true, + 'phpdoc_trim' => true, + 'phpdoc_trim_consecutive_blank_line_separation' => true, + 'phpdoc_types' => true, + 'phpdoc_types_order' => ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none'], + 'phpdoc_var_without_name' => true, + 'single_trait_insert_per_statement' => true, + 'standardize_not_equals' => true, + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in(__DIR__.'/src') + ->in(__DIR__.'/tests') + ->name('*.php') + ) +; + +return $config; diff --git a/vendor/guzzlehttp/psr7/CHANGELOG.md b/vendor/guzzlehttp/psr7/CHANGELOG.md new file mode 100644 index 0000000..f177f58 --- /dev/null +++ b/vendor/guzzlehttp/psr7/CHANGELOG.md @@ -0,0 +1,312 @@ +# Change Log + + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) +and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). + + +## Unreleased + +## 1.8.5 - 2022-03-20 + +### Fixed + +- Correct header value validation + +## 1.8.4 - 2022-03-20 + +### Fixed + +- Validate header values properly + +## 1.8.3 - 2021-10-05 + +### Fixed + +- Return `null` in caching stream size if remote size is `null` + +## 1.8.2 - 2021-04-26 + +### Fixed + +- Handle possibly unset `url` in `stream_get_meta_data` + +## 1.8.1 - 2021-03-21 + +### Fixed + +- Issue parsing IPv6 URLs +- Issue modifying ServerRequest lost all its attributes + +## 1.8.0 - 2021-03-21 + +### Added + +- Locale independent URL parsing +- Most classes got a `@final` annotation to prepare for 2.0 + +### Fixed + +- Issue when creating stream from `php://input` and curl-ext is not installed +- Broken `Utils::tryFopen()` on PHP 8 + +## 1.7.0 - 2020-09-30 + +### Added + +- Replaced functions by static methods + +### Fixed + +- Converting a non-seekable stream to a string +- Handle multiple Set-Cookie correctly +- Ignore array keys in header values when merging +- Allow multibyte characters to be parsed in `Message:bodySummary()` + +### Changed + +- Restored partial HHVM 3 support + + +## [1.6.1] - 2019-07-02 + +### Fixed + +- Accept null and bool header values again + + +## [1.6.0] - 2019-06-30 + +### Added + +- Allowed version `^3.0` of `ralouphie/getallheaders` dependency (#244) +- Added MIME type for WEBP image format (#246) +- Added more validation of values according to PSR-7 and RFC standards, e.g. status code range (#250, #272) + +### Changed + +- Tests don't pass with HHVM 4.0, so HHVM support got dropped. Other libraries like composer have done the same. (#262) +- Accept port number 0 to be valid (#270) + +### Fixed + +- Fixed subsequent reads from `php://input` in ServerRequest (#247) +- Fixed readable/writable detection for certain stream modes (#248) +- Fixed encoding of special characters in the `userInfo` component of an URI (#253) + + +## [1.5.2] - 2018-12-04 + +### Fixed + +- Check body size when getting the message summary + + +## [1.5.1] - 2018-12-04 + +### Fixed + +- Get the summary of a body only if it is readable + + +## [1.5.0] - 2018-12-03 + +### Added + +- Response first-line to response string exception (fixes #145) +- A test for #129 behavior +- `get_message_body_summary` function in order to get the message summary +- `3gp` and `mkv` mime types + +### Changed + +- Clarify exception message when stream is detached + +### Deprecated + +- Deprecated parsing folded header lines as per RFC 7230 + +### Fixed + +- Fix `AppendStream::detach` to not close streams +- `InflateStream` preserves `isSeekable` attribute of the underlying stream +- `ServerRequest::getUriFromGlobals` to support URLs in query parameters + + +Several other fixes and improvements. + + +## [1.4.2] - 2017-03-20 + +### Fixed + +- Reverted BC break to `Uri::resolve` and `Uri::removeDotSegments` by removing + calls to `trigger_error` when deprecated methods are invoked. + + +## [1.4.1] - 2017-02-27 + +### Added + +- Rriggering of silenced deprecation warnings. + +### Fixed + +- Reverted BC break by reintroducing behavior to automagically fix a URI with a + relative path and an authority by adding a leading slash to the path. It's only + deprecated now. + + +## [1.4.0] - 2017-02-21 + +### Added + +- Added common URI utility methods based on RFC 3986 (see documentation in the readme): + - `Uri::isDefaultPort` + - `Uri::isAbsolute` + - `Uri::isNetworkPathReference` + - `Uri::isAbsolutePathReference` + - `Uri::isRelativePathReference` + - `Uri::isSameDocumentReference` + - `Uri::composeComponents` + - `UriNormalizer::normalize` + - `UriNormalizer::isEquivalent` + - `UriResolver::relativize` + +### Changed + +- Ensure `ServerRequest::getUriFromGlobals` returns a URI in absolute form. +- Allow `parse_response` to parse a response without delimiting space and reason. +- Ensure each URI modification results in a valid URI according to PSR-7 discussions. + Invalid modifications will throw an exception instead of returning a wrong URI or + doing some magic. + - `(new Uri)->withPath('foo')->withHost('example.com')` will throw an exception + because the path of a URI with an authority must start with a slash "/" or be empty + - `(new Uri())->withScheme('http')` will return `'http://localhost'` + +### Deprecated + +- `Uri::resolve` in favor of `UriResolver::resolve` +- `Uri::removeDotSegments` in favor of `UriResolver::removeDotSegments` + +### Fixed + +- `Stream::read` when length parameter <= 0. +- `copy_to_stream` reads bytes in chunks instead of `maxLen` into memory. +- `ServerRequest::getUriFromGlobals` when `Host` header contains port. +- Compatibility of URIs with `file` scheme and empty host. + + +## [1.3.1] - 2016-06-25 + +### Fixed + +- `Uri::__toString` for network path references, e.g. `//example.org`. +- Missing lowercase normalization for host. +- Handling of URI components in case they are `'0'` in a lot of places, + e.g. as a user info password. +- `Uri::withAddedHeader` to correctly merge headers with different case. +- Trimming of header values in `Uri::withAddedHeader`. Header values may + be surrounded by whitespace which should be ignored according to RFC 7230 + Section 3.2.4. This does not apply to header names. +- `Uri::withAddedHeader` with an array of header values. +- `Uri::resolve` when base path has no slash and handling of fragment. +- Handling of encoding in `Uri::with(out)QueryValue` so one can pass the + key/value both in encoded as well as decoded form to those methods. This is + consistent with withPath, withQuery etc. +- `ServerRequest::withoutAttribute` when attribute value is null. + + +## [1.3.0] - 2016-04-13 + +### Added + +- Remaining interfaces needed for full PSR7 compatibility + (ServerRequestInterface, UploadedFileInterface, etc.). +- Support for stream_for from scalars. + +### Changed + +- Can now extend Uri. + +### Fixed +- A bug in validating request methods by making it more permissive. + + +## [1.2.3] - 2016-02-18 + +### Fixed + +- Support in `GuzzleHttp\Psr7\CachingStream` for seeking forward on remote + streams, which can sometimes return fewer bytes than requested with `fread`. +- Handling of gzipped responses with FNAME headers. + + +## [1.2.2] - 2016-01-22 + +### Added + +- Support for URIs without any authority. +- Support for HTTP 451 'Unavailable For Legal Reasons.' +- Support for using '0' as a filename. +- Support for including non-standard ports in Host headers. + + +## [1.2.1] - 2015-11-02 + +### Changes + +- Now supporting negative offsets when seeking to SEEK_END. + + +## [1.2.0] - 2015-08-15 + +### Changed + +- Body as `"0"` is now properly added to a response. +- Now allowing forward seeking in CachingStream. +- Now properly parsing HTTP requests that contain proxy targets in + `parse_request`. +- functions.php is now conditionally required. +- user-info is no longer dropped when resolving URIs. + + +## [1.1.0] - 2015-06-24 + +### Changed + +- URIs can now be relative. +- `multipart/form-data` headers are now overridden case-insensitively. +- URI paths no longer encode the following characters because they are allowed + in URIs: "(", ")", "*", "!", "'" +- A port is no longer added to a URI when the scheme is missing and no port is + present. + + +## 1.0.0 - 2015-05-19 + +Initial release. + +Currently unsupported: + +- `Psr\Http\Message\ServerRequestInterface` +- `Psr\Http\Message\UploadedFileInterface` + + + +[1.6.0]: https://github.com/guzzle/psr7/compare/1.5.2...1.6.0 +[1.5.2]: https://github.com/guzzle/psr7/compare/1.5.1...1.5.2 +[1.5.1]: https://github.com/guzzle/psr7/compare/1.5.0...1.5.1 +[1.5.0]: https://github.com/guzzle/psr7/compare/1.4.2...1.5.0 +[1.4.2]: https://github.com/guzzle/psr7/compare/1.4.1...1.4.2 +[1.4.1]: https://github.com/guzzle/psr7/compare/1.4.0...1.4.1 +[1.4.0]: https://github.com/guzzle/psr7/compare/1.3.1...1.4.0 +[1.3.1]: https://github.com/guzzle/psr7/compare/1.3.0...1.3.1 +[1.3.0]: https://github.com/guzzle/psr7/compare/1.2.3...1.3.0 +[1.2.3]: https://github.com/guzzle/psr7/compare/1.2.2...1.2.3 +[1.2.2]: https://github.com/guzzle/psr7/compare/1.2.1...1.2.2 +[1.2.1]: https://github.com/guzzle/psr7/compare/1.2.0...1.2.1 +[1.2.0]: https://github.com/guzzle/psr7/compare/1.1.0...1.2.0 +[1.1.0]: https://github.com/guzzle/psr7/compare/1.0.0...1.1.0 diff --git a/vendor/guzzlehttp/psr7/LICENSE b/vendor/guzzlehttp/psr7/LICENSE new file mode 100644 index 0000000..51c7ec8 --- /dev/null +++ b/vendor/guzzlehttp/psr7/LICENSE @@ -0,0 +1,26 @@ +The MIT License (MIT) + +Copyright (c) 2015 Michael Dowling +Copyright (c) 2015 Márk Sági-Kazár +Copyright (c) 2015 Graham Campbell +Copyright (c) 2016 Tobias Schultze +Copyright (c) 2016 George Mponos +Copyright (c) 2018 Tobias Nyholm + +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/guzzlehttp/psr7/README.md b/vendor/guzzlehttp/psr7/README.md new file mode 100644 index 0000000..464cae4 --- /dev/null +++ b/vendor/guzzlehttp/psr7/README.md @@ -0,0 +1,824 @@ +# PSR-7 Message Implementation + +This repository contains a full [PSR-7](http://www.php-fig.org/psr/psr-7/) +message implementation, several stream decorators, and some helpful +functionality like query string parsing. + + +[![Build Status](https://travis-ci.org/guzzle/psr7.svg?branch=master)](https://travis-ci.org/guzzle/psr7) + + +# Stream implementation + +This package comes with a number of stream implementations and stream +decorators. + + +## AppendStream + +`GuzzleHttp\Psr7\AppendStream` + +Reads from multiple streams, one after the other. + +```php +use GuzzleHttp\Psr7; + +$a = Psr7\Utils::streamFor('abc, '); +$b = Psr7\Utils::streamFor('123.'); +$composed = new Psr7\AppendStream([$a, $b]); + +$composed->addStream(Psr7\Utils::streamFor(' Above all listen to me')); + +echo $composed; // abc, 123. Above all listen to me. +``` + + +## BufferStream + +`GuzzleHttp\Psr7\BufferStream` + +Provides a buffer stream that can be written to fill a buffer, and read +from to remove bytes from the buffer. + +This stream returns a "hwm" metadata value that tells upstream consumers +what the configured high water mark of the stream is, or the maximum +preferred size of the buffer. + +```php +use GuzzleHttp\Psr7; + +// When more than 1024 bytes are in the buffer, it will begin returning +// false to writes. This is an indication that writers should slow down. +$buffer = new Psr7\BufferStream(1024); +``` + + +## CachingStream + +The CachingStream is used to allow seeking over previously read bytes on +non-seekable streams. This can be useful when transferring a non-seekable +entity body fails due to needing to rewind the stream (for example, resulting +from a redirect). Data that is read from the remote stream will be buffered in +a PHP temp stream so that previously read bytes are cached first in memory, +then on disk. + +```php +use GuzzleHttp\Psr7; + +$original = Psr7\Utils::streamFor(fopen('http://www.google.com', 'r')); +$stream = new Psr7\CachingStream($original); + +$stream->read(1024); +echo $stream->tell(); +// 1024 + +$stream->seek(0); +echo $stream->tell(); +// 0 +``` + + +## DroppingStream + +`GuzzleHttp\Psr7\DroppingStream` + +Stream decorator that begins dropping data once the size of the underlying +stream becomes too full. + +```php +use GuzzleHttp\Psr7; + +// Create an empty stream +$stream = Psr7\Utils::streamFor(); + +// Start dropping data when the stream has more than 10 bytes +$dropping = new Psr7\DroppingStream($stream, 10); + +$dropping->write('01234567890123456789'); +echo $stream; // 0123456789 +``` + + +## FnStream + +`GuzzleHttp\Psr7\FnStream` + +Compose stream implementations based on a hash of functions. + +Allows for easy testing and extension of a provided stream without needing +to create a concrete class for a simple extension point. + +```php + +use GuzzleHttp\Psr7; + +$stream = Psr7\Utils::streamFor('hi'); +$fnStream = Psr7\FnStream::decorate($stream, [ + 'rewind' => function () use ($stream) { + echo 'About to rewind - '; + $stream->rewind(); + echo 'rewound!'; + } +]); + +$fnStream->rewind(); +// Outputs: About to rewind - rewound! +``` + + +## InflateStream + +`GuzzleHttp\Psr7\InflateStream` + +Uses PHP's zlib.inflate filter to inflate deflate or gzipped content. + +This stream decorator skips the first 10 bytes of the given stream to remove +the gzip header, converts the provided stream to a PHP stream resource, +then appends the zlib.inflate filter. The stream is then converted back +to a Guzzle stream resource to be used as a Guzzle stream. + + +## LazyOpenStream + +`GuzzleHttp\Psr7\LazyOpenStream` + +Lazily reads or writes to a file that is opened only after an IO operation +take place on the stream. + +```php +use GuzzleHttp\Psr7; + +$stream = new Psr7\LazyOpenStream('/path/to/file', 'r'); +// The file has not yet been opened... + +echo $stream->read(10); +// The file is opened and read from only when needed. +``` + + +## LimitStream + +`GuzzleHttp\Psr7\LimitStream` + +LimitStream can be used to read a subset or slice of an existing stream object. +This can be useful for breaking a large file into smaller pieces to be sent in +chunks (e.g. Amazon S3's multipart upload API). + +```php +use GuzzleHttp\Psr7; + +$original = Psr7\Utils::streamFor(fopen('/tmp/test.txt', 'r+')); +echo $original->getSize(); +// >>> 1048576 + +// Limit the size of the body to 1024 bytes and start reading from byte 2048 +$stream = new Psr7\LimitStream($original, 1024, 2048); +echo $stream->getSize(); +// >>> 1024 +echo $stream->tell(); +// >>> 0 +``` + + +## MultipartStream + +`GuzzleHttp\Psr7\MultipartStream` + +Stream that when read returns bytes for a streaming multipart or +multipart/form-data stream. + + +## NoSeekStream + +`GuzzleHttp\Psr7\NoSeekStream` + +NoSeekStream wraps a stream and does not allow seeking. + +```php +use GuzzleHttp\Psr7; + +$original = Psr7\Utils::streamFor('foo'); +$noSeek = new Psr7\NoSeekStream($original); + +echo $noSeek->read(3); +// foo +var_export($noSeek->isSeekable()); +// false +$noSeek->seek(0); +var_export($noSeek->read(3)); +// NULL +``` + + +## PumpStream + +`GuzzleHttp\Psr7\PumpStream` + +Provides a read only stream that pumps data from a PHP callable. + +When invoking the provided callable, the PumpStream will pass the amount of +data requested to read to the callable. The callable can choose to ignore +this value and return fewer or more bytes than requested. Any extra data +returned by the provided callable is buffered internally until drained using +the read() function of the PumpStream. The provided callable MUST return +false when there is no more data to read. + + +## Implementing stream decorators + +Creating a stream decorator is very easy thanks to the +`GuzzleHttp\Psr7\StreamDecoratorTrait`. This trait provides methods that +implement `Psr\Http\Message\StreamInterface` by proxying to an underlying +stream. Just `use` the `StreamDecoratorTrait` and implement your custom +methods. + +For example, let's say we wanted to call a specific function each time the last +byte is read from a stream. This could be implemented by overriding the +`read()` method. + +```php +use Psr\Http\Message\StreamInterface; +use GuzzleHttp\Psr7\StreamDecoratorTrait; + +class EofCallbackStream implements StreamInterface +{ + use StreamDecoratorTrait; + + private $callback; + + public function __construct(StreamInterface $stream, callable $cb) + { + $this->stream = $stream; + $this->callback = $cb; + } + + public function read($length) + { + $result = $this->stream->read($length); + + // Invoke the callback when EOF is hit. + if ($this->eof()) { + call_user_func($this->callback); + } + + return $result; + } +} +``` + +This decorator could be added to any existing stream and used like so: + +```php +use GuzzleHttp\Psr7; + +$original = Psr7\Utils::streamFor('foo'); + +$eofStream = new EofCallbackStream($original, function () { + echo 'EOF!'; +}); + +$eofStream->read(2); +$eofStream->read(1); +// echoes "EOF!" +$eofStream->seek(0); +$eofStream->read(3); +// echoes "EOF!" +``` + + +## PHP StreamWrapper + +You can use the `GuzzleHttp\Psr7\StreamWrapper` class if you need to use a +PSR-7 stream as a PHP stream resource. + +Use the `GuzzleHttp\Psr7\StreamWrapper::getResource()` method to create a PHP +stream from a PSR-7 stream. + +```php +use GuzzleHttp\Psr7\StreamWrapper; + +$stream = GuzzleHttp\Psr7\Utils::streamFor('hello!'); +$resource = StreamWrapper::getResource($stream); +echo fread($resource, 6); // outputs hello! +``` + + +# Static API + +There are various static methods available under the `GuzzleHttp\Psr7` namespace. + + +## `GuzzleHttp\Psr7\Message::toString` + +`public static function toString(MessageInterface $message): string` + +Returns the string representation of an HTTP message. + +```php +$request = new GuzzleHttp\Psr7\Request('GET', 'http://example.com'); +echo GuzzleHttp\Psr7\Message::toString($request); +``` + + +## `GuzzleHttp\Psr7\Message::bodySummary` + +`public static function bodySummary(MessageInterface $message, int $truncateAt = 120): string|null` + +Get a short summary of the message body. + +Will return `null` if the response is not printable. + + +## `GuzzleHttp\Psr7\Message::rewindBody` + +`public static function rewindBody(MessageInterface $message): void` + +Attempts to rewind a message body and throws an exception on failure. + +The body of the message will only be rewound if a call to `tell()` +returns a value other than `0`. + + +## `GuzzleHttp\Psr7\Message::parseMessage` + +`public static function parseMessage(string $message): array` + +Parses an HTTP message into an associative array. + +The array contains the "start-line" key containing the start line of +the message, "headers" key containing an associative array of header +array values, and a "body" key containing the body of the message. + + +## `GuzzleHttp\Psr7\Message::parseRequestUri` + +`public static function parseRequestUri(string $path, array $headers): string` + +Constructs a URI for an HTTP request message. + + +## `GuzzleHttp\Psr7\Message::parseRequest` + +`public static function parseRequest(string $message): Request` + +Parses a request message string into a request object. + + +## `GuzzleHttp\Psr7\Message::parseResponse` + +`public static function parseResponse(string $message): Response` + +Parses a response message string into a response object. + + +## `GuzzleHttp\Psr7\Header::parse` + +`public static function parse(string|array $header): array` + +Parse an array of header values containing ";" separated data into an +array of associative arrays representing the header key value pair data +of the header. When a parameter does not contain a value, but just +contains a key, this function will inject a key with a '' string value. + + +## `GuzzleHttp\Psr7\Header::normalize` + +`public static function normalize(string|array $header): array` + +Converts an array of header values that may contain comma separated +headers into an array of headers with no comma separated values. + + +## `GuzzleHttp\Psr7\Query::parse` + +`public static function parse(string $str, int|bool $urlEncoding = true): array` + +Parse a query string into an associative array. + +If multiple values are found for the same key, the value of that key +value pair will become an array. This function does not parse nested +PHP style arrays into an associative array (e.g., `foo[a]=1&foo[b]=2` +will be parsed into `['foo[a]' => '1', 'foo[b]' => '2'])`. + + +## `GuzzleHttp\Psr7\Query::build` + +`public static function build(array $params, int|false $encoding = PHP_QUERY_RFC3986): string` + +Build a query string from an array of key value pairs. + +This function can use the return value of `parse()` to build a query +string. This function does not modify the provided keys when an array is +encountered (like `http_build_query()` would). + + +## `GuzzleHttp\Psr7\Utils::caselessRemove` + +`public static function caselessRemove(iterable $keys, $keys, array $data): array` + +Remove the items given by the keys, case insensitively from the data. + + +## `GuzzleHttp\Psr7\Utils::copyToStream` + +`public static function copyToStream(StreamInterface $source, StreamInterface $dest, int $maxLen = -1): void` + +Copy the contents of a stream into another stream until the given number +of bytes have been read. + + +## `GuzzleHttp\Psr7\Utils::copyToString` + +`public static function copyToString(StreamInterface $stream, int $maxLen = -1): string` + +Copy the contents of a stream into a string until the given number of +bytes have been read. + + +## `GuzzleHttp\Psr7\Utils::hash` + +`public static function hash(StreamInterface $stream, string $algo, bool $rawOutput = false): string` + +Calculate a hash of a stream. + +This method reads the entire stream to calculate a rolling hash, based on +PHP's `hash_init` functions. + + +## `GuzzleHttp\Psr7\Utils::modifyRequest` + +`public static function modifyRequest(RequestInterface $request, array $changes): RequestInterface` + +Clone and modify a request with the given changes. + +This method is useful for reducing the number of clones needed to mutate +a message. + +- method: (string) Changes the HTTP method. +- set_headers: (array) Sets the given headers. +- remove_headers: (array) Remove the given headers. +- body: (mixed) Sets the given body. +- uri: (UriInterface) Set the URI. +- query: (string) Set the query string value of the URI. +- version: (string) Set the protocol version. + + +## `GuzzleHttp\Psr7\Utils::readLine` + +`public static function readLine(StreamInterface $stream, int $maxLength = null): string` + +Read a line from the stream up to the maximum allowed buffer length. + + +## `GuzzleHttp\Psr7\Utils::streamFor` + +`public static function streamFor(resource|string|null|int|float|bool|StreamInterface|callable|\Iterator $resource = '', array $options = []): StreamInterface` + +Create a new stream based on the input type. + +Options is an associative array that can contain the following keys: + +- metadata: Array of custom metadata. +- size: Size of the stream. + +This method accepts the following `$resource` types: + +- `Psr\Http\Message\StreamInterface`: Returns the value as-is. +- `string`: Creates a stream object that uses the given string as the contents. +- `resource`: Creates a stream object that wraps the given PHP stream resource. +- `Iterator`: If the provided value implements `Iterator`, then a read-only + stream object will be created that wraps the given iterable. Each time the + stream is read from, data from the iterator will fill a buffer and will be + continuously called until the buffer is equal to the requested read size. + Subsequent read calls will first read from the buffer and then call `next` + on the underlying iterator until it is exhausted. +- `object` with `__toString()`: If the object has the `__toString()` method, + the object will be cast to a string and then a stream will be returned that + uses the string value. +- `NULL`: When `null` is passed, an empty stream object is returned. +- `callable` When a callable is passed, a read-only stream object will be + created that invokes the given callable. The callable is invoked with the + number of suggested bytes to read. The callable can return any number of + bytes, but MUST return `false` when there is no more data to return. The + stream object that wraps the callable will invoke the callable until the + number of requested bytes are available. Any additional bytes will be + buffered and used in subsequent reads. + +```php +$stream = GuzzleHttp\Psr7\Utils::streamFor('foo'); +$stream = GuzzleHttp\Psr7\Utils::streamFor(fopen('/path/to/file', 'r')); + +$generator = function ($bytes) { + for ($i = 0; $i < $bytes; $i++) { + yield ' '; + } +} + +$stream = GuzzleHttp\Psr7\Utils::streamFor($generator(100)); +``` + + +## `GuzzleHttp\Psr7\Utils::tryFopen` + +`public static function tryFopen(string $filename, string $mode): resource` + +Safely opens a PHP stream resource using a filename. + +When fopen fails, PHP normally raises a warning. This function adds an +error handler that checks for errors and throws an exception instead. + + +## `GuzzleHttp\Psr7\Utils::uriFor` + +`public static function uriFor(string|UriInterface $uri): UriInterface` + +Returns a UriInterface for the given value. + +This function accepts a string or UriInterface and returns a +UriInterface for the given value. If the value is already a +UriInterface, it is returned as-is. + + +## `GuzzleHttp\Psr7\MimeType::fromFilename` + +`public static function fromFilename(string $filename): string|null` + +Determines the mimetype of a file by looking at its extension. + + +## `GuzzleHttp\Psr7\MimeType::fromExtension` + +`public static function fromExtension(string $extension): string|null` + +Maps a file extensions to a mimetype. + + +## Upgrading from Function API + +The static API was first introduced in 1.7.0, in order to mitigate problems with functions conflicting between global and local copies of the package. The function API will be removed in 2.0.0. A migration table has been provided here for your convenience: + +| Original Function | Replacement Method | +|----------------|----------------| +| `str` | `Message::toString` | +| `uri_for` | `Utils::uriFor` | +| `stream_for` | `Utils::streamFor` | +| `parse_header` | `Header::parse` | +| `normalize_header` | `Header::normalize` | +| `modify_request` | `Utils::modifyRequest` | +| `rewind_body` | `Message::rewindBody` | +| `try_fopen` | `Utils::tryFopen` | +| `copy_to_string` | `Utils::copyToString` | +| `copy_to_stream` | `Utils::copyToStream` | +| `hash` | `Utils::hash` | +| `readline` | `Utils::readLine` | +| `parse_request` | `Message::parseRequest` | +| `parse_response` | `Message::parseResponse` | +| `parse_query` | `Query::parse` | +| `build_query` | `Query::build` | +| `mimetype_from_filename` | `MimeType::fromFilename` | +| `mimetype_from_extension` | `MimeType::fromExtension` | +| `_parse_message` | `Message::parseMessage` | +| `_parse_request_uri` | `Message::parseRequestUri` | +| `get_message_body_summary` | `Message::bodySummary` | +| `_caseless_remove` | `Utils::caselessRemove` | + + +# Additional URI Methods + +Aside from the standard `Psr\Http\Message\UriInterface` implementation in form of the `GuzzleHttp\Psr7\Uri` class, +this library also provides additional functionality when working with URIs as static methods. + +## URI Types + +An instance of `Psr\Http\Message\UriInterface` can either be an absolute URI or a relative reference. +An absolute URI has a scheme. A relative reference is used to express a URI relative to another URI, +the base URI. Relative references can be divided into several forms according to +[RFC 3986 Section 4.2](https://tools.ietf.org/html/rfc3986#section-4.2): + +- network-path references, e.g. `//example.com/path` +- absolute-path references, e.g. `/path` +- relative-path references, e.g. `subpath` + +The following methods can be used to identify the type of the URI. + +### `GuzzleHttp\Psr7\Uri::isAbsolute` + +`public static function isAbsolute(UriInterface $uri): bool` + +Whether the URI is absolute, i.e. it has a scheme. + +### `GuzzleHttp\Psr7\Uri::isNetworkPathReference` + +`public static function isNetworkPathReference(UriInterface $uri): bool` + +Whether the URI is a network-path reference. A relative reference that begins with two slash characters is +termed an network-path reference. + +### `GuzzleHttp\Psr7\Uri::isAbsolutePathReference` + +`public static function isAbsolutePathReference(UriInterface $uri): bool` + +Whether the URI is a absolute-path reference. A relative reference that begins with a single slash character is +termed an absolute-path reference. + +### `GuzzleHttp\Psr7\Uri::isRelativePathReference` + +`public static function isRelativePathReference(UriInterface $uri): bool` + +Whether the URI is a relative-path reference. A relative reference that does not begin with a slash character is +termed a relative-path reference. + +### `GuzzleHttp\Psr7\Uri::isSameDocumentReference` + +`public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null): bool` + +Whether the URI is a same-document reference. A same-document reference refers to a URI that is, aside from its +fragment component, identical to the base URI. When no base URI is given, only an empty URI reference +(apart from its fragment) is considered a same-document reference. + +## URI Components + +Additional methods to work with URI components. + +### `GuzzleHttp\Psr7\Uri::isDefaultPort` + +`public static function isDefaultPort(UriInterface $uri): bool` + +Whether the URI has the default port of the current scheme. `Psr\Http\Message\UriInterface::getPort` may return null +or the standard port. This method can be used independently of the implementation. + +### `GuzzleHttp\Psr7\Uri::composeComponents` + +`public static function composeComponents($scheme, $authority, $path, $query, $fragment): string` + +Composes a URI reference string from its various components according to +[RFC 3986 Section 5.3](https://tools.ietf.org/html/rfc3986#section-5.3). Usually this method does not need to be called +manually but instead is used indirectly via `Psr\Http\Message\UriInterface::__toString`. + +### `GuzzleHttp\Psr7\Uri::fromParts` + +`public static function fromParts(array $parts): UriInterface` + +Creates a URI from a hash of [`parse_url`](http://php.net/manual/en/function.parse-url.php) components. + + +### `GuzzleHttp\Psr7\Uri::withQueryValue` + +`public static function withQueryValue(UriInterface $uri, $key, $value): UriInterface` + +Creates a new URI with a specific query string value. Any existing query string values that exactly match the +provided key are removed and replaced with the given key value pair. A value of null will set the query string +key without a value, e.g. "key" instead of "key=value". + +### `GuzzleHttp\Psr7\Uri::withQueryValues` + +`public static function withQueryValues(UriInterface $uri, array $keyValueArray): UriInterface` + +Creates a new URI with multiple query string values. It has the same behavior as `withQueryValue()` but for an +associative array of key => value. + +### `GuzzleHttp\Psr7\Uri::withoutQueryValue` + +`public static function withoutQueryValue(UriInterface $uri, $key): UriInterface` + +Creates a new URI with a specific query string value removed. Any existing query string values that exactly match the +provided key are removed. + +## Reference Resolution + +`GuzzleHttp\Psr7\UriResolver` provides methods to resolve a URI reference in the context of a base URI according +to [RFC 3986 Section 5](https://tools.ietf.org/html/rfc3986#section-5). This is for example also what web browsers +do when resolving a link in a website based on the current request URI. + +### `GuzzleHttp\Psr7\UriResolver::resolve` + +`public static function resolve(UriInterface $base, UriInterface $rel): UriInterface` + +Converts the relative URI into a new URI that is resolved against the base URI. + +### `GuzzleHttp\Psr7\UriResolver::removeDotSegments` + +`public static function removeDotSegments(string $path): string` + +Removes dot segments from a path and returns the new path according to +[RFC 3986 Section 5.2.4](https://tools.ietf.org/html/rfc3986#section-5.2.4). + +### `GuzzleHttp\Psr7\UriResolver::relativize` + +`public static function relativize(UriInterface $base, UriInterface $target): UriInterface` + +Returns the target URI as a relative reference from the base URI. This method is the counterpart to resolve(): + +```php +(string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target)) +``` + +One use-case is to use the current request URI as base URI and then generate relative links in your documents +to reduce the document size or offer self-contained downloadable document archives. + +```php +$base = new Uri('http://example.com/a/b/'); +echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'. +echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'. +echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'. +echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'. +``` + +## Normalization and Comparison + +`GuzzleHttp\Psr7\UriNormalizer` provides methods to normalize and compare URIs according to +[RFC 3986 Section 6](https://tools.ietf.org/html/rfc3986#section-6). + +### `GuzzleHttp\Psr7\UriNormalizer::normalize` + +`public static function normalize(UriInterface $uri, $flags = self::PRESERVING_NORMALIZATIONS): UriInterface` + +Returns a normalized URI. The scheme and host component are already normalized to lowercase per PSR-7 UriInterface. +This methods adds additional normalizations that can be configured with the `$flags` parameter which is a bitmask +of normalizations to apply. The following normalizations are available: + +- `UriNormalizer::PRESERVING_NORMALIZATIONS` + + Default normalizations which only include the ones that preserve semantics. + +- `UriNormalizer::CAPITALIZE_PERCENT_ENCODING` + + All letters within a percent-encoding triplet (e.g., "%3A") are case-insensitive, and should be capitalized. + + Example: `http://example.org/a%c2%b1b` → `http://example.org/a%C2%B1b` + +- `UriNormalizer::DECODE_UNRESERVED_CHARACTERS` + + Decodes percent-encoded octets of unreserved characters. For consistency, percent-encoded octets in the ranges of + ALPHA (%41–%5A and %61–%7A), DIGIT (%30–%39), hyphen (%2D), period (%2E), underscore (%5F), or tilde (%7E) should + not be created by URI producers and, when found in a URI, should be decoded to their corresponding unreserved + characters by URI normalizers. + + Example: `http://example.org/%7Eusern%61me/` → `http://example.org/~username/` + +- `UriNormalizer::CONVERT_EMPTY_PATH` + + Converts the empty path to "/" for http and https URIs. + + Example: `http://example.org` → `http://example.org/` + +- `UriNormalizer::REMOVE_DEFAULT_HOST` + + Removes the default host of the given URI scheme from the URI. Only the "file" scheme defines the default host + "localhost". All of `file:/myfile`, `file:///myfile`, and `file://localhost/myfile` are equivalent according to + RFC 3986. + + Example: `file://localhost/myfile` → `file:///myfile` + +- `UriNormalizer::REMOVE_DEFAULT_PORT` + + Removes the default port of the given URI scheme from the URI. + + Example: `http://example.org:80/` → `http://example.org/` + +- `UriNormalizer::REMOVE_DOT_SEGMENTS` + + Removes unnecessary dot-segments. Dot-segments in relative-path references are not removed as it would + change the semantics of the URI reference. + + Example: `http://example.org/../a/b/../c/./d.html` → `http://example.org/a/c/d.html` + +- `UriNormalizer::REMOVE_DUPLICATE_SLASHES` + + Paths which include two or more adjacent slashes are converted to one. Webservers usually ignore duplicate slashes + and treat those URIs equivalent. But in theory those URIs do not need to be equivalent. So this normalization + may change the semantics. Encoded slashes (%2F) are not removed. + + Example: `http://example.org//foo///bar.html` → `http://example.org/foo/bar.html` + +- `UriNormalizer::SORT_QUERY_PARAMETERS` + + Sort query parameters with their values in alphabetical order. However, the order of parameters in a URI may be + significant (this is not defined by the standard). So this normalization is not safe and may change the semantics + of the URI. + + Example: `?lang=en&article=fred` → `?article=fred&lang=en` + +### `GuzzleHttp\Psr7\UriNormalizer::isEquivalent` + +`public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, $normalizations = self::PRESERVING_NORMALIZATIONS): bool` + +Whether two URIs can be considered equivalent. Both URIs are normalized automatically before comparison with the given +`$normalizations` bitmask. The method also accepts relative URI references and returns true when they are equivalent. +This of course assumes they will be resolved against the same base URI. If this is not the case, determination of +equivalence or difference of relative references does not mean anything. + + +## Security + +If you discover a security vulnerability within this package, please send an email to security@tidelift.com. All security vulnerabilities will be promptly addressed. Please do not disclose security-related issues publicly until a fix has been announced. Please see [Security Policy](https://github.com/guzzle/psr7/security/policy) for more information. + +## License + +Guzzle is made available under the MIT License (MIT). Please see [License File](LICENSE) for more information. + +## For Enterprise + +Available as part of the Tidelift Subscription + +The maintainers of Guzzle and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/packagist-guzzlehttp-psr7?utm_source=packagist-guzzlehttp-psr7&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) diff --git a/vendor/guzzlehttp/psr7/composer.json b/vendor/guzzlehttp/psr7/composer.json new file mode 100644 index 0000000..7ecdc8b --- /dev/null +++ b/vendor/guzzlehttp/psr7/composer.json @@ -0,0 +1,76 @@ +{ + "name": "guzzlehttp/psr7", + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": ["request", "response", "message", "stream", "http", "uri", "url", "psr-7"], + "license": "MIT", + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0", + "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.10", + "ext-zlib": "*" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + }, + "files": ["src/functions_include.php"] + }, + "autoload-dev": { + "psr-4": { + "GuzzleHttp\\Tests\\Psr7\\": "tests/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.7-dev" + } + }, + "config": { + "preferred-install": "dist", + "sort-packages": true, + "allow-plugins": { + "bamarni/composer-bin-plugin": true + } + } +} diff --git a/vendor/guzzlehttp/psr7/src/AppendStream.php b/vendor/guzzlehttp/psr7/src/AppendStream.php new file mode 100644 index 0000000..fa9153d --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/AppendStream.php @@ -0,0 +1,246 @@ +addStream($stream); + } + } + + public function __toString() + { + try { + $this->rewind(); + return $this->getContents(); + } catch (\Exception $e) { + return ''; + } + } + + /** + * Add a stream to the AppendStream + * + * @param StreamInterface $stream Stream to append. Must be readable. + * + * @throws \InvalidArgumentException if the stream is not readable + */ + public function addStream(StreamInterface $stream) + { + if (!$stream->isReadable()) { + throw new \InvalidArgumentException('Each stream must be readable'); + } + + // The stream is only seekable if all streams are seekable + if (!$stream->isSeekable()) { + $this->seekable = false; + } + + $this->streams[] = $stream; + } + + public function getContents() + { + return Utils::copyToString($this); + } + + /** + * Closes each attached stream. + * + * {@inheritdoc} + */ + public function close() + { + $this->pos = $this->current = 0; + $this->seekable = true; + + foreach ($this->streams as $stream) { + $stream->close(); + } + + $this->streams = []; + } + + /** + * Detaches each attached stream. + * + * Returns null as it's not clear which underlying stream resource to return. + * + * {@inheritdoc} + */ + public function detach() + { + $this->pos = $this->current = 0; + $this->seekable = true; + + foreach ($this->streams as $stream) { + $stream->detach(); + } + + $this->streams = []; + + return null; + } + + public function tell() + { + return $this->pos; + } + + /** + * Tries to calculate the size by adding the size of each stream. + * + * If any of the streams do not return a valid number, then the size of the + * append stream cannot be determined and null is returned. + * + * {@inheritdoc} + */ + public function getSize() + { + $size = 0; + + foreach ($this->streams as $stream) { + $s = $stream->getSize(); + if ($s === null) { + return null; + } + $size += $s; + } + + return $size; + } + + public function eof() + { + return !$this->streams || + ($this->current >= count($this->streams) - 1 && + $this->streams[$this->current]->eof()); + } + + public function rewind() + { + $this->seek(0); + } + + /** + * Attempts to seek to the given position. Only supports SEEK_SET. + * + * {@inheritdoc} + */ + public function seek($offset, $whence = SEEK_SET) + { + if (!$this->seekable) { + throw new \RuntimeException('This AppendStream is not seekable'); + } elseif ($whence !== SEEK_SET) { + throw new \RuntimeException('The AppendStream can only seek with SEEK_SET'); + } + + $this->pos = $this->current = 0; + + // Rewind each stream + foreach ($this->streams as $i => $stream) { + try { + $stream->rewind(); + } catch (\Exception $e) { + throw new \RuntimeException('Unable to seek stream ' + . $i . ' of the AppendStream', 0, $e); + } + } + + // Seek to the actual position by reading from each stream + while ($this->pos < $offset && !$this->eof()) { + $result = $this->read(min(8096, $offset - $this->pos)); + if ($result === '') { + break; + } + } + } + + /** + * Reads from all of the appended streams until the length is met or EOF. + * + * {@inheritdoc} + */ + public function read($length) + { + $buffer = ''; + $total = count($this->streams) - 1; + $remaining = $length; + $progressToNext = false; + + while ($remaining > 0) { + + // Progress to the next stream if needed. + if ($progressToNext || $this->streams[$this->current]->eof()) { + $progressToNext = false; + if ($this->current === $total) { + break; + } + $this->current++; + } + + $result = $this->streams[$this->current]->read($remaining); + + // Using a loose comparison here to match on '', false, and null + if ($result == null) { + $progressToNext = true; + continue; + } + + $buffer .= $result; + $remaining = $length - strlen($buffer); + } + + $this->pos += strlen($buffer); + + return $buffer; + } + + public function isReadable() + { + return true; + } + + public function isWritable() + { + return false; + } + + public function isSeekable() + { + return $this->seekable; + } + + public function write($string) + { + throw new \RuntimeException('Cannot write to an AppendStream'); + } + + public function getMetadata($key = null) + { + return $key ? null : []; + } +} diff --git a/vendor/guzzlehttp/psr7/src/BufferStream.php b/vendor/guzzlehttp/psr7/src/BufferStream.php new file mode 100644 index 0000000..783859c --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/BufferStream.php @@ -0,0 +1,142 @@ +hwm = $hwm; + } + + public function __toString() + { + return $this->getContents(); + } + + public function getContents() + { + $buffer = $this->buffer; + $this->buffer = ''; + + return $buffer; + } + + public function close() + { + $this->buffer = ''; + } + + public function detach() + { + $this->close(); + + return null; + } + + public function getSize() + { + return strlen($this->buffer); + } + + public function isReadable() + { + return true; + } + + public function isWritable() + { + return true; + } + + public function isSeekable() + { + return false; + } + + public function rewind() + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET) + { + throw new \RuntimeException('Cannot seek a BufferStream'); + } + + public function eof() + { + return strlen($this->buffer) === 0; + } + + public function tell() + { + throw new \RuntimeException('Cannot determine the position of a BufferStream'); + } + + /** + * Reads data from the buffer. + */ + public function read($length) + { + $currentLength = strlen($this->buffer); + + if ($length >= $currentLength) { + // No need to slice the buffer because we don't have enough data. + $result = $this->buffer; + $this->buffer = ''; + } else { + // Slice up the result to provide a subset of the buffer. + $result = substr($this->buffer, 0, $length); + $this->buffer = substr($this->buffer, $length); + } + + return $result; + } + + /** + * Writes data to the buffer. + */ + public function write($string) + { + $this->buffer .= $string; + + // TODO: What should happen here? + if (strlen($this->buffer) >= $this->hwm) { + return false; + } + + return strlen($string); + } + + public function getMetadata($key = null) + { + if ($key == 'hwm') { + return $this->hwm; + } + + return $key ? null : []; + } +} diff --git a/vendor/guzzlehttp/psr7/src/CachingStream.php b/vendor/guzzlehttp/psr7/src/CachingStream.php new file mode 100644 index 0000000..febade9 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/CachingStream.php @@ -0,0 +1,147 @@ +remoteStream = $stream; + $this->stream = $target ?: new Stream(Utils::tryFopen('php://temp', 'r+')); + } + + public function getSize() + { + $remoteSize = $this->remoteStream->getSize(); + + if (null === $remoteSize) { + return null; + } + + return max($this->stream->getSize(), $remoteSize); + } + + public function rewind() + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET) + { + if ($whence == SEEK_SET) { + $byte = $offset; + } elseif ($whence == SEEK_CUR) { + $byte = $offset + $this->tell(); + } elseif ($whence == SEEK_END) { + $size = $this->remoteStream->getSize(); + if ($size === null) { + $size = $this->cacheEntireStream(); + } + $byte = $size + $offset; + } else { + throw new \InvalidArgumentException('Invalid whence'); + } + + $diff = $byte - $this->stream->getSize(); + + if ($diff > 0) { + // Read the remoteStream until we have read in at least the amount + // of bytes requested, or we reach the end of the file. + while ($diff > 0 && !$this->remoteStream->eof()) { + $this->read($diff); + $diff = $byte - $this->stream->getSize(); + } + } else { + // We can just do a normal seek since we've already seen this byte. + $this->stream->seek($byte); + } + } + + public function read($length) + { + // Perform a regular read on any previously read data from the buffer + $data = $this->stream->read($length); + $remaining = $length - strlen($data); + + // More data was requested so read from the remote stream + if ($remaining) { + // If data was written to the buffer in a position that would have + // been filled from the remote stream, then we must skip bytes on + // the remote stream to emulate overwriting bytes from that + // position. This mimics the behavior of other PHP stream wrappers. + $remoteData = $this->remoteStream->read( + $remaining + $this->skipReadBytes + ); + + if ($this->skipReadBytes) { + $len = strlen($remoteData); + $remoteData = substr($remoteData, $this->skipReadBytes); + $this->skipReadBytes = max(0, $this->skipReadBytes - $len); + } + + $data .= $remoteData; + $this->stream->write($remoteData); + } + + return $data; + } + + public function write($string) + { + // When appending to the end of the currently read stream, you'll want + // to skip bytes from being read from the remote stream to emulate + // other stream wrappers. Basically replacing bytes of data of a fixed + // length. + $overflow = (strlen($string) + $this->tell()) - $this->remoteStream->tell(); + if ($overflow > 0) { + $this->skipReadBytes += $overflow; + } + + return $this->stream->write($string); + } + + public function eof() + { + return $this->stream->eof() && $this->remoteStream->eof(); + } + + /** + * Close both the remote stream and buffer stream + */ + public function close() + { + $this->remoteStream->close() && $this->stream->close(); + } + + private function cacheEntireStream() + { + $target = new FnStream(['write' => 'strlen']); + Utils::copyToStream($this, $target); + + return $this->tell(); + } +} diff --git a/vendor/guzzlehttp/psr7/src/DroppingStream.php b/vendor/guzzlehttp/psr7/src/DroppingStream.php new file mode 100644 index 0000000..9f7420c --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/DroppingStream.php @@ -0,0 +1,45 @@ +stream = $stream; + $this->maxLength = $maxLength; + } + + public function write($string) + { + $diff = $this->maxLength - $this->stream->getSize(); + + // Begin returning 0 when the underlying stream is too large. + if ($diff <= 0) { + return 0; + } + + // Write the stream or a subset of the stream if needed. + if (strlen($string) < $diff) { + return $this->stream->write($string); + } + + return $this->stream->write(substr($string, 0, $diff)); + } +} diff --git a/vendor/guzzlehttp/psr7/src/FnStream.php b/vendor/guzzlehttp/psr7/src/FnStream.php new file mode 100644 index 0000000..76a8cc7 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/FnStream.php @@ -0,0 +1,163 @@ +methods = $methods; + + // Create the functions on the class + foreach ($methods as $name => $fn) { + $this->{'_fn_' . $name} = $fn; + } + } + + /** + * Lazily determine which methods are not implemented. + * + * @throws \BadMethodCallException + */ + public function __get($name) + { + throw new \BadMethodCallException(str_replace('_fn_', '', $name) + . '() is not implemented in the FnStream'); + } + + /** + * The close method is called on the underlying stream only if possible. + */ + public function __destruct() + { + if (isset($this->_fn_close)) { + call_user_func($this->_fn_close); + } + } + + /** + * An unserialize would allow the __destruct to run when the unserialized value goes out of scope. + * + * @throws \LogicException + */ + public function __wakeup() + { + throw new \LogicException('FnStream should never be unserialized'); + } + + /** + * Adds custom functionality to an underlying stream by intercepting + * specific method calls. + * + * @param StreamInterface $stream Stream to decorate + * @param array $methods Hash of method name to a closure + * + * @return FnStream + */ + public static function decorate(StreamInterface $stream, array $methods) + { + // If any of the required methods were not provided, then simply + // proxy to the decorated stream. + foreach (array_diff(self::$slots, array_keys($methods)) as $diff) { + $methods[$diff] = [$stream, $diff]; + } + + return new self($methods); + } + + public function __toString() + { + return call_user_func($this->_fn___toString); + } + + public function close() + { + return call_user_func($this->_fn_close); + } + + public function detach() + { + return call_user_func($this->_fn_detach); + } + + public function getSize() + { + return call_user_func($this->_fn_getSize); + } + + public function tell() + { + return call_user_func($this->_fn_tell); + } + + public function eof() + { + return call_user_func($this->_fn_eof); + } + + public function isSeekable() + { + return call_user_func($this->_fn_isSeekable); + } + + public function rewind() + { + call_user_func($this->_fn_rewind); + } + + public function seek($offset, $whence = SEEK_SET) + { + call_user_func($this->_fn_seek, $offset, $whence); + } + + public function isWritable() + { + return call_user_func($this->_fn_isWritable); + } + + public function write($string) + { + return call_user_func($this->_fn_write, $string); + } + + public function isReadable() + { + return call_user_func($this->_fn_isReadable); + } + + public function read($length) + { + return call_user_func($this->_fn_read, $length); + } + + public function getContents() + { + return call_user_func($this->_fn_getContents); + } + + public function getMetadata($key = null) + { + return call_user_func($this->_fn_getMetadata, $key); + } +} diff --git a/vendor/guzzlehttp/psr7/src/Header.php b/vendor/guzzlehttp/psr7/src/Header.php new file mode 100644 index 0000000..865d742 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Header.php @@ -0,0 +1,71 @@ +]+>|[^=]+/', $kvp, $matches)) { + $m = $matches[0]; + if (isset($m[1])) { + $part[trim($m[0], $trimmed)] = trim($m[1], $trimmed); + } else { + $part[] = trim($m[0], $trimmed); + } + } + } + if ($part) { + $params[] = $part; + } + } + + return $params; + } + + /** + * Converts an array of header values that may contain comma separated + * headers into an array of headers with no comma separated values. + * + * @param string|array $header Header to normalize. + * + * @return array Returns the normalized header field values. + */ + public static function normalize($header) + { + if (!is_array($header)) { + return array_map('trim', explode(',', $header)); + } + + $result = []; + foreach ($header as $value) { + foreach ((array) $value as $v) { + if (strpos($v, ',') === false) { + $result[] = $v; + continue; + } + foreach (preg_split('/,(?=([^"]*"[^"]*")*[^"]*$)/', $v) as $vv) { + $result[] = trim($vv); + } + } + } + + return $result; + } +} diff --git a/vendor/guzzlehttp/psr7/src/InflateStream.php b/vendor/guzzlehttp/psr7/src/InflateStream.php new file mode 100644 index 0000000..0cbd2cc --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/InflateStream.php @@ -0,0 +1,56 @@ +read(10); + $filenameHeaderLength = $this->getLengthOfPossibleFilenameHeader($stream, $header); + // Skip the header, that is 10 + length of filename + 1 (nil) bytes + $stream = new LimitStream($stream, -1, 10 + $filenameHeaderLength); + $resource = StreamWrapper::getResource($stream); + stream_filter_append($resource, 'zlib.inflate', STREAM_FILTER_READ); + $this->stream = $stream->isSeekable() ? new Stream($resource) : new NoSeekStream(new Stream($resource)); + } + + /** + * @param StreamInterface $stream + * @param $header + * + * @return int + */ + private function getLengthOfPossibleFilenameHeader(StreamInterface $stream, $header) + { + $filename_header_length = 0; + + if (substr(bin2hex($header), 6, 2) === '08') { + // we have a filename, read until nil + $filename_header_length = 1; + while ($stream->read(1) !== chr(0)) { + $filename_header_length++; + } + } + + return $filename_header_length; + } +} diff --git a/vendor/guzzlehttp/psr7/src/LazyOpenStream.php b/vendor/guzzlehttp/psr7/src/LazyOpenStream.php new file mode 100644 index 0000000..911e127 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/LazyOpenStream.php @@ -0,0 +1,42 @@ +filename = $filename; + $this->mode = $mode; + } + + /** + * Creates the underlying stream lazily when required. + * + * @return StreamInterface + */ + protected function createStream() + { + return Utils::streamFor(Utils::tryFopen($this->filename, $this->mode)); + } +} diff --git a/vendor/guzzlehttp/psr7/src/LimitStream.php b/vendor/guzzlehttp/psr7/src/LimitStream.php new file mode 100644 index 0000000..1173ec4 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/LimitStream.php @@ -0,0 +1,157 @@ +stream = $stream; + $this->setLimit($limit); + $this->setOffset($offset); + } + + public function eof() + { + // Always return true if the underlying stream is EOF + if ($this->stream->eof()) { + return true; + } + + // No limit and the underlying stream is not at EOF + if ($this->limit == -1) { + return false; + } + + return $this->stream->tell() >= $this->offset + $this->limit; + } + + /** + * Returns the size of the limited subset of data + * {@inheritdoc} + */ + public function getSize() + { + if (null === ($length = $this->stream->getSize())) { + return null; + } elseif ($this->limit == -1) { + return $length - $this->offset; + } else { + return min($this->limit, $length - $this->offset); + } + } + + /** + * Allow for a bounded seek on the read limited stream + * {@inheritdoc} + */ + public function seek($offset, $whence = SEEK_SET) + { + if ($whence !== SEEK_SET || $offset < 0) { + throw new \RuntimeException(sprintf( + 'Cannot seek to offset %s with whence %s', + $offset, + $whence + )); + } + + $offset += $this->offset; + + if ($this->limit !== -1) { + if ($offset > $this->offset + $this->limit) { + $offset = $this->offset + $this->limit; + } + } + + $this->stream->seek($offset); + } + + /** + * Give a relative tell() + * {@inheritdoc} + */ + public function tell() + { + return $this->stream->tell() - $this->offset; + } + + /** + * Set the offset to start limiting from + * + * @param int $offset Offset to seek to and begin byte limiting from + * + * @throws \RuntimeException if the stream cannot be seeked. + */ + public function setOffset($offset) + { + $current = $this->stream->tell(); + + if ($current !== $offset) { + // If the stream cannot seek to the offset position, then read to it + if ($this->stream->isSeekable()) { + $this->stream->seek($offset); + } elseif ($current > $offset) { + throw new \RuntimeException("Could not seek to stream offset $offset"); + } else { + $this->stream->read($offset - $current); + } + } + + $this->offset = $offset; + } + + /** + * Set the limit of bytes that the decorator allows to be read from the + * stream. + * + * @param int $limit Number of bytes to allow to be read from the stream. + * Use -1 for no limit. + */ + public function setLimit($limit) + { + $this->limit = $limit; + } + + public function read($length) + { + if ($this->limit == -1) { + return $this->stream->read($length); + } + + // Check if the current position is less than the total allowed + // bytes + original offset + $remaining = ($this->offset + $this->limit) - $this->stream->tell(); + if ($remaining > 0) { + // Only return the amount of requested data, ensuring that the byte + // limit is not exceeded + return $this->stream->read(min($remaining, $length)); + } + + return ''; + } +} diff --git a/vendor/guzzlehttp/psr7/src/Message.php b/vendor/guzzlehttp/psr7/src/Message.php new file mode 100644 index 0000000..516d1cb --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Message.php @@ -0,0 +1,252 @@ +getMethod() . ' ' + . $message->getRequestTarget()) + . ' HTTP/' . $message->getProtocolVersion(); + if (!$message->hasHeader('host')) { + $msg .= "\r\nHost: " . $message->getUri()->getHost(); + } + } elseif ($message instanceof ResponseInterface) { + $msg = 'HTTP/' . $message->getProtocolVersion() . ' ' + . $message->getStatusCode() . ' ' + . $message->getReasonPhrase(); + } else { + throw new \InvalidArgumentException('Unknown message type'); + } + + foreach ($message->getHeaders() as $name => $values) { + if (strtolower($name) === 'set-cookie') { + foreach ($values as $value) { + $msg .= "\r\n{$name}: " . $value; + } + } else { + $msg .= "\r\n{$name}: " . implode(', ', $values); + } + } + + return "{$msg}\r\n\r\n" . $message->getBody(); + } + + /** + * Get a short summary of the message body. + * + * Will return `null` if the response is not printable. + * + * @param MessageInterface $message The message to get the body summary + * @param int $truncateAt The maximum allowed size of the summary + * + * @return string|null + */ + public static function bodySummary(MessageInterface $message, $truncateAt = 120) + { + $body = $message->getBody(); + + if (!$body->isSeekable() || !$body->isReadable()) { + return null; + } + + $size = $body->getSize(); + + if ($size === 0) { + return null; + } + + $summary = $body->read($truncateAt); + $body->rewind(); + + if ($size > $truncateAt) { + $summary .= ' (truncated...)'; + } + + // Matches any printable character, including unicode characters: + // letters, marks, numbers, punctuation, spacing, and separators. + if (preg_match('/[^\pL\pM\pN\pP\pS\pZ\n\r\t]/u', $summary)) { + return null; + } + + return $summary; + } + + /** + * Attempts to rewind a message body and throws an exception on failure. + * + * The body of the message will only be rewound if a call to `tell()` + * returns a value other than `0`. + * + * @param MessageInterface $message Message to rewind + * + * @throws \RuntimeException + */ + public static function rewindBody(MessageInterface $message) + { + $body = $message->getBody(); + + if ($body->tell()) { + $body->rewind(); + } + } + + /** + * Parses an HTTP message into an associative array. + * + * The array contains the "start-line" key containing the start line of + * the message, "headers" key containing an associative array of header + * array values, and a "body" key containing the body of the message. + * + * @param string $message HTTP request or response to parse. + * + * @return array + */ + public static function parseMessage($message) + { + if (!$message) { + throw new \InvalidArgumentException('Invalid message'); + } + + $message = ltrim($message, "\r\n"); + + $messageParts = preg_split("/\r?\n\r?\n/", $message, 2); + + if ($messageParts === false || count($messageParts) !== 2) { + throw new \InvalidArgumentException('Invalid message: Missing header delimiter'); + } + + list($rawHeaders, $body) = $messageParts; + $rawHeaders .= "\r\n"; // Put back the delimiter we split previously + $headerParts = preg_split("/\r?\n/", $rawHeaders, 2); + + if ($headerParts === false || count($headerParts) !== 2) { + throw new \InvalidArgumentException('Invalid message: Missing status line'); + } + + list($startLine, $rawHeaders) = $headerParts; + + if (preg_match("/(?:^HTTP\/|^[A-Z]+ \S+ HTTP\/)(\d+(?:\.\d+)?)/i", $startLine, $matches) && $matches[1] === '1.0') { + // Header folding is deprecated for HTTP/1.1, but allowed in HTTP/1.0 + $rawHeaders = preg_replace(Rfc7230::HEADER_FOLD_REGEX, ' ', $rawHeaders); + } + + /** @var array[] $headerLines */ + $count = preg_match_all(Rfc7230::HEADER_REGEX, $rawHeaders, $headerLines, PREG_SET_ORDER); + + // If these aren't the same, then one line didn't match and there's an invalid header. + if ($count !== substr_count($rawHeaders, "\n")) { + // Folding is deprecated, see https://tools.ietf.org/html/rfc7230#section-3.2.4 + if (preg_match(Rfc7230::HEADER_FOLD_REGEX, $rawHeaders)) { + throw new \InvalidArgumentException('Invalid header syntax: Obsolete line folding'); + } + + throw new \InvalidArgumentException('Invalid header syntax'); + } + + $headers = []; + + foreach ($headerLines as $headerLine) { + $headers[$headerLine[1]][] = $headerLine[2]; + } + + return [ + 'start-line' => $startLine, + 'headers' => $headers, + 'body' => $body, + ]; + } + + /** + * Constructs a URI for an HTTP request message. + * + * @param string $path Path from the start-line + * @param array $headers Array of headers (each value an array). + * + * @return string + */ + public static function parseRequestUri($path, array $headers) + { + $hostKey = array_filter(array_keys($headers), function ($k) { + return strtolower($k) === 'host'; + }); + + // If no host is found, then a full URI cannot be constructed. + if (!$hostKey) { + return $path; + } + + $host = $headers[reset($hostKey)][0]; + $scheme = substr($host, -4) === ':443' ? 'https' : 'http'; + + return $scheme . '://' . $host . '/' . ltrim($path, '/'); + } + + /** + * Parses a request message string into a request object. + * + * @param string $message Request message string. + * + * @return Request + */ + public static function parseRequest($message) + { + $data = self::parseMessage($message); + $matches = []; + if (!preg_match('/^[\S]+\s+([a-zA-Z]+:\/\/|\/).*/', $data['start-line'], $matches)) { + throw new \InvalidArgumentException('Invalid request string'); + } + $parts = explode(' ', $data['start-line'], 3); + $version = isset($parts[2]) ? explode('/', $parts[2])[1] : '1.1'; + + $request = new Request( + $parts[0], + $matches[1] === '/' ? self::parseRequestUri($parts[1], $data['headers']) : $parts[1], + $data['headers'], + $data['body'], + $version + ); + + return $matches[1] === '/' ? $request : $request->withRequestTarget($parts[1]); + } + + /** + * Parses a response message string into a response object. + * + * @param string $message Response message string. + * + * @return Response + */ + public static function parseResponse($message) + { + $data = self::parseMessage($message); + // According to https://tools.ietf.org/html/rfc7230#section-3.1.2 the space + // between status-code and reason-phrase is required. But browsers accept + // responses without space and reason as well. + if (!preg_match('/^HTTP\/.* [0-9]{3}( .*|$)/', $data['start-line'])) { + throw new \InvalidArgumentException('Invalid response string: ' . $data['start-line']); + } + $parts = explode(' ', $data['start-line'], 3); + + return new Response( + (int) $parts[1], + $data['headers'], + $data['body'], + explode('/', $parts[0])[1], + isset($parts[2]) ? $parts[2] : null + ); + } +} diff --git a/vendor/guzzlehttp/psr7/src/MessageTrait.php b/vendor/guzzlehttp/psr7/src/MessageTrait.php new file mode 100644 index 0000000..0ac8663 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/MessageTrait.php @@ -0,0 +1,270 @@ + array of values */ + private $headers = []; + + /** @var array Map of lowercase header name => original name at registration */ + private $headerNames = []; + + /** @var string */ + private $protocol = '1.1'; + + /** @var StreamInterface|null */ + private $stream; + + public function getProtocolVersion() + { + return $this->protocol; + } + + public function withProtocolVersion($version) + { + if ($this->protocol === $version) { + return $this; + } + + $new = clone $this; + $new->protocol = $version; + return $new; + } + + public function getHeaders() + { + return $this->headers; + } + + public function hasHeader($header) + { + return isset($this->headerNames[strtolower($header)]); + } + + public function getHeader($header) + { + $header = strtolower($header); + + if (!isset($this->headerNames[$header])) { + return []; + } + + $header = $this->headerNames[$header]; + + return $this->headers[$header]; + } + + public function getHeaderLine($header) + { + return implode(', ', $this->getHeader($header)); + } + + public function withHeader($header, $value) + { + $this->assertHeader($header); + $value = $this->normalizeHeaderValue($value); + $normalized = strtolower($header); + + $new = clone $this; + if (isset($new->headerNames[$normalized])) { + unset($new->headers[$new->headerNames[$normalized]]); + } + $new->headerNames[$normalized] = $header; + $new->headers[$header] = $value; + + return $new; + } + + public function withAddedHeader($header, $value) + { + $this->assertHeader($header); + $value = $this->normalizeHeaderValue($value); + $normalized = strtolower($header); + + $new = clone $this; + if (isset($new->headerNames[$normalized])) { + $header = $this->headerNames[$normalized]; + $new->headers[$header] = array_merge($this->headers[$header], $value); + } else { + $new->headerNames[$normalized] = $header; + $new->headers[$header] = $value; + } + + return $new; + } + + public function withoutHeader($header) + { + $normalized = strtolower($header); + + if (!isset($this->headerNames[$normalized])) { + return $this; + } + + $header = $this->headerNames[$normalized]; + + $new = clone $this; + unset($new->headers[$header], $new->headerNames[$normalized]); + + return $new; + } + + public function getBody() + { + if (!$this->stream) { + $this->stream = Utils::streamFor(''); + } + + return $this->stream; + } + + public function withBody(StreamInterface $body) + { + if ($body === $this->stream) { + return $this; + } + + $new = clone $this; + $new->stream = $body; + return $new; + } + + private function setHeaders(array $headers) + { + $this->headerNames = $this->headers = []; + foreach ($headers as $header => $value) { + if (is_int($header)) { + // Numeric array keys are converted to int by PHP but having a header name '123' is not forbidden by the spec + // and also allowed in withHeader(). So we need to cast it to string again for the following assertion to pass. + $header = (string) $header; + } + $this->assertHeader($header); + $value = $this->normalizeHeaderValue($value); + $normalized = strtolower($header); + if (isset($this->headerNames[$normalized])) { + $header = $this->headerNames[$normalized]; + $this->headers[$header] = array_merge($this->headers[$header], $value); + } else { + $this->headerNames[$normalized] = $header; + $this->headers[$header] = $value; + } + } + } + + /** + * @param mixed $value + * + * @return string[] + */ + private function normalizeHeaderValue($value) + { + if (!is_array($value)) { + return $this->trimAndValidateHeaderValues([$value]); + } + + if (count($value) === 0) { + throw new \InvalidArgumentException('Header value can not be an empty array.'); + } + + return $this->trimAndValidateHeaderValues($value); + } + + /** + * Trims whitespace from the header values. + * + * Spaces and tabs ought to be excluded by parsers when extracting the field value from a header field. + * + * header-field = field-name ":" OWS field-value OWS + * OWS = *( SP / HTAB ) + * + * @param mixed[] $values Header values + * + * @return string[] Trimmed header values + * + * @see https://tools.ietf.org/html/rfc7230#section-3.2.4 + */ + private function trimAndValidateHeaderValues(array $values) + { + return array_map(function ($value) { + if (!is_scalar($value) && null !== $value) { + throw new \InvalidArgumentException(sprintf( + 'Header value must be scalar or null but %s provided.', + is_object($value) ? get_class($value) : gettype($value) + )); + } + + $trimmed = trim((string) $value, " \t"); + $this->assertValue($trimmed); + + return $trimmed; + }, array_values($values)); + } + + /** + * @see https://tools.ietf.org/html/rfc7230#section-3.2 + * + * @param mixed $header + * + * @return void + */ + private function assertHeader($header) + { + if (!is_string($header)) { + throw new \InvalidArgumentException(sprintf( + 'Header name must be a string but %s provided.', + is_object($header) ? get_class($header) : gettype($header) + )); + } + + if ($header === '') { + throw new \InvalidArgumentException('Header name can not be empty.'); + } + + if (! preg_match('/^[a-zA-Z0-9\'`#$%&*+.^_|~!-]+$/', $header)) { + throw new \InvalidArgumentException( + sprintf( + '"%s" is not valid header name', + $header + ) + ); + } + } + + /** + * @param string $value + * + * @return void + * + * @see https://tools.ietf.org/html/rfc7230#section-3.2 + * + * field-value = *( field-content / obs-fold ) + * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] + * field-vchar = VCHAR / obs-text + * VCHAR = %x21-7E + * obs-text = %x80-FF + * obs-fold = CRLF 1*( SP / HTAB ) + */ + private function assertValue($value) + { + // The regular expression intentionally does not support the obs-fold production, because as + // per RFC 7230#3.2.4: + // + // A sender MUST NOT generate a message that includes + // line folding (i.e., that has any field-value that contains a match to + // the obs-fold rule) unless the message is intended for packaging + // within the message/http media type. + // + // Clients must not send a request with line folding and a server sending folded headers is + // likely very rare. Line folding is a fairly obscure feature of HTTP/1.1 and thus not accepting + // folding is not likely to break any legitimate use case. + if (! preg_match('/^[\x20\x09\x21-\x7E\x80-\xFF]*$/', $value)) { + throw new \InvalidArgumentException(sprintf('"%s" is not valid header value', $value)); + } + } +} diff --git a/vendor/guzzlehttp/psr7/src/MimeType.php b/vendor/guzzlehttp/psr7/src/MimeType.php new file mode 100644 index 0000000..205c7b1 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/MimeType.php @@ -0,0 +1,140 @@ + 'video/3gpp', + '7z' => 'application/x-7z-compressed', + 'aac' => 'audio/x-aac', + 'ai' => 'application/postscript', + 'aif' => 'audio/x-aiff', + 'asc' => 'text/plain', + 'asf' => 'video/x-ms-asf', + 'atom' => 'application/atom+xml', + 'avi' => 'video/x-msvideo', + 'bmp' => 'image/bmp', + 'bz2' => 'application/x-bzip2', + 'cer' => 'application/pkix-cert', + 'crl' => 'application/pkix-crl', + 'crt' => 'application/x-x509-ca-cert', + 'css' => 'text/css', + 'csv' => 'text/csv', + 'cu' => 'application/cu-seeme', + 'deb' => 'application/x-debian-package', + 'doc' => 'application/msword', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'dvi' => 'application/x-dvi', + 'eot' => 'application/vnd.ms-fontobject', + 'eps' => 'application/postscript', + 'epub' => 'application/epub+zip', + 'etx' => 'text/x-setext', + 'flac' => 'audio/flac', + 'flv' => 'video/x-flv', + 'gif' => 'image/gif', + 'gz' => 'application/gzip', + 'htm' => 'text/html', + 'html' => 'text/html', + 'ico' => 'image/x-icon', + 'ics' => 'text/calendar', + 'ini' => 'text/plain', + 'iso' => 'application/x-iso9660-image', + 'jar' => 'application/java-archive', + 'jpe' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'js' => 'text/javascript', + 'json' => 'application/json', + 'latex' => 'application/x-latex', + 'log' => 'text/plain', + 'm4a' => 'audio/mp4', + 'm4v' => 'video/mp4', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mov' => 'video/quicktime', + 'mkv' => 'video/x-matroska', + 'mp3' => 'audio/mpeg', + 'mp4' => 'video/mp4', + 'mp4a' => 'audio/mp4', + 'mp4v' => 'video/mp4', + 'mpe' => 'video/mpeg', + 'mpeg' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'mpg4' => 'video/mp4', + 'oga' => 'audio/ogg', + 'ogg' => 'audio/ogg', + 'ogv' => 'video/ogg', + 'ogx' => 'application/ogg', + 'pbm' => 'image/x-portable-bitmap', + 'pdf' => 'application/pdf', + 'pgm' => 'image/x-portable-graymap', + 'png' => 'image/png', + 'pnm' => 'image/x-portable-anymap', + 'ppm' => 'image/x-portable-pixmap', + 'ppt' => 'application/vnd.ms-powerpoint', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'ps' => 'application/postscript', + 'qt' => 'video/quicktime', + 'rar' => 'application/x-rar-compressed', + 'ras' => 'image/x-cmu-raster', + 'rss' => 'application/rss+xml', + 'rtf' => 'application/rtf', + 'sgm' => 'text/sgml', + 'sgml' => 'text/sgml', + 'svg' => 'image/svg+xml', + 'swf' => 'application/x-shockwave-flash', + 'tar' => 'application/x-tar', + 'tif' => 'image/tiff', + 'tiff' => 'image/tiff', + 'torrent' => 'application/x-bittorrent', + 'ttf' => 'application/x-font-ttf', + 'txt' => 'text/plain', + 'wav' => 'audio/x-wav', + 'webm' => 'video/webm', + 'webp' => 'image/webp', + 'wma' => 'audio/x-ms-wma', + 'wmv' => 'video/x-ms-wmv', + 'woff' => 'application/x-font-woff', + 'wsdl' => 'application/wsdl+xml', + 'xbm' => 'image/x-xbitmap', + 'xls' => 'application/vnd.ms-excel', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xml' => 'application/xml', + 'xpm' => 'image/x-xpixmap', + 'xwd' => 'image/x-xwindowdump', + 'yaml' => 'text/yaml', + 'yml' => 'text/yaml', + 'zip' => 'application/zip', + ]; + + $extension = strtolower($extension); + + return isset($mimetypes[$extension]) + ? $mimetypes[$extension] + : null; + } +} diff --git a/vendor/guzzlehttp/psr7/src/MultipartStream.php b/vendor/guzzlehttp/psr7/src/MultipartStream.php new file mode 100644 index 0000000..5a6079a --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/MultipartStream.php @@ -0,0 +1,158 @@ +boundary = $boundary ?: sha1(uniqid('', true)); + $this->stream = $this->createStream($elements); + } + + /** + * Get the boundary + * + * @return string + */ + public function getBoundary() + { + return $this->boundary; + } + + public function isWritable() + { + return false; + } + + /** + * Get the headers needed before transferring the content of a POST file + */ + private function getHeaders(array $headers) + { + $str = ''; + foreach ($headers as $key => $value) { + $str .= "{$key}: {$value}\r\n"; + } + + return "--{$this->boundary}\r\n" . trim($str) . "\r\n\r\n"; + } + + /** + * Create the aggregate stream that will be used to upload the POST data + */ + protected function createStream(array $elements) + { + $stream = new AppendStream(); + + foreach ($elements as $element) { + $this->addElement($stream, $element); + } + + // Add the trailing boundary with CRLF + $stream->addStream(Utils::streamFor("--{$this->boundary}--\r\n")); + + return $stream; + } + + private function addElement(AppendStream $stream, array $element) + { + foreach (['contents', 'name'] as $key) { + if (!array_key_exists($key, $element)) { + throw new \InvalidArgumentException("A '{$key}' key is required"); + } + } + + $element['contents'] = Utils::streamFor($element['contents']); + + if (empty($element['filename'])) { + $uri = $element['contents']->getMetadata('uri'); + if (substr($uri, 0, 6) !== 'php://') { + $element['filename'] = $uri; + } + } + + list($body, $headers) = $this->createElement( + $element['name'], + $element['contents'], + isset($element['filename']) ? $element['filename'] : null, + isset($element['headers']) ? $element['headers'] : [] + ); + + $stream->addStream(Utils::streamFor($this->getHeaders($headers))); + $stream->addStream($body); + $stream->addStream(Utils::streamFor("\r\n")); + } + + /** + * @return array + */ + private function createElement($name, StreamInterface $stream, $filename, array $headers) + { + // Set a default content-disposition header if one was no provided + $disposition = $this->getHeader($headers, 'content-disposition'); + if (!$disposition) { + $headers['Content-Disposition'] = ($filename === '0' || $filename) + ? sprintf( + 'form-data; name="%s"; filename="%s"', + $name, + basename($filename) + ) + : "form-data; name=\"{$name}\""; + } + + // Set a default content-length header if one was no provided + $length = $this->getHeader($headers, 'content-length'); + if (!$length) { + if ($length = $stream->getSize()) { + $headers['Content-Length'] = (string) $length; + } + } + + // Set a default Content-Type if one was not supplied + $type = $this->getHeader($headers, 'content-type'); + if (!$type && ($filename === '0' || $filename)) { + if ($type = MimeType::fromFilename($filename)) { + $headers['Content-Type'] = $type; + } + } + + return [$stream, $headers]; + } + + private function getHeader(array $headers, $key) + { + $lowercaseHeader = strtolower($key); + foreach ($headers as $k => $v) { + if (strtolower($k) === $lowercaseHeader) { + return $v; + } + } + + return null; + } +} diff --git a/vendor/guzzlehttp/psr7/src/NoSeekStream.php b/vendor/guzzlehttp/psr7/src/NoSeekStream.php new file mode 100644 index 0000000..d66bdde --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/NoSeekStream.php @@ -0,0 +1,25 @@ +source = $source; + $this->size = isset($options['size']) ? $options['size'] : null; + $this->metadata = isset($options['metadata']) ? $options['metadata'] : []; + $this->buffer = new BufferStream(); + } + + public function __toString() + { + try { + return Utils::copyToString($this); + } catch (\Exception $e) { + return ''; + } + } + + public function close() + { + $this->detach(); + } + + public function detach() + { + $this->tellPos = false; + $this->source = null; + + return null; + } + + public function getSize() + { + return $this->size; + } + + public function tell() + { + return $this->tellPos; + } + + public function eof() + { + return !$this->source; + } + + public function isSeekable() + { + return false; + } + + public function rewind() + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET) + { + throw new \RuntimeException('Cannot seek a PumpStream'); + } + + public function isWritable() + { + return false; + } + + public function write($string) + { + throw new \RuntimeException('Cannot write to a PumpStream'); + } + + public function isReadable() + { + return true; + } + + public function read($length) + { + $data = $this->buffer->read($length); + $readLen = strlen($data); + $this->tellPos += $readLen; + $remaining = $length - $readLen; + + if ($remaining) { + $this->pump($remaining); + $data .= $this->buffer->read($remaining); + $this->tellPos += strlen($data) - $readLen; + } + + return $data; + } + + public function getContents() + { + $result = ''; + while (!$this->eof()) { + $result .= $this->read(1000000); + } + + return $result; + } + + public function getMetadata($key = null) + { + if (!$key) { + return $this->metadata; + } + + return isset($this->metadata[$key]) ? $this->metadata[$key] : null; + } + + private function pump($length) + { + if ($this->source) { + do { + $data = call_user_func($this->source, $length); + if ($data === false || $data === null) { + $this->source = null; + return; + } + $this->buffer->write($data); + $length -= strlen($data); + } while ($length > 0); + } + } +} diff --git a/vendor/guzzlehttp/psr7/src/Query.php b/vendor/guzzlehttp/psr7/src/Query.php new file mode 100644 index 0000000..5a7cc03 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Query.php @@ -0,0 +1,113 @@ + '1', 'foo[b]' => '2'])`. + * + * @param string $str Query string to parse + * @param int|bool $urlEncoding How the query string is encoded + * + * @return array + */ + public static function parse($str, $urlEncoding = true) + { + $result = []; + + if ($str === '') { + return $result; + } + + if ($urlEncoding === true) { + $decoder = function ($value) { + return rawurldecode(str_replace('+', ' ', $value)); + }; + } elseif ($urlEncoding === PHP_QUERY_RFC3986) { + $decoder = 'rawurldecode'; + } elseif ($urlEncoding === PHP_QUERY_RFC1738) { + $decoder = 'urldecode'; + } else { + $decoder = function ($str) { + return $str; + }; + } + + foreach (explode('&', $str) as $kvp) { + $parts = explode('=', $kvp, 2); + $key = $decoder($parts[0]); + $value = isset($parts[1]) ? $decoder($parts[1]) : null; + if (!isset($result[$key])) { + $result[$key] = $value; + } else { + if (!is_array($result[$key])) { + $result[$key] = [$result[$key]]; + } + $result[$key][] = $value; + } + } + + return $result; + } + + /** + * Build a query string from an array of key value pairs. + * + * This function can use the return value of `parse()` to build a query + * string. This function does not modify the provided keys when an array is + * encountered (like `http_build_query()` would). + * + * @param array $params Query string parameters. + * @param int|false $encoding Set to false to not encode, PHP_QUERY_RFC3986 + * to encode using RFC3986, or PHP_QUERY_RFC1738 + * to encode using RFC1738. + * + * @return string + */ + public static function build(array $params, $encoding = PHP_QUERY_RFC3986) + { + if (!$params) { + return ''; + } + + if ($encoding === false) { + $encoder = function ($str) { + return $str; + }; + } elseif ($encoding === PHP_QUERY_RFC3986) { + $encoder = 'rawurlencode'; + } elseif ($encoding === PHP_QUERY_RFC1738) { + $encoder = 'urlencode'; + } else { + throw new \InvalidArgumentException('Invalid type'); + } + + $qs = ''; + foreach ($params as $k => $v) { + $k = $encoder($k); + if (!is_array($v)) { + $qs .= $k; + if ($v !== null) { + $qs .= '=' . $encoder($v); + } + $qs .= '&'; + } else { + foreach ($v as $vv) { + $qs .= $k; + if ($vv !== null) { + $qs .= '=' . $encoder($vv); + } + $qs .= '&'; + } + } + } + + return $qs ? (string) substr($qs, 0, -1) : ''; + } +} diff --git a/vendor/guzzlehttp/psr7/src/Request.php b/vendor/guzzlehttp/psr7/src/Request.php new file mode 100644 index 0000000..c1cdaeb --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Request.php @@ -0,0 +1,152 @@ +assertMethod($method); + if (!($uri instanceof UriInterface)) { + $uri = new Uri($uri); + } + + $this->method = strtoupper($method); + $this->uri = $uri; + $this->setHeaders($headers); + $this->protocol = $version; + + if (!isset($this->headerNames['host'])) { + $this->updateHostFromUri(); + } + + if ($body !== '' && $body !== null) { + $this->stream = Utils::streamFor($body); + } + } + + public function getRequestTarget() + { + if ($this->requestTarget !== null) { + return $this->requestTarget; + } + + $target = $this->uri->getPath(); + if ($target == '') { + $target = '/'; + } + if ($this->uri->getQuery() != '') { + $target .= '?' . $this->uri->getQuery(); + } + + return $target; + } + + public function withRequestTarget($requestTarget) + { + if (preg_match('#\s#', $requestTarget)) { + throw new InvalidArgumentException( + 'Invalid request target provided; cannot contain whitespace' + ); + } + + $new = clone $this; + $new->requestTarget = $requestTarget; + return $new; + } + + public function getMethod() + { + return $this->method; + } + + public function withMethod($method) + { + $this->assertMethod($method); + $new = clone $this; + $new->method = strtoupper($method); + return $new; + } + + public function getUri() + { + return $this->uri; + } + + public function withUri(UriInterface $uri, $preserveHost = false) + { + if ($uri === $this->uri) { + return $this; + } + + $new = clone $this; + $new->uri = $uri; + + if (!$preserveHost || !isset($this->headerNames['host'])) { + $new->updateHostFromUri(); + } + + return $new; + } + + private function updateHostFromUri() + { + $host = $this->uri->getHost(); + + if ($host == '') { + return; + } + + if (($port = $this->uri->getPort()) !== null) { + $host .= ':' . $port; + } + + if (isset($this->headerNames['host'])) { + $header = $this->headerNames['host']; + } else { + $header = 'Host'; + $this->headerNames['host'] = 'Host'; + } + // Ensure Host is the first header. + // See: http://tools.ietf.org/html/rfc7230#section-5.4 + $this->headers = [$header => [$host]] + $this->headers; + } + + private function assertMethod($method) + { + if (!is_string($method) || $method === '') { + throw new \InvalidArgumentException('Method must be a non-empty string.'); + } + } +} diff --git a/vendor/guzzlehttp/psr7/src/Response.php b/vendor/guzzlehttp/psr7/src/Response.php new file mode 100644 index 0000000..8c01a0f --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Response.php @@ -0,0 +1,155 @@ + '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', + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 306 => 'Switch Proxy', + 307 => 'Temporary 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 Time-out', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Large', + 415 => 'Unsupported Media Type', + 416 => 'Requested range not satisfiable', + 417 => 'Expectation Failed', + 418 => 'I\'m a teapot', + 422 => 'Unprocessable Entity', + 423 => 'Locked', + 424 => 'Failed Dependency', + 425 => 'Unordered Collection', + 426 => 'Upgrade Required', + 428 => 'Precondition Required', + 429 => 'Too Many Requests', + 431 => 'Request Header Fields Too Large', + 451 => 'Unavailable For Legal Reasons', + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Time-out', + 505 => 'HTTP Version not supported', + 506 => 'Variant Also Negotiates', + 507 => 'Insufficient Storage', + 508 => 'Loop Detected', + 511 => 'Network Authentication Required', + ]; + + /** @var string */ + private $reasonPhrase = ''; + + /** @var int */ + private $statusCode = 200; + + /** + * @param int $status Status code + * @param array $headers Response headers + * @param string|resource|StreamInterface|null $body Response body + * @param string $version Protocol version + * @param string|null $reason Reason phrase (when empty a default will be used based on the status code) + */ + public function __construct( + $status = 200, + array $headers = [], + $body = null, + $version = '1.1', + $reason = null + ) { + $this->assertStatusCodeIsInteger($status); + $status = (int) $status; + $this->assertStatusCodeRange($status); + + $this->statusCode = $status; + + if ($body !== '' && $body !== null) { + $this->stream = Utils::streamFor($body); + } + + $this->setHeaders($headers); + if ($reason == '' && isset(self::$phrases[$this->statusCode])) { + $this->reasonPhrase = self::$phrases[$this->statusCode]; + } else { + $this->reasonPhrase = (string) $reason; + } + + $this->protocol = $version; + } + + public function getStatusCode() + { + return $this->statusCode; + } + + public function getReasonPhrase() + { + return $this->reasonPhrase; + } + + public function withStatus($code, $reasonPhrase = '') + { + $this->assertStatusCodeIsInteger($code); + $code = (int) $code; + $this->assertStatusCodeRange($code); + + $new = clone $this; + $new->statusCode = $code; + if ($reasonPhrase == '' && isset(self::$phrases[$new->statusCode])) { + $reasonPhrase = self::$phrases[$new->statusCode]; + } + $new->reasonPhrase = (string) $reasonPhrase; + return $new; + } + + private function assertStatusCodeIsInteger($statusCode) + { + if (filter_var($statusCode, FILTER_VALIDATE_INT) === false) { + throw new \InvalidArgumentException('Status code must be an integer value.'); + } + } + + private function assertStatusCodeRange($statusCode) + { + if ($statusCode < 100 || $statusCode >= 600) { + throw new \InvalidArgumentException('Status code must be an integer value between 1xx and 5xx.'); + } + } +} diff --git a/vendor/guzzlehttp/psr7/src/Rfc7230.php b/vendor/guzzlehttp/psr7/src/Rfc7230.php new file mode 100644 index 0000000..51b571f --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Rfc7230.php @@ -0,0 +1,19 @@ +@,;:\\\"/[\]?={}\x01-\x20\x7F]++):[ \t]*+((?:[ \t]*+[\x21-\x7E\x80-\xFF]++)*+)[ \t]*+\r?\n)m"; + const HEADER_FOLD_REGEX = "(\r?\n[ \t]++)"; +} diff --git a/vendor/guzzlehttp/psr7/src/ServerRequest.php b/vendor/guzzlehttp/psr7/src/ServerRequest.php new file mode 100644 index 0000000..e6d26f5 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/ServerRequest.php @@ -0,0 +1,379 @@ +serverParams = $serverParams; + + parent::__construct($method, $uri, $headers, $body, $version); + } + + /** + * Return an UploadedFile instance array. + * + * @param array $files A array which respect $_FILES structure + * + * @return array + * + * @throws InvalidArgumentException for unrecognized values + */ + public static function normalizeFiles(array $files) + { + $normalized = []; + + foreach ($files as $key => $value) { + if ($value instanceof UploadedFileInterface) { + $normalized[$key] = $value; + } elseif (is_array($value) && isset($value['tmp_name'])) { + $normalized[$key] = self::createUploadedFileFromSpec($value); + } elseif (is_array($value)) { + $normalized[$key] = self::normalizeFiles($value); + continue; + } else { + throw new InvalidArgumentException('Invalid value in files specification'); + } + } + + return $normalized; + } + + /** + * Create and return an UploadedFile instance from a $_FILES specification. + * + * If the specification represents an array of values, this method will + * delegate to normalizeNestedFileSpec() and return that return value. + * + * @param array $value $_FILES struct + * + * @return array|UploadedFileInterface + */ + private static function createUploadedFileFromSpec(array $value) + { + if (is_array($value['tmp_name'])) { + return self::normalizeNestedFileSpec($value); + } + + return new UploadedFile( + $value['tmp_name'], + (int) $value['size'], + (int) $value['error'], + $value['name'], + $value['type'] + ); + } + + /** + * Normalize an array of file specifications. + * + * Loops through all nested files and returns a normalized array of + * UploadedFileInterface instances. + * + * @param array $files + * + * @return UploadedFileInterface[] + */ + private static function normalizeNestedFileSpec(array $files = []) + { + $normalizedFiles = []; + + foreach (array_keys($files['tmp_name']) as $key) { + $spec = [ + 'tmp_name' => $files['tmp_name'][$key], + 'size' => $files['size'][$key], + 'error' => $files['error'][$key], + 'name' => $files['name'][$key], + 'type' => $files['type'][$key], + ]; + $normalizedFiles[$key] = self::createUploadedFileFromSpec($spec); + } + + return $normalizedFiles; + } + + /** + * Return a ServerRequest populated with superglobals: + * $_GET + * $_POST + * $_COOKIE + * $_FILES + * $_SERVER + * + * @return ServerRequestInterface + */ + public static function fromGlobals() + { + $method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'GET'; + $headers = getallheaders(); + $uri = self::getUriFromGlobals(); + $body = new CachingStream(new LazyOpenStream('php://input', 'r+')); + $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? str_replace('HTTP/', '', $_SERVER['SERVER_PROTOCOL']) : '1.1'; + + $serverRequest = new ServerRequest($method, $uri, $headers, $body, $protocol, $_SERVER); + + return $serverRequest + ->withCookieParams($_COOKIE) + ->withQueryParams($_GET) + ->withParsedBody($_POST) + ->withUploadedFiles(self::normalizeFiles($_FILES)); + } + + private static function extractHostAndPortFromAuthority($authority) + { + $uri = 'http://' . $authority; + $parts = parse_url($uri); + if (false === $parts) { + return [null, null]; + } + + $host = isset($parts['host']) ? $parts['host'] : null; + $port = isset($parts['port']) ? $parts['port'] : null; + + return [$host, $port]; + } + + /** + * Get a Uri populated with values from $_SERVER. + * + * @return UriInterface + */ + public static function getUriFromGlobals() + { + $uri = new Uri(''); + + $uri = $uri->withScheme(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http'); + + $hasPort = false; + if (isset($_SERVER['HTTP_HOST'])) { + list($host, $port) = self::extractHostAndPortFromAuthority($_SERVER['HTTP_HOST']); + if ($host !== null) { + $uri = $uri->withHost($host); + } + + if ($port !== null) { + $hasPort = true; + $uri = $uri->withPort($port); + } + } elseif (isset($_SERVER['SERVER_NAME'])) { + $uri = $uri->withHost($_SERVER['SERVER_NAME']); + } elseif (isset($_SERVER['SERVER_ADDR'])) { + $uri = $uri->withHost($_SERVER['SERVER_ADDR']); + } + + if (!$hasPort && isset($_SERVER['SERVER_PORT'])) { + $uri = $uri->withPort($_SERVER['SERVER_PORT']); + } + + $hasQuery = false; + if (isset($_SERVER['REQUEST_URI'])) { + $requestUriParts = explode('?', $_SERVER['REQUEST_URI'], 2); + $uri = $uri->withPath($requestUriParts[0]); + if (isset($requestUriParts[1])) { + $hasQuery = true; + $uri = $uri->withQuery($requestUriParts[1]); + } + } + + if (!$hasQuery && isset($_SERVER['QUERY_STRING'])) { + $uri = $uri->withQuery($_SERVER['QUERY_STRING']); + } + + return $uri; + } + + /** + * {@inheritdoc} + */ + public function getServerParams() + { + return $this->serverParams; + } + + /** + * {@inheritdoc} + */ + public function getUploadedFiles() + { + return $this->uploadedFiles; + } + + /** + * {@inheritdoc} + */ + public function withUploadedFiles(array $uploadedFiles) + { + $new = clone $this; + $new->uploadedFiles = $uploadedFiles; + + return $new; + } + + /** + * {@inheritdoc} + */ + public function getCookieParams() + { + return $this->cookieParams; + } + + /** + * {@inheritdoc} + */ + public function withCookieParams(array $cookies) + { + $new = clone $this; + $new->cookieParams = $cookies; + + return $new; + } + + /** + * {@inheritdoc} + */ + public function getQueryParams() + { + return $this->queryParams; + } + + /** + * {@inheritdoc} + */ + public function withQueryParams(array $query) + { + $new = clone $this; + $new->queryParams = $query; + + return $new; + } + + /** + * {@inheritdoc} + */ + public function getParsedBody() + { + return $this->parsedBody; + } + + /** + * {@inheritdoc} + */ + public function withParsedBody($data) + { + $new = clone $this; + $new->parsedBody = $data; + + return $new; + } + + /** + * {@inheritdoc} + */ + public function getAttributes() + { + return $this->attributes; + } + + /** + * {@inheritdoc} + */ + public function getAttribute($attribute, $default = null) + { + if (false === array_key_exists($attribute, $this->attributes)) { + return $default; + } + + return $this->attributes[$attribute]; + } + + /** + * {@inheritdoc} + */ + public function withAttribute($attribute, $value) + { + $new = clone $this; + $new->attributes[$attribute] = $value; + + return $new; + } + + /** + * {@inheritdoc} + */ + public function withoutAttribute($attribute) + { + if (false === array_key_exists($attribute, $this->attributes)) { + return $this; + } + + $new = clone $this; + unset($new->attributes[$attribute]); + + return $new; + } +} diff --git a/vendor/guzzlehttp/psr7/src/Stream.php b/vendor/guzzlehttp/psr7/src/Stream.php new file mode 100644 index 0000000..3865d6d --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Stream.php @@ -0,0 +1,270 @@ +size = $options['size']; + } + + $this->customMetadata = isset($options['metadata']) + ? $options['metadata'] + : []; + + $this->stream = $stream; + $meta = stream_get_meta_data($this->stream); + $this->seekable = $meta['seekable']; + $this->readable = (bool)preg_match(self::READABLE_MODES, $meta['mode']); + $this->writable = (bool)preg_match(self::WRITABLE_MODES, $meta['mode']); + $this->uri = $this->getMetadata('uri'); + } + + /** + * Closes the stream when the destructed + */ + public function __destruct() + { + $this->close(); + } + + public function __toString() + { + try { + if ($this->isSeekable()) { + $this->seek(0); + } + return $this->getContents(); + } catch (\Exception $e) { + return ''; + } + } + + public function getContents() + { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + + $contents = stream_get_contents($this->stream); + + if ($contents === false) { + throw new \RuntimeException('Unable to read stream contents'); + } + + return $contents; + } + + public function close() + { + if (isset($this->stream)) { + if (is_resource($this->stream)) { + fclose($this->stream); + } + $this->detach(); + } + } + + public function detach() + { + if (!isset($this->stream)) { + return null; + } + + $result = $this->stream; + unset($this->stream); + $this->size = $this->uri = null; + $this->readable = $this->writable = $this->seekable = false; + + return $result; + } + + public function getSize() + { + if ($this->size !== null) { + return $this->size; + } + + if (!isset($this->stream)) { + return null; + } + + // Clear the stat cache if the stream has a URI + if ($this->uri) { + clearstatcache(true, $this->uri); + } + + $stats = fstat($this->stream); + if (isset($stats['size'])) { + $this->size = $stats['size']; + return $this->size; + } + + return null; + } + + public function isReadable() + { + return $this->readable; + } + + public function isWritable() + { + return $this->writable; + } + + public function isSeekable() + { + return $this->seekable; + } + + public function eof() + { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + + return feof($this->stream); + } + + public function tell() + { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + + $result = ftell($this->stream); + + if ($result === false) { + throw new \RuntimeException('Unable to determine stream position'); + } + + return $result; + } + + public function rewind() + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET) + { + $whence = (int) $whence; + + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + if (!$this->seekable) { + throw new \RuntimeException('Stream is not seekable'); + } + if (fseek($this->stream, $offset, $whence) === -1) { + throw new \RuntimeException('Unable to seek to stream position ' + . $offset . ' with whence ' . var_export($whence, true)); + } + } + + public function read($length) + { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + if (!$this->readable) { + throw new \RuntimeException('Cannot read from non-readable stream'); + } + if ($length < 0) { + throw new \RuntimeException('Length parameter cannot be negative'); + } + + if (0 === $length) { + return ''; + } + + $string = fread($this->stream, $length); + if (false === $string) { + throw new \RuntimeException('Unable to read from stream'); + } + + return $string; + } + + public function write($string) + { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + if (!$this->writable) { + throw new \RuntimeException('Cannot write to a non-writable stream'); + } + + // We can't know the size after writing anything + $this->size = null; + $result = fwrite($this->stream, $string); + + if ($result === false) { + throw new \RuntimeException('Unable to write to stream'); + } + + return $result; + } + + public function getMetadata($key = null) + { + if (!isset($this->stream)) { + return $key ? null : []; + } elseif (!$key) { + return $this->customMetadata + stream_get_meta_data($this->stream); + } elseif (isset($this->customMetadata[$key])) { + return $this->customMetadata[$key]; + } + + $meta = stream_get_meta_data($this->stream); + + return isset($meta[$key]) ? $meta[$key] : null; + } +} diff --git a/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php b/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php new file mode 100644 index 0000000..5025dd6 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php @@ -0,0 +1,152 @@ +stream = $stream; + } + + /** + * Magic method used to create a new stream if streams are not added in + * the constructor of a decorator (e.g., LazyOpenStream). + * + * @param string $name Name of the property (allows "stream" only). + * + * @return StreamInterface + */ + public function __get($name) + { + if ($name == 'stream') { + $this->stream = $this->createStream(); + return $this->stream; + } + + throw new \UnexpectedValueException("$name not found on class"); + } + + public function __toString() + { + try { + if ($this->isSeekable()) { + $this->seek(0); + } + return $this->getContents(); + } catch (\Exception $e) { + // Really, PHP? https://bugs.php.net/bug.php?id=53648 + trigger_error('StreamDecorator::__toString exception: ' + . (string) $e, E_USER_ERROR); + return ''; + } + } + + public function getContents() + { + return Utils::copyToString($this); + } + + /** + * Allow decorators to implement custom methods + * + * @param string $method Missing method name + * @param array $args Method arguments + * + * @return mixed + */ + public function __call($method, array $args) + { + $result = call_user_func_array([$this->stream, $method], $args); + + // Always return the wrapped object if the result is a return $this + return $result === $this->stream ? $this : $result; + } + + public function close() + { + $this->stream->close(); + } + + public function getMetadata($key = null) + { + return $this->stream->getMetadata($key); + } + + public function detach() + { + return $this->stream->detach(); + } + + public function getSize() + { + return $this->stream->getSize(); + } + + public function eof() + { + return $this->stream->eof(); + } + + public function tell() + { + return $this->stream->tell(); + } + + public function isReadable() + { + return $this->stream->isReadable(); + } + + public function isWritable() + { + return $this->stream->isWritable(); + } + + public function isSeekable() + { + return $this->stream->isSeekable(); + } + + public function rewind() + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET) + { + $this->stream->seek($offset, $whence); + } + + public function read($length) + { + return $this->stream->read($length); + } + + public function write($string) + { + return $this->stream->write($string); + } + + /** + * Implement in subclasses to dynamically create streams when requested. + * + * @return StreamInterface + * + * @throws \BadMethodCallException + */ + protected function createStream() + { + throw new \BadMethodCallException('Not implemented'); + } +} diff --git a/vendor/guzzlehttp/psr7/src/StreamWrapper.php b/vendor/guzzlehttp/psr7/src/StreamWrapper.php new file mode 100644 index 0000000..fc7cb96 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/StreamWrapper.php @@ -0,0 +1,165 @@ +isReadable()) { + $mode = $stream->isWritable() ? 'r+' : 'r'; + } elseif ($stream->isWritable()) { + $mode = 'w'; + } else { + throw new \InvalidArgumentException('The stream must be readable, ' + . 'writable, or both.'); + } + + return fopen('guzzle://stream', $mode, null, self::createStreamContext($stream)); + } + + /** + * Creates a stream context that can be used to open a stream as a php stream resource. + * + * @param StreamInterface $stream + * + * @return resource + */ + public static function createStreamContext(StreamInterface $stream) + { + return stream_context_create([ + 'guzzle' => ['stream' => $stream] + ]); + } + + /** + * Registers the stream wrapper if needed + */ + public static function register() + { + if (!in_array('guzzle', stream_get_wrappers())) { + stream_wrapper_register('guzzle', __CLASS__); + } + } + + public function stream_open($path, $mode, $options, &$opened_path) + { + $options = stream_context_get_options($this->context); + + if (!isset($options['guzzle']['stream'])) { + return false; + } + + $this->mode = $mode; + $this->stream = $options['guzzle']['stream']; + + return true; + } + + public function stream_read($count) + { + return $this->stream->read($count); + } + + public function stream_write($data) + { + return (int) $this->stream->write($data); + } + + public function stream_tell() + { + return $this->stream->tell(); + } + + public function stream_eof() + { + return $this->stream->eof(); + } + + public function stream_seek($offset, $whence) + { + $this->stream->seek($offset, $whence); + + return true; + } + + public function stream_cast($cast_as) + { + $stream = clone($this->stream); + + return $stream->detach(); + } + + public function stream_stat() + { + static $modeMap = [ + 'r' => 33060, + 'rb' => 33060, + 'r+' => 33206, + 'w' => 33188, + 'wb' => 33188 + ]; + + return [ + 'dev' => 0, + 'ino' => 0, + 'mode' => $modeMap[$this->mode], + 'nlink' => 0, + 'uid' => 0, + 'gid' => 0, + 'rdev' => 0, + 'size' => $this->stream->getSize() ?: 0, + 'atime' => 0, + 'mtime' => 0, + 'ctime' => 0, + 'blksize' => 0, + 'blocks' => 0 + ]; + } + + public function url_stat($path, $flags) + { + return [ + 'dev' => 0, + 'ino' => 0, + 'mode' => 0, + 'nlink' => 0, + 'uid' => 0, + 'gid' => 0, + 'rdev' => 0, + 'size' => 0, + 'atime' => 0, + 'mtime' => 0, + 'ctime' => 0, + 'blksize' => 0, + 'blocks' => 0 + ]; + } +} diff --git a/vendor/guzzlehttp/psr7/src/UploadedFile.php b/vendor/guzzlehttp/psr7/src/UploadedFile.php new file mode 100644 index 0000000..bf342c4 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/UploadedFile.php @@ -0,0 +1,328 @@ +setError($errorStatus); + $this->setSize($size); + $this->setClientFilename($clientFilename); + $this->setClientMediaType($clientMediaType); + + if ($this->isOk()) { + $this->setStreamOrFile($streamOrFile); + } + } + + /** + * Depending on the value set file or stream variable + * + * @param mixed $streamOrFile + * + * @throws InvalidArgumentException + */ + private function setStreamOrFile($streamOrFile) + { + if (is_string($streamOrFile)) { + $this->file = $streamOrFile; + } elseif (is_resource($streamOrFile)) { + $this->stream = new Stream($streamOrFile); + } elseif ($streamOrFile instanceof StreamInterface) { + $this->stream = $streamOrFile; + } else { + throw new InvalidArgumentException( + 'Invalid stream or file provided for UploadedFile' + ); + } + } + + /** + * @param int $error + * + * @throws InvalidArgumentException + */ + private function setError($error) + { + if (false === is_int($error)) { + throw new InvalidArgumentException( + 'Upload file error status must be an integer' + ); + } + + if (false === in_array($error, UploadedFile::$errors)) { + throw new InvalidArgumentException( + 'Invalid error status for UploadedFile' + ); + } + + $this->error = $error; + } + + /** + * @param int $size + * + * @throws InvalidArgumentException + */ + private function setSize($size) + { + if (false === is_int($size)) { + throw new InvalidArgumentException( + 'Upload file size must be an integer' + ); + } + + $this->size = $size; + } + + /** + * @param mixed $param + * + * @return bool + */ + private function isStringOrNull($param) + { + return in_array(gettype($param), ['string', 'NULL']); + } + + /** + * @param mixed $param + * + * @return bool + */ + private function isStringNotEmpty($param) + { + return is_string($param) && false === empty($param); + } + + /** + * @param string|null $clientFilename + * + * @throws InvalidArgumentException + */ + private function setClientFilename($clientFilename) + { + if (false === $this->isStringOrNull($clientFilename)) { + throw new InvalidArgumentException( + 'Upload file client filename must be a string or null' + ); + } + + $this->clientFilename = $clientFilename; + } + + /** + * @param string|null $clientMediaType + * + * @throws InvalidArgumentException + */ + private function setClientMediaType($clientMediaType) + { + if (false === $this->isStringOrNull($clientMediaType)) { + throw new InvalidArgumentException( + 'Upload file client media type must be a string or null' + ); + } + + $this->clientMediaType = $clientMediaType; + } + + /** + * Return true if there is no upload error + * + * @return bool + */ + private function isOk() + { + return $this->error === UPLOAD_ERR_OK; + } + + /** + * @return bool + */ + public function isMoved() + { + return $this->moved; + } + + /** + * @throws RuntimeException if is moved or not ok + */ + private function validateActive() + { + if (false === $this->isOk()) { + throw new RuntimeException('Cannot retrieve stream due to upload error'); + } + + if ($this->isMoved()) { + throw new RuntimeException('Cannot retrieve stream after it has already been moved'); + } + } + + /** + * {@inheritdoc} + * + * @throws RuntimeException if the upload was not successful. + */ + public function getStream() + { + $this->validateActive(); + + if ($this->stream instanceof StreamInterface) { + return $this->stream; + } + + return new LazyOpenStream($this->file, 'r+'); + } + + /** + * {@inheritdoc} + * + * @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 RuntimeException if the upload was not successful. + * @throws InvalidArgumentException if the $path 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) + { + $this->validateActive(); + + if (false === $this->isStringNotEmpty($targetPath)) { + throw new InvalidArgumentException( + 'Invalid path provided for move operation; must be a non-empty string' + ); + } + + if ($this->file) { + $this->moved = php_sapi_name() == 'cli' + ? rename($this->file, $targetPath) + : move_uploaded_file($this->file, $targetPath); + } else { + Utils::copyToStream( + $this->getStream(), + new LazyOpenStream($targetPath, 'w') + ); + + $this->moved = true; + } + + if (false === $this->moved) { + throw new RuntimeException( + sprintf('Uploaded file could not be moved to %s', $targetPath) + ); + } + } + + /** + * {@inheritdoc} + * + * @return int|null The file size in bytes or null if unknown. + */ + public function getSize() + { + return $this->size; + } + + /** + * {@inheritdoc} + * + * @see http://php.net/manual/en/features.file-upload.errors.php + * + * @return int One of PHP's UPLOAD_ERR_XXX constants. + */ + public function getError() + { + return $this->error; + } + + /** + * {@inheritdoc} + * + * @return string|null The filename sent by the client or null if none + * was provided. + */ + public function getClientFilename() + { + return $this->clientFilename; + } + + /** + * {@inheritdoc} + */ + public function getClientMediaType() + { + return $this->clientMediaType; + } +} diff --git a/vendor/guzzlehttp/psr7/src/Uri.php b/vendor/guzzlehttp/psr7/src/Uri.php new file mode 100644 index 0000000..0f9f020 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Uri.php @@ -0,0 +1,810 @@ + 80, + 'https' => 443, + 'ftp' => 21, + 'gopher' => 70, + 'nntp' => 119, + 'news' => 119, + 'telnet' => 23, + 'tn3270' => 23, + 'imap' => 143, + 'pop' => 110, + 'ldap' => 389, + ]; + + private static $charUnreserved = 'a-zA-Z0-9_\-\.~'; + private static $charSubDelims = '!\$&\'\(\)\*\+,;='; + private static $replaceQuery = ['=' => '%3D', '&' => '%26']; + + /** @var string Uri scheme. */ + private $scheme = ''; + + /** @var string Uri user info. */ + private $userInfo = ''; + + /** @var string Uri host. */ + private $host = ''; + + /** @var int|null Uri port. */ + private $port; + + /** @var string Uri path. */ + private $path = ''; + + /** @var string Uri query string. */ + private $query = ''; + + /** @var string Uri fragment. */ + private $fragment = ''; + + /** + * @param string $uri URI to parse + */ + public function __construct($uri = '') + { + // weak type check to also accept null until we can add scalar type hints + if ($uri != '') { + $parts = self::parse($uri); + if ($parts === false) { + throw new \InvalidArgumentException("Unable to parse URI: $uri"); + } + $this->applyParts($parts); + } + } + + /** + * UTF-8 aware \parse_url() replacement. + * + * The internal function produces broken output for non ASCII domain names + * (IDN) when used with locales other than "C". + * + * On the other hand, cURL understands IDN correctly only when UTF-8 locale + * is configured ("C.UTF-8", "en_US.UTF-8", etc.). + * + * @see https://bugs.php.net/bug.php?id=52923 + * @see https://www.php.net/manual/en/function.parse-url.php#114817 + * @see https://curl.haxx.se/libcurl/c/CURLOPT_URL.html#ENCODING + * + * @param string $url + * + * @return array|false + */ + private static function parse($url) + { + // If IPv6 + $prefix = ''; + if (preg_match('%^(.*://\[[0-9:a-f]+\])(.*?)$%', $url, $matches)) { + $prefix = $matches[1]; + $url = $matches[2]; + } + + $encodedUrl = preg_replace_callback( + '%[^:/@?&=#]+%usD', + static function ($matches) { + return urlencode($matches[0]); + }, + $url + ); + + $result = parse_url($prefix . $encodedUrl); + + if ($result === false) { + return false; + } + + return array_map('urldecode', $result); + } + + public function __toString() + { + return self::composeComponents( + $this->scheme, + $this->getAuthority(), + $this->path, + $this->query, + $this->fragment + ); + } + + /** + * Composes a URI reference string from its various components. + * + * Usually this method does not need to be called manually but instead is used indirectly via + * `Psr\Http\Message\UriInterface::__toString`. + * + * PSR-7 UriInterface treats an empty component the same as a missing component as + * getQuery(), getFragment() etc. always return a string. This explains the slight + * difference to RFC 3986 Section 5.3. + * + * Another adjustment is that the authority separator is added even when the authority is missing/empty + * for the "file" scheme. This is because PHP stream functions like `file_get_contents` only work with + * `file:///myfile` but not with `file:/myfile` although they are equivalent according to RFC 3986. But + * `file:///` is the more common syntax for the file scheme anyway (Chrome for example redirects to + * that format). + * + * @param string $scheme + * @param string $authority + * @param string $path + * @param string $query + * @param string $fragment + * + * @return string + * + * @link https://tools.ietf.org/html/rfc3986#section-5.3 + */ + public static function composeComponents($scheme, $authority, $path, $query, $fragment) + { + $uri = ''; + + // weak type checks to also accept null until we can add scalar type hints + if ($scheme != '') { + $uri .= $scheme . ':'; + } + + if ($authority != ''|| $scheme === 'file') { + $uri .= '//' . $authority; + } + + $uri .= $path; + + if ($query != '') { + $uri .= '?' . $query; + } + + if ($fragment != '') { + $uri .= '#' . $fragment; + } + + return $uri; + } + + /** + * Whether the URI has the default port of the current scheme. + * + * `Psr\Http\Message\UriInterface::getPort` may return null or the standard port. This method can be used + * independently of the implementation. + * + * @param UriInterface $uri + * + * @return bool + */ + public static function isDefaultPort(UriInterface $uri) + { + return $uri->getPort() === null + || (isset(self::$defaultPorts[$uri->getScheme()]) && $uri->getPort() === self::$defaultPorts[$uri->getScheme()]); + } + + /** + * Whether the URI is absolute, i.e. it has a scheme. + * + * An instance of UriInterface can either be an absolute URI or a relative reference. This method returns true + * if it is the former. An absolute URI has a scheme. A relative reference is used to express a URI relative + * to another URI, the base URI. Relative references can be divided into several forms: + * - network-path references, e.g. '//example.com/path' + * - absolute-path references, e.g. '/path' + * - relative-path references, e.g. 'subpath' + * + * @param UriInterface $uri + * + * @return bool + * + * @see Uri::isNetworkPathReference + * @see Uri::isAbsolutePathReference + * @see Uri::isRelativePathReference + * @link https://tools.ietf.org/html/rfc3986#section-4 + */ + public static function isAbsolute(UriInterface $uri) + { + return $uri->getScheme() !== ''; + } + + /** + * Whether the URI is a network-path reference. + * + * A relative reference that begins with two slash characters is termed an network-path reference. + * + * @param UriInterface $uri + * + * @return bool + * + * @link https://tools.ietf.org/html/rfc3986#section-4.2 + */ + public static function isNetworkPathReference(UriInterface $uri) + { + return $uri->getScheme() === '' && $uri->getAuthority() !== ''; + } + + /** + * Whether the URI is a absolute-path reference. + * + * A relative reference that begins with a single slash character is termed an absolute-path reference. + * + * @param UriInterface $uri + * + * @return bool + * + * @link https://tools.ietf.org/html/rfc3986#section-4.2 + */ + public static function isAbsolutePathReference(UriInterface $uri) + { + return $uri->getScheme() === '' + && $uri->getAuthority() === '' + && isset($uri->getPath()[0]) + && $uri->getPath()[0] === '/'; + } + + /** + * Whether the URI is a relative-path reference. + * + * A relative reference that does not begin with a slash character is termed a relative-path reference. + * + * @param UriInterface $uri + * + * @return bool + * + * @link https://tools.ietf.org/html/rfc3986#section-4.2 + */ + public static function isRelativePathReference(UriInterface $uri) + { + return $uri->getScheme() === '' + && $uri->getAuthority() === '' + && (!isset($uri->getPath()[0]) || $uri->getPath()[0] !== '/'); + } + + /** + * Whether the URI is a same-document reference. + * + * A same-document reference refers to a URI that is, aside from its fragment + * component, identical to the base URI. When no base URI is given, only an empty + * URI reference (apart from its fragment) is considered a same-document reference. + * + * @param UriInterface $uri The URI to check + * @param UriInterface|null $base An optional base URI to compare against + * + * @return bool + * + * @link https://tools.ietf.org/html/rfc3986#section-4.4 + */ + public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null) + { + if ($base !== null) { + $uri = UriResolver::resolve($base, $uri); + + return ($uri->getScheme() === $base->getScheme()) + && ($uri->getAuthority() === $base->getAuthority()) + && ($uri->getPath() === $base->getPath()) + && ($uri->getQuery() === $base->getQuery()); + } + + return $uri->getScheme() === '' && $uri->getAuthority() === '' && $uri->getPath() === '' && $uri->getQuery() === ''; + } + + /** + * Removes dot segments from a path and returns the new path. + * + * @param string $path + * + * @return string + * + * @deprecated since version 1.4. Use UriResolver::removeDotSegments instead. + * @see UriResolver::removeDotSegments + */ + public static function removeDotSegments($path) + { + return UriResolver::removeDotSegments($path); + } + + /** + * Converts the relative URI into a new URI that is resolved against the base URI. + * + * @param UriInterface $base Base URI + * @param string|UriInterface $rel Relative URI + * + * @return UriInterface + * + * @deprecated since version 1.4. Use UriResolver::resolve instead. + * @see UriResolver::resolve + */ + public static function resolve(UriInterface $base, $rel) + { + if (!($rel instanceof UriInterface)) { + $rel = new self($rel); + } + + return UriResolver::resolve($base, $rel); + } + + /** + * Creates a new URI with a specific query string value removed. + * + * Any existing query string values that exactly match the provided key are + * removed. + * + * @param UriInterface $uri URI to use as a base. + * @param string $key Query string key to remove. + * + * @return UriInterface + */ + public static function withoutQueryValue(UriInterface $uri, $key) + { + $result = self::getFilteredQueryString($uri, [$key]); + + return $uri->withQuery(implode('&', $result)); + } + + /** + * Creates a new URI with a specific query string value. + * + * Any existing query string values that exactly match the provided key are + * removed and replaced with the given key value pair. + * + * A value of null will set the query string key without a value, e.g. "key" + * instead of "key=value". + * + * @param UriInterface $uri URI to use as a base. + * @param string $key Key to set. + * @param string|null $value Value to set + * + * @return UriInterface + */ + public static function withQueryValue(UriInterface $uri, $key, $value) + { + $result = self::getFilteredQueryString($uri, [$key]); + + $result[] = self::generateQueryString($key, $value); + + return $uri->withQuery(implode('&', $result)); + } + + /** + * Creates a new URI with multiple specific query string values. + * + * It has the same behavior as withQueryValue() but for an associative array of key => value. + * + * @param UriInterface $uri URI to use as a base. + * @param array $keyValueArray Associative array of key and values + * + * @return UriInterface + */ + public static function withQueryValues(UriInterface $uri, array $keyValueArray) + { + $result = self::getFilteredQueryString($uri, array_keys($keyValueArray)); + + foreach ($keyValueArray as $key => $value) { + $result[] = self::generateQueryString($key, $value); + } + + return $uri->withQuery(implode('&', $result)); + } + + /** + * Creates a URI from a hash of `parse_url` components. + * + * @param array $parts + * + * @return UriInterface + * + * @link http://php.net/manual/en/function.parse-url.php + * + * @throws \InvalidArgumentException If the components do not form a valid URI. + */ + public static function fromParts(array $parts) + { + $uri = new self(); + $uri->applyParts($parts); + $uri->validateState(); + + return $uri; + } + + public function getScheme() + { + return $this->scheme; + } + + public function getAuthority() + { + $authority = $this->host; + if ($this->userInfo !== '') { + $authority = $this->userInfo . '@' . $authority; + } + + if ($this->port !== null) { + $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) + { + $scheme = $this->filterScheme($scheme); + + if ($this->scheme === $scheme) { + return $this; + } + + $new = clone $this; + $new->scheme = $scheme; + $new->removeDefaultPort(); + $new->validateState(); + + return $new; + } + + public function withUserInfo($user, $password = null) + { + $info = $this->filterUserInfoComponent($user); + if ($password !== null) { + $info .= ':' . $this->filterUserInfoComponent($password); + } + + if ($this->userInfo === $info) { + return $this; + } + + $new = clone $this; + $new->userInfo = $info; + $new->validateState(); + + return $new; + } + + public function withHost($host) + { + $host = $this->filterHost($host); + + if ($this->host === $host) { + return $this; + } + + $new = clone $this; + $new->host = $host; + $new->validateState(); + + return $new; + } + + public function withPort($port) + { + $port = $this->filterPort($port); + + if ($this->port === $port) { + return $this; + } + + $new = clone $this; + $new->port = $port; + $new->removeDefaultPort(); + $new->validateState(); + + return $new; + } + + public function withPath($path) + { + $path = $this->filterPath($path); + + if ($this->path === $path) { + return $this; + } + + $new = clone $this; + $new->path = $path; + $new->validateState(); + + return $new; + } + + public function withQuery($query) + { + $query = $this->filterQueryAndFragment($query); + + if ($this->query === $query) { + return $this; + } + + $new = clone $this; + $new->query = $query; + + return $new; + } + + public function withFragment($fragment) + { + $fragment = $this->filterQueryAndFragment($fragment); + + if ($this->fragment === $fragment) { + return $this; + } + + $new = clone $this; + $new->fragment = $fragment; + + return $new; + } + + /** + * Apply parse_url parts to a URI. + * + * @param array $parts Array of parse_url parts to apply. + */ + private function applyParts(array $parts) + { + $this->scheme = isset($parts['scheme']) + ? $this->filterScheme($parts['scheme']) + : ''; + $this->userInfo = isset($parts['user']) + ? $this->filterUserInfoComponent($parts['user']) + : ''; + $this->host = isset($parts['host']) + ? $this->filterHost($parts['host']) + : ''; + $this->port = isset($parts['port']) + ? $this->filterPort($parts['port']) + : null; + $this->path = isset($parts['path']) + ? $this->filterPath($parts['path']) + : ''; + $this->query = isset($parts['query']) + ? $this->filterQueryAndFragment($parts['query']) + : ''; + $this->fragment = isset($parts['fragment']) + ? $this->filterQueryAndFragment($parts['fragment']) + : ''; + if (isset($parts['pass'])) { + $this->userInfo .= ':' . $this->filterUserInfoComponent($parts['pass']); + } + + $this->removeDefaultPort(); + } + + /** + * @param string $scheme + * + * @return string + * + * @throws \InvalidArgumentException If the scheme is invalid. + */ + private function filterScheme($scheme) + { + if (!is_string($scheme)) { + throw new \InvalidArgumentException('Scheme must be a string'); + } + + return \strtr($scheme, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); + } + + /** + * @param string $component + * + * @return string + * + * @throws \InvalidArgumentException If the user info is invalid. + */ + private function filterUserInfoComponent($component) + { + if (!is_string($component)) { + throw new \InvalidArgumentException('User info must be a string'); + } + + return preg_replace_callback( + '/(?:[^%' . self::$charUnreserved . self::$charSubDelims . ']+|%(?![A-Fa-f0-9]{2}))/', + [$this, 'rawurlencodeMatchZero'], + $component + ); + } + + /** + * @param string $host + * + * @return string + * + * @throws \InvalidArgumentException If the host is invalid. + */ + private function filterHost($host) + { + if (!is_string($host)) { + throw new \InvalidArgumentException('Host must be a string'); + } + + return \strtr($host, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); + } + + /** + * @param int|null $port + * + * @return int|null + * + * @throws \InvalidArgumentException If the port is invalid. + */ + private function filterPort($port) + { + if ($port === null) { + return null; + } + + $port = (int) $port; + if (0 > $port || 0xffff < $port) { + throw new \InvalidArgumentException( + sprintf('Invalid port: %d. Must be between 0 and 65535', $port) + ); + } + + return $port; + } + + /** + * @param UriInterface $uri + * @param array $keys + * + * @return array + */ + private static function getFilteredQueryString(UriInterface $uri, array $keys) + { + $current = $uri->getQuery(); + + if ($current === '') { + return []; + } + + $decodedKeys = array_map('rawurldecode', $keys); + + return array_filter(explode('&', $current), function ($part) use ($decodedKeys) { + return !in_array(rawurldecode(explode('=', $part)[0]), $decodedKeys, true); + }); + } + + /** + * @param string $key + * @param string|null $value + * + * @return string + */ + private static function generateQueryString($key, $value) + { + // Query string separators ("=", "&") within the key or value need to be encoded + // (while preventing double-encoding) before setting the query string. All other + // chars that need percent-encoding will be encoded by withQuery(). + $queryString = strtr($key, self::$replaceQuery); + + if ($value !== null) { + $queryString .= '=' . strtr($value, self::$replaceQuery); + } + + return $queryString; + } + + private function removeDefaultPort() + { + if ($this->port !== null && self::isDefaultPort($this)) { + $this->port = null; + } + } + + /** + * Filters the path of a URI + * + * @param string $path + * + * @return string + * + * @throws \InvalidArgumentException If the path is invalid. + */ + private function filterPath($path) + { + if (!is_string($path)) { + throw new \InvalidArgumentException('Path must be a string'); + } + + return preg_replace_callback( + '/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/]++|%(?![A-Fa-f0-9]{2}))/', + [$this, 'rawurlencodeMatchZero'], + $path + ); + } + + /** + * Filters the query string or fragment of a URI. + * + * @param string $str + * + * @return string + * + * @throws \InvalidArgumentException If the query or fragment is invalid. + */ + private function filterQueryAndFragment($str) + { + if (!is_string($str)) { + throw new \InvalidArgumentException('Query and fragment must be a string'); + } + + return preg_replace_callback( + '/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/', + [$this, 'rawurlencodeMatchZero'], + $str + ); + } + + private function rawurlencodeMatchZero(array $match) + { + return rawurlencode($match[0]); + } + + private function validateState() + { + if ($this->host === '' && ($this->scheme === 'http' || $this->scheme === 'https')) { + $this->host = self::HTTP_DEFAULT_HOST; + } + + if ($this->getAuthority() === '') { + if (0 === strpos($this->path, '//')) { + throw new \InvalidArgumentException('The path of a URI without an authority must not start with two slashes "//"'); + } + if ($this->scheme === '' && false !== strpos(explode('/', $this->path, 2)[0], ':')) { + throw new \InvalidArgumentException('A relative URI must not have a path beginning with a segment containing a colon'); + } + } elseif (isset($this->path[0]) && $this->path[0] !== '/') { + @trigger_error( + 'The path of a URI with an authority must start with a slash "/" or be empty. Automagically fixing the URI ' . + 'by adding a leading slash to the path is deprecated since version 1.4 and will throw an exception instead.', + E_USER_DEPRECATED + ); + $this->path = '/' . $this->path; + //throw new \InvalidArgumentException('The path of a URI with an authority must start with a slash "/" or be empty'); + } + } +} diff --git a/vendor/guzzlehttp/psr7/src/UriNormalizer.php b/vendor/guzzlehttp/psr7/src/UriNormalizer.php new file mode 100644 index 0000000..81419ea --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/UriNormalizer.php @@ -0,0 +1,219 @@ +getPath() === '' && + ($uri->getScheme() === 'http' || $uri->getScheme() === 'https') + ) { + $uri = $uri->withPath('/'); + } + + if ($flags & self::REMOVE_DEFAULT_HOST && $uri->getScheme() === 'file' && $uri->getHost() === 'localhost') { + $uri = $uri->withHost(''); + } + + if ($flags & self::REMOVE_DEFAULT_PORT && $uri->getPort() !== null && Uri::isDefaultPort($uri)) { + $uri = $uri->withPort(null); + } + + if ($flags & self::REMOVE_DOT_SEGMENTS && !Uri::isRelativePathReference($uri)) { + $uri = $uri->withPath(UriResolver::removeDotSegments($uri->getPath())); + } + + if ($flags & self::REMOVE_DUPLICATE_SLASHES) { + $uri = $uri->withPath(preg_replace('#//++#', '/', $uri->getPath())); + } + + if ($flags & self::SORT_QUERY_PARAMETERS && $uri->getQuery() !== '') { + $queryKeyValues = explode('&', $uri->getQuery()); + sort($queryKeyValues); + $uri = $uri->withQuery(implode('&', $queryKeyValues)); + } + + return $uri; + } + + /** + * Whether two URIs can be considered equivalent. + * + * Both URIs are normalized automatically before comparison with the given $normalizations bitmask. The method also + * accepts relative URI references and returns true when they are equivalent. This of course assumes they will be + * resolved against the same base URI. If this is not the case, determination of equivalence or difference of + * relative references does not mean anything. + * + * @param UriInterface $uri1 An URI to compare + * @param UriInterface $uri2 An URI to compare + * @param int $normalizations A bitmask of normalizations to apply, see constants + * + * @return bool + * + * @link https://tools.ietf.org/html/rfc3986#section-6.1 + */ + public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, $normalizations = self::PRESERVING_NORMALIZATIONS) + { + return (string) self::normalize($uri1, $normalizations) === (string) self::normalize($uri2, $normalizations); + } + + private static function capitalizePercentEncoding(UriInterface $uri) + { + $regex = '/(?:%[A-Fa-f0-9]{2})++/'; + + $callback = function (array $match) { + return strtoupper($match[0]); + }; + + return + $uri->withPath( + preg_replace_callback($regex, $callback, $uri->getPath()) + )->withQuery( + preg_replace_callback($regex, $callback, $uri->getQuery()) + ); + } + + private static function decodeUnreservedCharacters(UriInterface $uri) + { + $regex = '/%(?:2D|2E|5F|7E|3[0-9]|[46][1-9A-F]|[57][0-9A])/i'; + + $callback = function (array $match) { + return rawurldecode($match[0]); + }; + + return + $uri->withPath( + preg_replace_callback($regex, $callback, $uri->getPath()) + )->withQuery( + preg_replace_callback($regex, $callback, $uri->getQuery()) + ); + } + + private function __construct() + { + // cannot be instantiated + } +} diff --git a/vendor/guzzlehttp/psr7/src/UriResolver.php b/vendor/guzzlehttp/psr7/src/UriResolver.php new file mode 100644 index 0000000..a3cb15d --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/UriResolver.php @@ -0,0 +1,222 @@ +getScheme() != '') { + return $rel->withPath(self::removeDotSegments($rel->getPath())); + } + + if ($rel->getAuthority() != '') { + $targetAuthority = $rel->getAuthority(); + $targetPath = self::removeDotSegments($rel->getPath()); + $targetQuery = $rel->getQuery(); + } else { + $targetAuthority = $base->getAuthority(); + if ($rel->getPath() === '') { + $targetPath = $base->getPath(); + $targetQuery = $rel->getQuery() != '' ? $rel->getQuery() : $base->getQuery(); + } else { + if ($rel->getPath()[0] === '/') { + $targetPath = $rel->getPath(); + } else { + if ($targetAuthority != '' && $base->getPath() === '') { + $targetPath = '/' . $rel->getPath(); + } else { + $lastSlashPos = strrpos($base->getPath(), '/'); + if ($lastSlashPos === false) { + $targetPath = $rel->getPath(); + } else { + $targetPath = substr($base->getPath(), 0, $lastSlashPos + 1) . $rel->getPath(); + } + } + } + $targetPath = self::removeDotSegments($targetPath); + $targetQuery = $rel->getQuery(); + } + } + + return new Uri(Uri::composeComponents( + $base->getScheme(), + $targetAuthority, + $targetPath, + $targetQuery, + $rel->getFragment() + )); + } + + /** + * Returns the target URI as a relative reference from the base URI. + * + * This method is the counterpart to resolve(): + * + * (string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target)) + * + * One use-case is to use the current request URI as base URI and then generate relative links in your documents + * to reduce the document size or offer self-contained downloadable document archives. + * + * $base = new Uri('http://example.com/a/b/'); + * echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'. + * echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'. + * echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'. + * echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'. + * + * This method also accepts a target that is already relative and will try to relativize it further. Only a + * relative-path reference will be returned as-is. + * + * echo UriResolver::relativize($base, new Uri('/a/b/c')); // prints 'c' as well + * + * @param UriInterface $base Base URI + * @param UriInterface $target Target URI + * + * @return UriInterface The relative URI reference + */ + public static function relativize(UriInterface $base, UriInterface $target) + { + if ($target->getScheme() !== '' && + ($base->getScheme() !== $target->getScheme() || $target->getAuthority() === '' && $base->getAuthority() !== '') + ) { + return $target; + } + + if (Uri::isRelativePathReference($target)) { + // As the target is already highly relative we return it as-is. It would be possible to resolve + // the target with `$target = self::resolve($base, $target);` and then try make it more relative + // by removing a duplicate query. But let's not do that automatically. + return $target; + } + + if ($target->getAuthority() !== '' && $base->getAuthority() !== $target->getAuthority()) { + return $target->withScheme(''); + } + + // We must remove the path before removing the authority because if the path starts with two slashes, the URI + // would turn invalid. And we also cannot set a relative path before removing the authority, as that is also + // invalid. + $emptyPathUri = $target->withScheme('')->withPath('')->withUserInfo('')->withPort(null)->withHost(''); + + if ($base->getPath() !== $target->getPath()) { + return $emptyPathUri->withPath(self::getRelativePath($base, $target)); + } + + if ($base->getQuery() === $target->getQuery()) { + // Only the target fragment is left. And it must be returned even if base and target fragment are the same. + return $emptyPathUri->withQuery(''); + } + + // If the base URI has a query but the target has none, we cannot return an empty path reference as it would + // inherit the base query component when resolving. + if ($target->getQuery() === '') { + $segments = explode('/', $target->getPath()); + $lastSegment = end($segments); + + return $emptyPathUri->withPath($lastSegment === '' ? './' : $lastSegment); + } + + return $emptyPathUri; + } + + private static function getRelativePath(UriInterface $base, UriInterface $target) + { + $sourceSegments = explode('/', $base->getPath()); + $targetSegments = explode('/', $target->getPath()); + array_pop($sourceSegments); + $targetLastSegment = array_pop($targetSegments); + foreach ($sourceSegments as $i => $segment) { + if (isset($targetSegments[$i]) && $segment === $targetSegments[$i]) { + unset($sourceSegments[$i], $targetSegments[$i]); + } else { + break; + } + } + $targetSegments[] = $targetLastSegment; + $relativePath = str_repeat('../', count($sourceSegments)) . implode('/', $targetSegments); + + // A reference to am empty last segment or an empty first sub-segment 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. + if ('' === $relativePath || false !== strpos(explode('/', $relativePath, 2)[0], ':')) { + $relativePath = "./$relativePath"; + } elseif ('/' === $relativePath[0]) { + if ($base->getAuthority() != '' && $base->getPath() === '') { + // In this case an extra slash is added by resolve() automatically. So we must not add one here. + $relativePath = ".$relativePath"; + } else { + $relativePath = "./$relativePath"; + } + } + + return $relativePath; + } + + private function __construct() + { + // cannot be instantiated + } +} diff --git a/vendor/guzzlehttp/psr7/src/Utils.php b/vendor/guzzlehttp/psr7/src/Utils.php new file mode 100644 index 0000000..6b6c8cc --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Utils.php @@ -0,0 +1,428 @@ + $keys + * + * @return array + */ + public static function caselessRemove($keys, array $data) + { + $result = []; + + foreach ($keys as &$key) { + $key = strtolower($key); + } + + foreach ($data as $k => $v) { + if (!in_array(strtolower($k), $keys)) { + $result[$k] = $v; + } + } + + return $result; + } + + /** + * Copy the contents of a stream into another stream until the given number + * of bytes have been read. + * + * @param StreamInterface $source Stream to read from + * @param StreamInterface $dest Stream to write to + * @param int $maxLen Maximum number of bytes to read. Pass -1 + * to read the entire stream. + * + * @throws \RuntimeException on error. + */ + public static function copyToStream(StreamInterface $source, StreamInterface $dest, $maxLen = -1) + { + $bufferSize = 8192; + + if ($maxLen === -1) { + while (!$source->eof()) { + if (!$dest->write($source->read($bufferSize))) { + break; + } + } + } else { + $remaining = $maxLen; + while ($remaining > 0 && !$source->eof()) { + $buf = $source->read(min($bufferSize, $remaining)); + $len = strlen($buf); + if (!$len) { + break; + } + $remaining -= $len; + $dest->write($buf); + } + } + } + + /** + * Copy the contents of a stream into a string until the given number of + * bytes have been read. + * + * @param StreamInterface $stream Stream to read + * @param int $maxLen Maximum number of bytes to read. Pass -1 + * to read the entire stream. + * + * @return string + * + * @throws \RuntimeException on error. + */ + public static function copyToString(StreamInterface $stream, $maxLen = -1) + { + $buffer = ''; + + if ($maxLen === -1) { + while (!$stream->eof()) { + $buf = $stream->read(1048576); + // Using a loose equality here to match on '' and false. + if ($buf == null) { + break; + } + $buffer .= $buf; + } + return $buffer; + } + + $len = 0; + while (!$stream->eof() && $len < $maxLen) { + $buf = $stream->read($maxLen - $len); + // Using a loose equality here to match on '' and false. + if ($buf == null) { + break; + } + $buffer .= $buf; + $len = strlen($buffer); + } + + return $buffer; + } + + /** + * Calculate a hash of a stream. + * + * This method reads the entire stream to calculate a rolling hash, based + * on PHP's `hash_init` functions. + * + * @param StreamInterface $stream Stream to calculate the hash for + * @param string $algo Hash algorithm (e.g. md5, crc32, etc) + * @param bool $rawOutput Whether or not to use raw output + * + * @return string Returns the hash of the stream + * + * @throws \RuntimeException on error. + */ + public static function hash(StreamInterface $stream, $algo, $rawOutput = false) + { + $pos = $stream->tell(); + + if ($pos > 0) { + $stream->rewind(); + } + + $ctx = hash_init($algo); + while (!$stream->eof()) { + hash_update($ctx, $stream->read(1048576)); + } + + $out = hash_final($ctx, (bool) $rawOutput); + $stream->seek($pos); + + return $out; + } + + /** + * Clone and modify a request with the given changes. + * + * This method is useful for reducing the number of clones needed to mutate + * a message. + * + * The changes can be one of: + * - method: (string) Changes the HTTP method. + * - set_headers: (array) Sets the given headers. + * - remove_headers: (array) Remove the given headers. + * - body: (mixed) Sets the given body. + * - uri: (UriInterface) Set the URI. + * - query: (string) Set the query string value of the URI. + * - version: (string) Set the protocol version. + * + * @param RequestInterface $request Request to clone and modify. + * @param array $changes Changes to apply. + * + * @return RequestInterface + */ + public static function modifyRequest(RequestInterface $request, array $changes) + { + if (!$changes) { + return $request; + } + + $headers = $request->getHeaders(); + + if (!isset($changes['uri'])) { + $uri = $request->getUri(); + } else { + // Remove the host header if one is on the URI + if ($host = $changes['uri']->getHost()) { + $changes['set_headers']['Host'] = $host; + + if ($port = $changes['uri']->getPort()) { + $standardPorts = ['http' => 80, 'https' => 443]; + $scheme = $changes['uri']->getScheme(); + if (isset($standardPorts[$scheme]) && $port != $standardPorts[$scheme]) { + $changes['set_headers']['Host'] .= ':' . $port; + } + } + } + $uri = $changes['uri']; + } + + if (!empty($changes['remove_headers'])) { + $headers = self::caselessRemove($changes['remove_headers'], $headers); + } + + if (!empty($changes['set_headers'])) { + $headers = self::caselessRemove(array_keys($changes['set_headers']), $headers); + $headers = $changes['set_headers'] + $headers; + } + + if (isset($changes['query'])) { + $uri = $uri->withQuery($changes['query']); + } + + if ($request instanceof ServerRequestInterface) { + $new = (new ServerRequest( + isset($changes['method']) ? $changes['method'] : $request->getMethod(), + $uri, + $headers, + isset($changes['body']) ? $changes['body'] : $request->getBody(), + isset($changes['version']) + ? $changes['version'] + : $request->getProtocolVersion(), + $request->getServerParams() + )) + ->withParsedBody($request->getParsedBody()) + ->withQueryParams($request->getQueryParams()) + ->withCookieParams($request->getCookieParams()) + ->withUploadedFiles($request->getUploadedFiles()); + + foreach ($request->getAttributes() as $key => $value) { + $new = $new->withAttribute($key, $value); + } + + return $new; + } + + return new Request( + isset($changes['method']) ? $changes['method'] : $request->getMethod(), + $uri, + $headers, + isset($changes['body']) ? $changes['body'] : $request->getBody(), + isset($changes['version']) + ? $changes['version'] + : $request->getProtocolVersion() + ); + } + + /** + * Read a line from the stream up to the maximum allowed buffer length. + * + * @param StreamInterface $stream Stream to read from + * @param int|null $maxLength Maximum buffer length + * + * @return string + */ + public static function readLine(StreamInterface $stream, $maxLength = null) + { + $buffer = ''; + $size = 0; + + while (!$stream->eof()) { + // Using a loose equality here to match on '' and false. + if (null == ($byte = $stream->read(1))) { + return $buffer; + } + $buffer .= $byte; + // Break when a new line is found or the max length - 1 is reached + if ($byte === "\n" || ++$size === $maxLength - 1) { + break; + } + } + + return $buffer; + } + + /** + * Create a new stream based on the input type. + * + * Options is an associative array that can contain the following keys: + * - metadata: Array of custom metadata. + * - size: Size of the stream. + * + * This method accepts the following `$resource` types: + * - `Psr\Http\Message\StreamInterface`: Returns the value as-is. + * - `string`: Creates a stream object that uses the given string as the contents. + * - `resource`: Creates a stream object that wraps the given PHP stream resource. + * - `Iterator`: If the provided value implements `Iterator`, then a read-only + * stream object will be created that wraps the given iterable. Each time the + * stream is read from, data from the iterator will fill a buffer and will be + * continuously called until the buffer is equal to the requested read size. + * Subsequent read calls will first read from the buffer and then call `next` + * on the underlying iterator until it is exhausted. + * - `object` with `__toString()`: If the object has the `__toString()` method, + * the object will be cast to a string and then a stream will be returned that + * uses the string value. + * - `NULL`: When `null` is passed, an empty stream object is returned. + * - `callable` When a callable is passed, a read-only stream object will be + * created that invokes the given callable. The callable is invoked with the + * number of suggested bytes to read. The callable can return any number of + * bytes, but MUST return `false` when there is no more data to return. The + * stream object that wraps the callable will invoke the callable until the + * number of requested bytes are available. Any additional bytes will be + * buffered and used in subsequent reads. + * + * @param resource|string|int|float|bool|StreamInterface|callable|\Iterator|null $resource Entity body data + * @param array $options Additional options + * + * @return StreamInterface + * + * @throws \InvalidArgumentException if the $resource arg is not valid. + */ + public static function streamFor($resource = '', array $options = []) + { + if (is_scalar($resource)) { + $stream = self::tryFopen('php://temp', 'r+'); + if ($resource !== '') { + fwrite($stream, $resource); + fseek($stream, 0); + } + return new Stream($stream, $options); + } + + switch (gettype($resource)) { + case 'resource': + /* + * The 'php://input' is a special stream with quirks and inconsistencies. + * We avoid using that stream by reading it into php://temp + */ + $metaData = \stream_get_meta_data($resource); + if (isset($metaData['uri']) && $metaData['uri'] === 'php://input') { + $stream = self::tryFopen('php://temp', 'w+'); + fwrite($stream, stream_get_contents($resource)); + fseek($stream, 0); + $resource = $stream; + } + return new Stream($resource, $options); + case 'object': + if ($resource instanceof StreamInterface) { + return $resource; + } elseif ($resource instanceof \Iterator) { + return new PumpStream(function () use ($resource) { + if (!$resource->valid()) { + return false; + } + $result = $resource->current(); + $resource->next(); + return $result; + }, $options); + } elseif (method_exists($resource, '__toString')) { + return Utils::streamFor((string) $resource, $options); + } + break; + case 'NULL': + return new Stream(self::tryFopen('php://temp', 'r+'), $options); + } + + if (is_callable($resource)) { + return new PumpStream($resource, $options); + } + + throw new \InvalidArgumentException('Invalid resource type: ' . gettype($resource)); + } + + /** + * Safely opens a PHP stream resource using a filename. + * + * When fopen fails, PHP normally raises a warning. This function adds an + * error handler that checks for errors and throws an exception instead. + * + * @param string $filename File to open + * @param string $mode Mode used to open the file + * + * @return resource + * + * @throws \RuntimeException if the file cannot be opened + */ + public static function tryFopen($filename, $mode) + { + $ex = null; + set_error_handler(function () use ($filename, $mode, &$ex) { + $ex = new \RuntimeException(sprintf( + 'Unable to open "%s" using mode "%s": %s', + $filename, + $mode, + func_get_args()[1] + )); + + return true; + }); + + try { + $handle = fopen($filename, $mode); + } catch (\Throwable $e) { + $ex = new \RuntimeException(sprintf( + 'Unable to open "%s" using mode "%s": %s', + $filename, + $mode, + $e->getMessage() + ), 0, $e); + } + + restore_error_handler(); + + if ($ex) { + /** @var $ex \RuntimeException */ + throw $ex; + } + + return $handle; + } + + /** + * Returns a UriInterface for the given value. + * + * This function accepts a string or UriInterface and returns a + * UriInterface for the given value. If the value is already a + * UriInterface, it is returned as-is. + * + * @param string|UriInterface $uri + * + * @return UriInterface + * + * @throws \InvalidArgumentException + */ + public static function uriFor($uri) + { + if ($uri instanceof UriInterface) { + return $uri; + } + + if (is_string($uri)) { + return new Uri($uri); + } + + throw new \InvalidArgumentException('URI must be a string or UriInterface'); + } +} diff --git a/vendor/guzzlehttp/psr7/src/functions.php b/vendor/guzzlehttp/psr7/src/functions.php new file mode 100644 index 0000000..b0901fa --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/functions.php @@ -0,0 +1,422 @@ + '1', 'foo[b]' => '2'])`. + * + * @param string $str Query string to parse + * @param int|bool $urlEncoding How the query string is encoded + * + * @return array + * + * @deprecated parse_query will be removed in guzzlehttp/psr7:2.0. Use Query::parse instead. + */ +function parse_query($str, $urlEncoding = true) +{ + return Query::parse($str, $urlEncoding); +} + +/** + * Build a query string from an array of key value pairs. + * + * This function can use the return value of `parse_query()` to build a query + * string. This function does not modify the provided keys when an array is + * encountered (like `http_build_query()` would). + * + * @param array $params Query string parameters. + * @param int|false $encoding Set to false to not encode, PHP_QUERY_RFC3986 + * to encode using RFC3986, or PHP_QUERY_RFC1738 + * to encode using RFC1738. + * + * @return string + * + * @deprecated build_query will be removed in guzzlehttp/psr7:2.0. Use Query::build instead. + */ +function build_query(array $params, $encoding = PHP_QUERY_RFC3986) +{ + return Query::build($params, $encoding); +} + +/** + * Determines the mimetype of a file by looking at its extension. + * + * @param string $filename + * + * @return string|null + * + * @deprecated mimetype_from_filename will be removed in guzzlehttp/psr7:2.0. Use MimeType::fromFilename instead. + */ +function mimetype_from_filename($filename) +{ + return MimeType::fromFilename($filename); +} + +/** + * Maps a file extensions to a mimetype. + * + * @param $extension string The file extension. + * + * @return string|null + * + * @link http://svn.apache.org/repos/asf/httpd/httpd/branches/1.3.x/conf/mime.types + * @deprecated mimetype_from_extension will be removed in guzzlehttp/psr7:2.0. Use MimeType::fromExtension instead. + */ +function mimetype_from_extension($extension) +{ + return MimeType::fromExtension($extension); +} + +/** + * Parses an HTTP message into an associative array. + * + * The array contains the "start-line" key containing the start line of + * the message, "headers" key containing an associative array of header + * array values, and a "body" key containing the body of the message. + * + * @param string $message HTTP request or response to parse. + * + * @return array + * + * @internal + * + * @deprecated _parse_message will be removed in guzzlehttp/psr7:2.0. Use Message::parseMessage instead. + */ +function _parse_message($message) +{ + return Message::parseMessage($message); +} + +/** + * Constructs a URI for an HTTP request message. + * + * @param string $path Path from the start-line + * @param array $headers Array of headers (each value an array). + * + * @return string + * + * @internal + * + * @deprecated _parse_request_uri will be removed in guzzlehttp/psr7:2.0. Use Message::parseRequestUri instead. + */ +function _parse_request_uri($path, array $headers) +{ + return Message::parseRequestUri($path, $headers); +} + +/** + * Get a short summary of the message body. + * + * Will return `null` if the response is not printable. + * + * @param MessageInterface $message The message to get the body summary + * @param int $truncateAt The maximum allowed size of the summary + * + * @return string|null + * + * @deprecated get_message_body_summary will be removed in guzzlehttp/psr7:2.0. Use Message::bodySummary instead. + */ +function get_message_body_summary(MessageInterface $message, $truncateAt = 120) +{ + return Message::bodySummary($message, $truncateAt); +} + +/** + * Remove the items given by the keys, case insensitively from the data. + * + * @param iterable $keys + * + * @return array + * + * @internal + * + * @deprecated _caseless_remove will be removed in guzzlehttp/psr7:2.0. Use Utils::caselessRemove instead. + */ +function _caseless_remove($keys, array $data) +{ + return Utils::caselessRemove($keys, $data); +} diff --git a/vendor/guzzlehttp/psr7/src/functions_include.php b/vendor/guzzlehttp/psr7/src/functions_include.php new file mode 100644 index 0000000..96a4a83 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/functions_include.php @@ -0,0 +1,6 @@ +=7.0.0", + "nelexa/zip": "^3.3", + "symfony/var-exporter": "^4.4.13" + }, + "autoload": { + "psr-4": { + "think\\": "src/" + }, + "files": [ + "src/common.php" + ] + }, + "extra": { + "think-config": { + "addons": "src/config.php" + } + } +} diff --git a/vendor/karsonzhang/fastadmin-addons/src/Addons.php b/vendor/karsonzhang/fastadmin-addons/src/Addons.php new file mode 100644 index 0000000..720036f --- /dev/null +++ b/vendor/karsonzhang/fastadmin-addons/src/Addons.php @@ -0,0 +1,286 @@ + + * @package think\addons + */ +abstract class Addons +{ + + // 视图实例对象 + protected $view = null; + // 当前错误信息 + protected $error; + // 插件目录 + public $addons_path = ''; + public $addonPath = ''; + + // 插件标识 + protected $addonName = ''; + // 插件配置作用域 + protected $configRange = 'addonconfig'; + // 插件信息作用域 + protected $infoRange = 'addoninfo'; + + /** + * 架构函数 + * @access public + */ + public function __construct($name = null) + { + $name = is_null($name) ? $this->getName() : $name; + + //设置插件标识 + $this->addonName = $name; + + // 获取当前插件目录 + $this->addonPath = ADDON_PATH . $name . DS; + $this->addons_path = $this->addonPath; + + // 初始化视图模型 + $config = ['view_path' => $this->addonPath]; + $config = array_merge(Config::get('template'), $config); + $this->view = new View($config, Config::get('view_replace_str')); + + // 控制器初始化 + if (method_exists($this, '_initialize')) { + $this->_initialize(); + } + } + + /** + * 读取基础配置信息 + * @param string $name + * @return array + */ + final public function getInfo($name = '', $force = false) + { + if (empty($name)) { + $name = $this->getName(); + } + if (!$force) { + $info = Config::get($name, $this->infoRange); + if ($info) { + return $info; + } + } + $info = []; + $infoFile = $this->addonPath . 'info.ini'; + if (is_file($infoFile)) { + $info = Config::parse($infoFile, '', $name, $this->infoRange); + $info['url'] = addon_url($name); + } + Config::set($name, $info, $this->infoRange); + + return $info ? $info : []; + } + + /** + * 获取插件的配置数组 + * @param string $name 可选模块名 + * @return array + */ + final public function getConfig($name = '', $force = false) + { + if (empty($name)) { + $name = $this->getName(); + } + if (!$force) { + $config = Config::get($name, $this->configRange); + if ($config) { + return $config; + } + } + $config = []; + $configFile = $this->addonPath . 'config.php'; + if (is_file($configFile)) { + $configArr = include $configFile; + if (is_array($configArr)) { + foreach ($configArr as $key => $value) { + $config[$value['name']] = $value['value']; + } + unset($configArr); + } + } + Config::set($name, $config, $this->configRange); + + return $config; + } + + /** + * 设置配置数据 + * @param $name + * @param array $value + * @return array + */ + final public function setConfig($name = '', $value = []) + { + if (empty($name)) { + $name = $this->getName(); + } + $config = $this->getConfig($name); + $config = array_merge($config, $value); + Config::set($name, $config, $this->configRange); + return $config; + } + + /** + * 设置插件信息数据 + * @param $name + * @param array $value + * @return array + */ + final public function setInfo($name = '', $value = []) + { + if (empty($name)) { + $name = $this->getName(); + } + $info = $this->getInfo($name); + $info = array_merge($info, $value); + Config::set($name, $info, $this->infoRange); + return $info; + } + + /** + * 获取完整配置列表 + * @param string $name + * @return array + */ + final public function getFullConfig($name = '') + { + $fullConfigArr = []; + if (empty($name)) { + $name = $this->getName(); + } + $configFile = $this->addonPath . 'config.php'; + if (is_file($configFile)) { + $fullConfigArr = include $configFile; + } + return $fullConfigArr; + } + + /** + * 获取当前模块名 + * @return string + */ + final public function getName() + { + if ($this->addonName) { + return $this->addonName; + } + $data = explode('\\', get_class($this)); + return strtolower(array_pop($data)); + } + + /** + * 设置插件标识 + * @param $name + */ + final public function setName($name) + { + $this->addonName = $name; + } + + /** + * 检查基础配置信息是否完整 + * @return bool + */ + final public function checkInfo() + { + $info = $this->getInfo(); + $info_check_keys = ['name', 'title', 'intro', 'author', 'version', 'state']; + foreach ($info_check_keys as $value) { + if (!array_key_exists($value, $info)) { + return false; + } + } + return true; + } + + /** + * 加载模板和页面输出 可以返回输出内容 + * @access public + * @param string $template 模板文件名或者内容 + * @param array $vars 模板输出变量 + * @param array $replace 替换内容 + * @param array $config 模板参数 + * @return mixed + * @throws \Exception + */ + public function fetch($template = '', $vars = [], $replace = [], $config = []) + { + if (!is_file($template)) { + $template = '/' . $template; + } + // 关闭模板布局 + $this->view->engine->layout(false); + + echo $this->view->fetch($template, $vars, $replace, $config); + } + + /** + * 渲染内容输出 + * @access public + * @param string $content 内容 + * @param array $vars 模板输出变量 + * @param array $replace 替换内容 + * @param array $config 模板参数 + * @return mixed + */ + public function display($content, $vars = [], $replace = [], $config = []) + { + // 关闭模板布局 + $this->view->engine->layout(false); + + echo $this->view->display($content, $vars, $replace, $config); + } + + /** + * 渲染内容输出 + * @access public + * @param string $content 内容 + * @param array $vars 模板输出变量 + * @return mixed + */ + public function show($content, $vars = []) + { + // 关闭模板布局 + $this->view->engine->layout(false); + + echo $this->view->fetch($content, $vars, [], [], true); + } + + /** + * 模板变量赋值 + * @access protected + * @param mixed $name 要显示的模板变量 + * @param mixed $value 变量的值 + * @return void + */ + public function assign($name, $value = '') + { + $this->view->assign($name, $value); + } + + /** + * 获取当前错误信息 + * @return mixed + */ + public function getError() + { + return $this->error; + } + + //必须实现安装 + abstract public function install(); + + //必须卸载插件方法 + abstract public function uninstall(); +} diff --git a/vendor/karsonzhang/fastadmin-addons/src/addons/AddonException.php b/vendor/karsonzhang/fastadmin-addons/src/addons/AddonException.php new file mode 100644 index 0000000..bb9c3ba --- /dev/null +++ b/vendor/karsonzhang/fastadmin-addons/src/addons/AddonException.php @@ -0,0 +1,21 @@ +message = $message; + $this->code = $code; + $this->data = $data; + } + +} diff --git a/vendor/karsonzhang/fastadmin-addons/src/addons/Controller.php b/vendor/karsonzhang/fastadmin-addons/src/addons/Controller.php new file mode 100644 index 0000000..5fb981a --- /dev/null +++ b/vendor/karsonzhang/fastadmin-addons/src/addons/Controller.php @@ -0,0 +1,210 @@ +request = $request; + + //移除HTML标签 + $this->request->filter('trim,strip_tags,htmlspecialchars'); + + // 是否自动转换控制器和操作名 + $convert = Config::get('url_convert'); + + $filter = $convert ? 'strtolower' : 'trim'; + // 处理路由参数 + $param = $this->request->param(); + $dispatch = $this->request->dispatch(); + $var = isset($dispatch['var']) ? $dispatch['var'] : []; + $var = array_merge($param, $var); + if (isset($dispatch['method']) && substr($dispatch['method'][0], 0, 7) == "\\addons") { + $arr = explode("\\", $dispatch['method'][0]); + $addon = strtolower($arr[2]); + $controller = strtolower(end($arr)); + $action = $dispatch['method'][1]; + } else { + $addon = isset($var['addon']) ? $var['addon'] : ''; + $controller = isset($var['controller']) ? $var['controller'] : ''; + $action = isset($var['action']) ? $var['action'] : ''; + } + + $this->addon = $addon ? call_user_func($filter, $addon) : ''; + $this->controller = $controller ? call_user_func($filter, $controller) : 'index'; + $this->action = $action ? call_user_func($filter, $action) : 'index'; + + // 重置配置 + Config::set('template.view_path', ADDON_PATH . $this->addon . DS . 'view' . DS); + + // 父类的调用必须放在设置模板路径之后 + parent::__construct($this->request); + } + + protected function _initialize() + { + // 检测IP是否允许 + if (function_exists("check_ip_allowed")) { + check_ip_allowed(); + } + + // 渲染配置到视图中 + $config = get_addon_config($this->addon); + $this->view->assign("config", $config); + + $lang = $this->request->langset(); + $lang = preg_match("/^([a-zA-Z\-_]{2,10})\$/i", $lang) ? $lang : 'zh-cn'; + + // 加载系统语言包 + Lang::load([ + ADDON_PATH . $this->addon . DS . 'lang' . DS . $lang . EXT, + ]); + + // 设置替换字符串 + $cdnurl = Config::get('site.cdnurl'); + $this->view->replace('__ADDON__', $cdnurl . "/assets/addons/" . $this->addon); + + $this->auth = Auth::instance(); + // token + $token = $this->request->server('HTTP_TOKEN', $this->request->request('token', \think\Cookie::get('token'))); + + $path = 'addons/' . $this->addon . '/' . str_replace('.', '/', $this->controller) . '/' . $this->action; + // 设置当前请求的URI + $this->auth->setRequestUri($path); + // 检测是否需要验证登录 + if (!$this->auth->match($this->noNeedLogin)) { + //初始化 + $this->auth->init($token); + //检测是否登录 + if (!$this->auth->isLogin()) { + $this->error(__('Please login first'), 'index/user/login'); + } + // 判断是否需要验证权限 + if (!$this->auth->match($this->noNeedRight)) { + // 判断控制器和方法判断是否有对应权限 + if (!$this->auth->check($path)) { + $this->error(__('You have no permission')); + } + } + } else { + // 如果有传递token才验证是否登录状态 + if ($token) { + $this->auth->init($token); + } + } + + // 如果有使用模板布局 + if ($this->layout) { + $this->view->engine->layout('layout/' . $this->layout); + } + + $this->view->assign('user', $this->auth->getUser()); + + $site = Config::get("site"); + + $upload = \app\common\model\Config::upload(); + + // 上传信息配置后 + Hook::listen("upload_config_init", $upload); + Config::set('upload', array_merge(Config::get('upload'), $upload)); + + // 加载当前控制器语言包 + $this->assign('site', $site); + } + + /** + * 加载模板输出 + * @access protected + * @param string $template 模板文件名 + * @param array $vars 模板输出变量 + * @param array $replace 模板替换 + * @param array $config 模板参数 + * @return mixed + */ + protected function fetch($template = '', $vars = [], $replace = [], $config = []) + { + $controller = Loader::parseName($this->controller); + if ('think' == strtolower(Config::get('template.type')) && $controller && 0 !== strpos($template, '/')) { + $depr = Config::get('template.view_depr'); + $template = str_replace(['/', ':'], $depr, $template); + if ('' == $template) { + // 如果模板文件名为空 按照默认规则定位 + $template = str_replace('.', DS, $controller) . $depr . $this->action; + } elseif (false === strpos($template, $depr)) { + $template = str_replace('.', DS, $controller) . $depr . $template; + } + } + return parent::fetch($template, $vars, $replace, $config); + } + + /** + * 刷新Token + */ + protected function token() + { + $token = $this->request->param('__token__'); + + //验证Token + if (!Validate::make()->check(['__token__' => $token], ['__token__' => 'require|token'])) { + $this->error(__('Token verification error'), '', ['__token__' => $this->request->token()]); + } + + //刷新Token + $this->request->token(); + } + +} diff --git a/vendor/karsonzhang/fastadmin-addons/src/addons/Route.php b/vendor/karsonzhang/fastadmin-addons/src/addons/Route.php new file mode 100644 index 0000000..f53f107 --- /dev/null +++ b/vendor/karsonzhang/fastadmin-addons/src/addons/Route.php @@ -0,0 +1,84 @@ +dispatch(); + if (isset($dispatch['var']) && $dispatch['var']) { + $request->route(array_diff_key($dispatch['var'], array_flip(['addon', 'controller', 'action']))); + } + + // 设置当前请求的控制器、操作 + $request->controller($controller)->action($action); + + // 监听addon_module_init + Hook::listen('addon_module_init', $request); + // 兼容旧版本行为,即将移除,不建议使用 + Hook::listen('addons_init', $request); + + $class = get_addon_class($addon, 'controller', $controller); + if (!$class) { + throw new HttpException(404, __('addon controller %s not found', Loader::parseName($controller, 1))); + } + + $instance = new $class($request); + + $vars = []; + if (is_callable([$instance, $action])) { + // 执行操作方法 + $call = [$instance, $action]; + } elseif (is_callable([$instance, '_empty'])) { + // 空操作 + $call = [$instance, '_empty']; + $vars = [$action]; + } else { + // 操作不存在 + throw new HttpException(404, __('addon action %s not found', get_class($instance) . '->' . $action . '()')); + } + + Hook::listen('addon_action_begin', $call); + + return call_user_func_array($call, $vars); + } else { + abort(500, lang('addon can not be empty')); + } + } + +} diff --git a/vendor/karsonzhang/fastadmin-addons/src/addons/Service.php b/vendor/karsonzhang/fastadmin-addons/src/addons/Service.php new file mode 100644 index 0000000..a2e78f3 --- /dev/null +++ b/vendor/karsonzhang/fastadmin-addons/src/addons/Service.php @@ -0,0 +1,1150 @@ +host(true); + return self::sendRequest('/addon/index', $params, 'GET'); + } + + /** + * 检测插件是否购买授权 + */ + public static function isBuy($name, $extend = []) + { + $params = array_merge(['name' => $name, 'domain' => request()->host(true)], $extend); + return self::sendRequest('/addon/isbuy', $params, 'POST'); + } + + /** + * 检测插件是否授权 + * + * @param string $name 插件名称 + * @param string $domain 验证域名 + */ + public static function isAuthorization($name, $domain = '') + { + $config = self::config($name); + $request = request(); + $domain = self::getRootDomain($domain ? $domain : $request->host(true)); + if (isset($config['domains']) && isset($config['domains']) && isset($config['validations']) && isset($config['licensecodes'])) { + $index = array_search($domain, $config['domains']); + if ((in_array($domain, $config['domains']) && in_array(md5(md5($domain) . ($config['licensecodes'][$index] ?? '')), $config['validations'])) || $request->isCli()) { + return true; + } + } + return false; + } + + /** + * 远程下载插件 + * + * @param string $name 插件名称 + * @param array $extend 扩展参数 + * @return string + */ + public static function download($name, $extend = []) + { + $addonsTempDir = self::getAddonsBackupDir(); + $tmpFile = $addonsTempDir . $name . ".zip"; + try { + $client = self::getClient(); + $response = $client->get('/addon/download', ['query' => array_merge(['name' => $name], $extend)]); + $body = $response->getBody(); + $content = $body->getContents(); + if (substr($content, 0, 1) === '{') { + $json = (array)json_decode($content, true); + //如果传回的是一个下载链接,则再次下载 + if ($json['data'] && isset($json['data']['url'])) { + $response = $client->get($json['data']['url']); + $body = $response->getBody(); + $content = $body->getContents(); + } else { + //下载返回错误,抛出异常 + throw new AddonException($json['msg'], $json['code'], $json['data']); + } + } + } catch (TransferException $e) { + throw new Exception("Addon package download failed"); + } + + if ($write = fopen($tmpFile, 'w')) { + fwrite($write, $content); + fclose($write); + return $tmpFile; + } + throw new Exception("No permission to write temporary files"); + } + + /** + * 解压插件 + * + * @param string $name 插件名称 + * @return string + * @throws Exception + */ + public static function unzip($name) + { + if (!$name) { + throw new Exception('Invalid parameters'); + } + $addonsBackupDir = self::getAddonsBackupDir(); + $file = $addonsBackupDir . $name . '.zip'; + + // 打开插件压缩包 + $zip = new ZipFile(); + try { + $zip->openFile($file); + } catch (ZipException $e) { + $zip->close(); + throw new Exception('Unable to open the zip file'); + } + + $dir = self::getAddonDir($name); + if (!is_dir($dir)) { + @mkdir($dir, 0755); + } + + // 解压插件压缩包 + try { + $zip->extractTo($dir); + } catch (ZipException $e) { + throw new Exception('Unable to extract the file'); + } finally { + $zip->close(); + } + return $dir; + } + + /** + * 离线安装 + * @param string $file 插件压缩包 + * @param array $extend + */ + public static function local($file, $extend = []) + { + $addonsTempDir = self::getAddonsBackupDir(); + if (!$file || !$file instanceof \think\File) { + throw new Exception('No file upload or server upload limit exceeded'); + } + $uploadFile = $file->rule('uniqid')->validate(['size' => 102400000, 'ext' => 'zip,fastaddon'])->move($addonsTempDir); + if (!$uploadFile) { + // 上传失败获取错误信息 + throw new Exception(__($file->getError())); + } + $tmpFile = $addonsTempDir . $uploadFile->getSaveName(); + + $info = []; + $zip = new ZipFile(); + try { + + // 打开插件压缩包 + try { + $zip->openFile($tmpFile); + } catch (ZipException $e) { + @unlink($tmpFile); + throw new Exception('Unable to open the zip file'); + } + + $config = self::getInfoIni($zip); + + // 判断插件标识 + $name = isset($config['name']) ? $config['name'] : ''; + if (!$name) { + throw new Exception('Addon info file data incorrect'); + } + + // 判断插件是否存在 + if (!preg_match("/^[a-zA-Z0-9]+$/", $name)) { + throw new Exception('Addon name incorrect'); + } + + // 判断新插件是否存在 + $newAddonDir = self::getAddonDir($name); + if (is_dir($newAddonDir)) { + throw new Exception('Addon already exists'); + } + + // 追加MD5和Data数据 + $extend['md5'] = md5_file($tmpFile); + $extend['data'] = $zip->getArchiveComment(); + $extend['unknownsources'] = config('app_debug') && config('fastadmin.unknownsources'); + $extend['faversion'] = config('fastadmin.version'); + + $params = array_merge($config, $extend); + + // 压缩包验证、版本依赖判断 + Service::valid($params); + + //创建插件目录 + @mkdir($newAddonDir, 0755, true); + + // 解压到插件目录 + try { + $zip->extractTo($newAddonDir); + } catch (ZipException $e) { + @unlink($newAddonDir); + throw new Exception('Unable to extract the file'); + } + + Db::startTrans(); + try { + //默认禁用该插件 + $info = get_addon_info($name); + if ($info['state']) { + $info['state'] = 0; + set_addon_info($name, $info); + } + + //执行插件的安装方法 + $class = get_addon_class($name); + if (class_exists($class)) { + $addon = new $class(); + $addon->install(); + } + Db::commit(); + } catch (\Exception $e) { + Db::rollback(); + @rmdirs($newAddonDir); + throw new Exception(__($e->getMessage())); + } + + //导入SQL + Service::importsql($name); + } catch (AddonException $e) { + throw new AddonException($e->getMessage(), $e->getCode(), $e->getData()); + } catch (Exception $e) { + throw new Exception(__($e->getMessage())); + } finally { + $zip->close(); + unset($uploadFile); + @unlink($tmpFile); + } + + $info['config'] = get_addon_config($name) ? 1 : 0; + $info['bootstrap'] = is_file(Service::getBootstrapFile($name)); + $info['testdata'] = is_file(Service::getTestdataFile($name)); + return $info; + } + + /** + * 验证压缩包、依赖验证 + * @param array $params + * @return bool + * @throws Exception + */ + public static function valid($params = []) + { + $json = self::sendRequest('/addon/valid', $params, 'POST'); + if ($json && isset($json['code'])) { + if ($json['code']) { + return true; + } else { + throw new Exception($json['msg'] ?? "Invalid addon package"); + } + } else { + throw new Exception("Unknown data format"); + } + } + + /** + * 备份插件 + * @param string $name 插件名称 + * @return bool + * @throws Exception + */ + public static function backup($name) + { + $addonsBackupDir = self::getAddonsBackupDir(); + $file = $addonsBackupDir . $name . '-backup-' . date("YmdHis") . '.zip'; + $zipFile = new ZipFile(); + try { + $zipFile + ->addDirRecursive(self::getAddonDir($name)) + ->saveAsFile($file) + ->close(); + } catch (ZipException $e) { + + } finally { + $zipFile->close(); + } + + return true; + } + + /** + * 检测插件是否完整 + * + * @param string $name 插件名称 + * @return boolean + * @throws Exception + */ + public static function check($name) + { + if (!$name || !is_dir(ADDON_PATH . $name)) { + throw new Exception('Addon not exists'); + } + $addonClass = get_addon_class($name); + if (!$addonClass) { + throw new Exception("The addon file does not exist"); + } + $addon = new $addonClass(); + if (!$addon->checkInfo()) { + throw new Exception("The configuration file content is incorrect"); + } + return true; + } + + /** + * 是否有冲突 + * + * @param string $name 插件名称 + * @return boolean + * @throws AddonException + */ + public static function noconflict($name) + { + // 检测冲突文件 + $list = self::getGlobalFiles($name, true); + if ($list) { + //发现冲突文件,抛出异常 + throw new AddonException(__("Conflicting file found"), -3, ['conflictlist' => $list]); + } + return true; + } + + /** + * 导入SQL + * + * @param string $name 插件名称 + * @param string $fileName SQL文件名称 + * @return boolean + */ + public static function importsql($name, $fileName = null) + { + $fileName = is_null($fileName) ? 'install.sql' : $fileName; + $sqlFile = self::getAddonDir($name) . $fileName; + if (is_file($sqlFile)) { + $lines = file($sqlFile); + $templine = ''; + foreach ($lines as $line) { + if (substr($line, 0, 2) == '--' || $line == '' || substr($line, 0, 2) == '/*') { + continue; + } + + $templine .= $line; + if (substr(trim($line), -1, 1) == ';') { + $templine = str_ireplace('__PREFIX__', config('database.prefix'), $templine); + $templine = str_ireplace('INSERT INTO ', 'INSERT IGNORE INTO ', $templine); + try { + Db::getPdo()->exec($templine); + } catch (\PDOException $e) { + //$e->getMessage(); + } + $templine = ''; + } + } + } + return true; + } + + /** + * 刷新插件缓存文件 + * + * @return boolean + * @throws Exception + */ + public static function refresh() + { + //刷新addons.js + $addons = get_addon_list(); + $bootstrapArr = []; + foreach ($addons as $name => $addon) { + $bootstrapFile = self::getBootstrapFile($name); + if ($addon['state'] && is_file($bootstrapFile)) { + $bootstrapArr[] = file_get_contents($bootstrapFile); + } + } + $addonsFile = ROOT_PATH . str_replace("/", DS, "public/assets/js/addons.js"); + if ($handle = fopen($addonsFile, 'w')) { + $tpl = <<host(true); + + // 远程下载插件 + $tmpFile = Service::download($name, $extend); + + $addonDir = self::getAddonDir($name); + + try { + // 解压插件压缩包到插件目录 + Service::unzip($name); + + // 检查插件是否完整 + Service::check($name); + + if (!$force) { + Service::noconflict($name); + } + } catch (AddonException $e) { + @rmdirs($addonDir); + throw new AddonException($e->getMessage(), $e->getCode(), $e->getData()); + } catch (Exception $e) { + @rmdirs($addonDir); + throw new Exception($e->getMessage()); + } finally { + // 移除临时文件 + @unlink($tmpFile); + } + + // 默认启用该插件 + $info = get_addon_info($name); + + Db::startTrans(); + try { + if (!$info['state']) { + $info['state'] = 1; + set_addon_info($name, $info); + } + + // 执行安装脚本 + $class = get_addon_class($name); + if (class_exists($class)) { + $addon = new $class(); + $addon->install(); + } + Db::commit(); + } catch (Exception $e) { + @rmdirs($addonDir); + Db::rollback(); + throw new Exception($e->getMessage()); + } + + // 导入 + Service::importsql($name); + + // 启用插件 + Service::enable($name, true); + + $info['config'] = get_addon_config($name) ? 1 : 0; + $info['bootstrap'] = is_file(Service::getBootstrapFile($name)); + $info['testdata'] = is_file(Service::getTestdataFile($name)); + return $info; + } + + /** + * 卸载插件 + * + * @param string $name + * @param boolean $force 是否强制卸载 + * @return boolean + * @throws Exception + */ + public static function uninstall($name, $force = false) + { + if (!$name || !is_dir(ADDON_PATH . $name)) { + throw new Exception('Addon not exists'); + } + + if (!$force) { + Service::noconflict($name); + } + + // 移除插件全局资源文件 + if ($force) { + $list = Service::getGlobalFiles($name); + foreach ($list as $k => $v) { + @unlink(ROOT_PATH . $v); + } + } + + // 执行卸载脚本 + try { + $class = get_addon_class($name); + if (class_exists($class)) { + $addon = new $class(); + $addon->uninstall(); + } + } catch (Exception $e) { + throw new Exception($e->getMessage()); + } + + // 移除插件目录 + rmdirs(ADDON_PATH . $name); + + // 刷新 + Service::refresh(); + return true; + } + + /** + * 启用 + * @param string $name 插件名称 + * @param boolean $force 是否强制覆盖 + * @return boolean + */ + public static function enable($name, $force = false) + { + if (!$name || !is_dir(ADDON_PATH . $name)) { + throw new Exception('Addon not exists'); + } + + if (!$force) { + Service::noconflict($name); + } + + //备份冲突文件 + if (config('fastadmin.backup_global_files')) { + $conflictFiles = self::getGlobalFiles($name, true); + if ($conflictFiles) { + $zip = new ZipFile(); + try { + foreach ($conflictFiles as $k => $v) { + $zip->addFile(ROOT_PATH . $v, $v); + } + $addonsBackupDir = self::getAddonsBackupDir(); + $zip->saveAsFile($addonsBackupDir . $name . "-conflict-enable-" . date("YmdHis") . ".zip"); + } catch (Exception $e) { + + } finally { + $zip->close(); + } + } + } + + $addonDir = self::getAddonDir($name); + $sourceAssetsDir = self::getSourceAssetsDir($name); + $destAssetsDir = self::getDestAssetsDir($name); + + $files = self::getGlobalFiles($name); + if ($files) { + //刷新插件配置缓存 + Service::config($name, ['files' => $files]); + } + + // 复制文件 + if (is_dir($sourceAssetsDir)) { + copydirs($sourceAssetsDir, $destAssetsDir); + } + + // 复制application和public到全局 + foreach (self::getCheckDirs() as $k => $dir) { + if (is_dir($addonDir . $dir)) { + copydirs($addonDir . $dir, ROOT_PATH . $dir); + } + } + + //插件纯净模式时将插件目录下的application、public和assets删除 + if (config('fastadmin.addon_pure_mode')) { + // 删除插件目录已复制到全局的文件 + @rmdirs($sourceAssetsDir); + foreach (self::getCheckDirs() as $k => $dir) { + @rmdirs($addonDir . $dir); + } + } + + //执行启用脚本 + try { + $class = get_addon_class($name); + if (class_exists($class)) { + $addon = new $class(); + if (method_exists($class, "enable")) { + $addon->enable(); + } + } + } catch (Exception $e) { + throw new Exception($e->getMessage()); + } + + $info = get_addon_info($name); + $info['state'] = 1; + unset($info['url']); + + set_addon_info($name, $info); + + // 刷新 + Service::refresh(); + return true; + } + + /** + * 禁用 + * + * @param string $name 插件名称 + * @param boolean $force 是否强制禁用 + * @return boolean + * @throws Exception + */ + public static function disable($name, $force = false) + { + if (!$name || !is_dir(ADDON_PATH . $name)) { + throw new Exception('Addon not exists'); + } + + $file = self::getExtraAddonsFile(); + if (!is_really_writable($file)) { + throw new Exception(__("Unable to open file '%s' for writing", "addons.php")); + } + + if (!$force) { + Service::noconflict($name); + } + + if (config('fastadmin.backup_global_files')) { + //仅备份修改过的文件 + $conflictFiles = Service::getGlobalFiles($name, true); + if ($conflictFiles) { + $zip = new ZipFile(); + try { + foreach ($conflictFiles as $k => $v) { + $zip->addFile(ROOT_PATH . $v, $v); + } + $addonsBackupDir = self::getAddonsBackupDir(); + $zip->saveAsFile($addonsBackupDir . $name . "-conflict-disable-" . date("YmdHis") . ".zip"); + } catch (Exception $e) { + + } finally { + $zip->close(); + } + } + } + + $config = Service::config($name); + + $addonDir = self::getAddonDir($name); + //插件资源目录 + $destAssetsDir = self::getDestAssetsDir($name); + + // 移除插件全局文件 + $list = Service::getGlobalFiles($name); + + //插件纯净模式时将原有的文件复制回插件目录 + //当无法获取全局文件列表时也将列表复制回插件目录 + if (config('fastadmin.addon_pure_mode') || !$list) { + if ($config && isset($config['files']) && is_array($config['files'])) { + foreach ($config['files'] as $index => $item) { + //避免切换不同服务器后导致路径不一致 + $item = str_replace(['/', '\\'], DS, $item); + //插件资源目录,无需重复复制 + if (stripos($item, str_replace(ROOT_PATH, '', $destAssetsDir)) === 0) { + continue; + } + //检查目录是否存在,不存在则创建 + $itemBaseDir = dirname($addonDir . $item); + if (!is_dir($itemBaseDir)) { + @mkdir($itemBaseDir, 0755, true); + } + if (is_file(ROOT_PATH . $item)) { + @copy(ROOT_PATH . $item, $addonDir . $item); + } + } + $list = $config['files']; + } + //复制插件目录资源 + if (is_dir($destAssetsDir)) { + @copydirs($destAssetsDir, $addonDir . 'assets' . DS); + } + } + + $dirs = []; + foreach ($list as $k => $v) { + $file = ROOT_PATH . $v; + $dirs[] = dirname($file); + @unlink($file); + } + + // 移除插件空目录 + $dirs = array_filter(array_unique($dirs)); + foreach ($dirs as $k => $v) { + remove_empty_folder($v); + } + + $info = get_addon_info($name); + $info['state'] = 0; + unset($info['url']); + + set_addon_info($name, $info); + + // 执行禁用脚本 + try { + $class = get_addon_class($name); + if (class_exists($class)) { + $addon = new $class(); + + if (method_exists($class, "disable")) { + $addon->disable(); + } + } + } catch (Exception $e) { + throw new Exception($e->getMessage()); + } + + // 刷新 + Service::refresh(); + return true; + } + + /** + * 升级插件 + * + * @param string $name 插件名称 + * @param array $extend 扩展参数 + */ + public static function upgrade($name, $extend = []) + { + $info = get_addon_info($name); + if ($info['state']) { + throw new Exception(__('Please disable addon first')); + } + $config = get_addon_config($name); + if ($config) { + //备份配置 + } + + // 远程下载插件 + $tmpFile = Service::download($name, $extend); + + // 备份插件文件 + Service::backup($name); + + $addonDir = self::getAddonDir($name); + + // 删除插件目录下的application和public + $files = self::getCheckDirs(); + foreach ($files as $index => $file) { + @rmdirs($addonDir . $file); + } + + try { + // 解压插件 + Service::unzip($name); + } catch (Exception $e) { + throw new Exception($e->getMessage()); + } finally { + // 移除临时文件 + @unlink($tmpFile); + } + + if ($config) { + // 还原配置 + set_addon_config($name, $config); + } + + // 导入 + Service::importsql($name); + + // 执行升级脚本 + try { + $addonName = ucfirst($name); + //创建临时类用于调用升级的方法 + $sourceFile = $addonDir . $addonName . ".php"; + $destFile = $addonDir . $addonName . "Upgrade.php"; + + $classContent = str_replace("class {$addonName} extends", "class {$addonName}Upgrade extends", file_get_contents($sourceFile)); + + //创建临时的类文件 + file_put_contents($destFile, $classContent); + + $className = "\\addons\\" . $name . "\\" . $addonName . "Upgrade"; + $addon = new $className($name); + + //调用升级的方法 + if (method_exists($addon, "upgrade")) { + $addon->upgrade(); + } + + //移除临时文件 + @unlink($destFile); + } catch (Exception $e) { + throw new Exception($e->getMessage()); + } + + // 刷新 + Service::refresh(); + + //必须变更版本号 + $info['version'] = isset($extend['version']) ? $extend['version'] : $info['version']; + + $info['config'] = get_addon_config($name) ? 1 : 0; + $info['bootstrap'] = is_file(Service::getBootstrapFile($name)); + return $info; + } + + /** + * 读取或修改插件配置 + * @param string $name + * @param array $changed + * @return array + */ + public static function config($name, $changed = []) + { + $addonDir = self::getAddonDir($name); + $addonConfigFile = $addonDir . '.addonrc'; + $config = []; + if (is_file($addonConfigFile)) { + $config = (array)json_decode(file_get_contents($addonConfigFile), true); + } + $config = array_merge($config, $changed); + if ($changed) { + file_put_contents($addonConfigFile, json_encode($config, JSON_UNESCAPED_UNICODE)); + } + return $config; + } + + /** + * 获取插件在全局的文件 + * + * @param string $name 插件名称 + * @param boolean $onlyconflict 是否只返回冲突文件 + * @return array + */ + public static function getGlobalFiles($name, $onlyconflict = false) + { + $list = []; + $addonDir = self::getAddonDir($name); + $checkDirList = self::getCheckDirs(); + $checkDirList = array_merge($checkDirList, ['assets']); + + $assetDir = self::getDestAssetsDir($name); + + // 扫描插件目录是否有覆盖的文件 + foreach ($checkDirList as $k => $dirName) { + //检测目录是否存在 + if (!is_dir($addonDir . $dirName)) { + continue; + } + //匹配出所有的文件 + $files = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($addonDir . $dirName, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST + ); + + foreach ($files as $fileinfo) { + if ($fileinfo->isFile()) { + $filePath = $fileinfo->getPathName(); + //如果名称为assets需要做特殊处理 + if ($dirName === 'assets') { + $path = str_replace(ROOT_PATH, '', $assetDir) . str_replace($addonDir . $dirName . DS, '', $filePath); + } else { + $path = str_replace($addonDir, '', $filePath); + } + if ($onlyconflict) { + $destPath = ROOT_PATH . $path; + if (is_file($destPath)) { + if (filesize($filePath) != filesize($destPath) || md5_file($filePath) != md5_file($destPath)) { + $list[] = $path; + } + } + } else { + $list[] = $path; + } + } + } + } + $list = array_filter(array_unique($list)); + return $list; + } + + /** + * 更新本地应用插件授权 + */ + public static function authorization($params = []) + { + $addonList = get_addon_list(); + $result = []; + $domain = request()->host(true); + $addons = []; + foreach ($addonList as $name => $item) { + $config = self::config($name); + $addons[] = ['name' => $name, 'domains' => $config['domains'] ?? [], 'licensecodes' => $config['licensecodes'] ?? [], 'validations' => $config['validations'] ?? []]; + } + $params = array_merge($params, [ + 'faversion' => config('fastadmin.version'), + 'domain' => $domain, + 'addons' => $addons + ]); + $result = self::sendRequest('/addon/authorization', $params, 'POST'); + if (isset($result['code']) && $result['code'] == 1) { + $json = $result['data']['addons'] ?? []; + foreach ($addonList as $name => $item) { + self::config($name, ['domains' => $json[$name]['domains'] ?? [], 'licensecodes' => $json[$name]['licensecodes'] ?? [], 'validations' => $json[$name]['validations'] ?? []]); + } + return true; + } else { + throw new Exception($result['msg'] ?? __('Network error')); + } + } + + /** + * 验证插件授权,应用插件需要授权使用,移除或绕过授权验证,保留追究法律责任的权利 + * @param $name + * @return bool + */ + public static function checkAddonAuthorization($name) + { + $request = request(); + $config = self::config($name); + $domain = self::getRootDomain($request->host(true)); + //应用插件需要授权使用,移除或绕过授权验证,保留追究法律责任的权利 + if (isset($config['domains']) && isset($config['domains']) && isset($config['validations']) && isset($config['licensecodes'])) { + $index = array_search($domain, $config['domains']); + if ((in_array($domain, $config['domains']) && in_array(md5(md5($domain) . ($config['licensecodes'][$index] ?? '')), $config['validations'])) || $request->isCli()) { + $request->bind('authorized', $domain ?: 'cli'); + return true; + } elseif ($config['domains']) { + foreach ($config['domains'] as $index => $item) { + if (substr_compare($domain, "." . $item, -strlen("." . $item)) === 0 && in_array(md5(md5($item) . ($config['licensecodes'][$index] ?? '')), $config['validations'])) { + $request->bind('authorized', $domain); + return true; + } + } + } + } + return false; + } + + /** + * 获取顶级域名 + * @param $domain + * @return string + */ + public static function getRootDomain($domain) + { + $host = strtolower(trim($domain)); + $hostArr = explode('.', $host); + $hostCount = count($hostArr); + $cnRegex = '/\w+\.(gov|org|ac|mil|net|edu|com|bj|tj|sh|cq|he|sx|nm|ln|jl|hl|js|zj|ah|fj|jx|sd|ha|hb|hn|gd|gx|hi|sc|gz|yn|xz|sn|gs|qh|nx|xj|tw|hk|mo)\.cn$/i'; + $countryRegex = '/\w+\.(\w{2}|com|net)\.\w{2}$/i'; + if ($hostCount > 2 && (preg_match($cnRegex, $host) || preg_match($countryRegex, $host))) { + $host = implode('.', array_slice($hostArr, -3, 3, true)); + } else { + $host = implode('.', array_slice($hostArr, -2, 2, true)); + } + return $host; + } + + /** + * 获取插件行为、路由配置文件 + * @return string + */ + public static function getExtraAddonsFile() + { + return CONF_PATH . 'extra' . DS . 'addons.php'; + } + + /** + * 获取bootstrap.js路径 + * @return string + */ + public static function getBootstrapFile($name) + { + return ADDON_PATH . $name . DS . 'bootstrap.js'; + } + + /** + * 获取testdata.sql路径 + * @return string + */ + public static function getTestdataFile($name) + { + return ADDON_PATH . $name . DS . 'testdata.sql'; + } + + /** + * 获取指定插件的目录 + */ + public static function getAddonDir($name) + { + $dir = ADDON_PATH . $name . DS; + return $dir; + } + + /** + * 获取插件备份目录 + */ + public static function getAddonsBackupDir() + { + $dir = RUNTIME_PATH . 'addons' . DS; + if (!is_dir($dir)) { + @mkdir($dir, 0755, true); + } + return $dir; + } + + /** + * 获取插件源资源文件夹 + * @param string $name 插件名称 + * @return string + */ + protected static function getSourceAssetsDir($name) + { + return ADDON_PATH . $name . DS . 'assets' . DS; + } + + /** + * 获取插件目标资源文件夹 + * @param string $name 插件名称 + * @return string + */ + protected static function getDestAssetsDir($name) + { + $assetsDir = ROOT_PATH . str_replace("/", DS, "public/assets/addons/{$name}/"); + return $assetsDir; + } + + /** + * 获取远程服务器 + * @return string + */ + protected static function getServerUrl() + { + return config('fastadmin.api_url'); + } + + /** + * 获取检测的全局文件夹目录 + * @return array + */ + protected static function getCheckDirs() + { + return [ + 'application', + 'public' + ]; + } + + /** + * 获取请求对象 + * @return Client + */ + public static function getClient() + { + $options = [ + 'base_uri' => self::getServerUrl(), + 'timeout' => 30, + 'connect_timeout' => 30, + 'verify' => false, + 'http_errors' => false, + 'headers' => [ + 'X-REQUESTED-WITH' => 'XMLHttpRequest', + 'Referer' => dirname(request()->root(true)), + 'User-Agent' => 'FastAddon', + ] + ]; + static $client; + if (empty($client)) { + $client = new Client($options); + } + return $client; + } + + /** + * 发送请求 + * @return array + * @throws Exception + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public static function sendRequest($url, $params = [], $method = 'POST') + { + $json = []; + try { + $client = self::getClient(); + $options = strtoupper($method) == 'POST' ? ['form_params' => $params] : ['query' => $params]; + $response = $client->request($method, $url, $options); + $body = $response->getBody(); + $content = $body->getContents(); + $json = (array)json_decode($content, true); + } catch (TransferException $e) { + throw new Exception(__('Network error')); + } catch (\Exception $e) { + throw new Exception(__('Unknown data format')); + } + return $json; + } + + /** + * 匹配配置文件中info信息 + * @param ZipFile $zip + * @return array|false + * @throws Exception + */ + protected static function getInfoIni($zip) + { + $config = []; + // 读取插件信息 + try { + $info = $zip->getEntryContents('info.ini'); + $config = parse_ini_string($info); + } catch (ZipException $e) { + throw new Exception('Unable to extract the file'); + } + return $config; + } + +} diff --git a/vendor/karsonzhang/fastadmin-addons/src/common.php b/vendor/karsonzhang/fastadmin-addons/src/common.php new file mode 100644 index 0000000..0f95710 --- /dev/null +++ b/vendor/karsonzhang/fastadmin-addons/src/common.php @@ -0,0 +1,520 @@ + $v) { + if (is_array($v)) { + $addon = $v['addon']; + $domain = $v['domain']; + $drules = []; + foreach ($v['rule'] as $m => $n) { + $urlArr = explode('/', $n); + if (count($urlArr) < 3) { + continue; + } + list($addon, $controller, $action) = $urlArr; + $drules[$m] = sprintf($execute . '&indomain=1', $addon, $controller, $action); + } + //$domains[$domain] = $drules ? $drules : "\\addons\\{$k}\\controller"; + $domains[$domain] = $drules ? $drules : []; + $domains[$domain][':controller/[:action]'] = sprintf($execute . '&indomain=1', $addon, ":controller", ":action"); + } else { + if (!$v) { + continue; + } + $urlArr = explode('/', $v); + if (count($urlArr) < 3) { + continue; + } + list($addon, $controller, $action) = $urlArr; + $rules[$k] = sprintf($execute, $addon, $controller, $action); + } + } + Route::rule($rules); + if ($domains) { + Route::domain($domains); + } + + // 获取系统配置 + $hooks = App::$debug ? [] : Cache::get('hooks', []); + if (empty($hooks)) { + $hooks = (array)Config::get('addons.hooks'); + // 初始化钩子 + foreach ($hooks as $key => $values) { + $values = is_string($values) ? explode(',', $values) : (array)$values; + $values = array_filter($values); + $hooks[$key] = array_filter(array_map('get_addon_class', $values)); + } + Cache::set('hooks', $hooks); + } + //如果在插件中有定义app_init,则直接执行 + if (isset($hooks['app_init'])) { + foreach ($hooks['app_init'] as $k => $v) { + Hook::exec($v, 'app_init'); + } + } + Hook::import($hooks, true); +}); + +/** + * 处理插件钩子 + * @param string $hook 钩子名称 + * @param mixed $params 传入参数 + * @return void + */ +function hook($hook, $params = []) +{ + Hook::listen($hook, $params); +} + +/** + * 移除空目录 + * @param string $dir 目录 + */ +function remove_empty_folder($dir) +{ + try { + $isDirEmpty = !(new \FilesystemIterator($dir))->valid(); + if ($isDirEmpty) { + @rmdir($dir); + remove_empty_folder(dirname($dir)); + } + } catch (\UnexpectedValueException $e) { + + } catch (\Exception $e) { + + } +} + +/** + * 获得插件列表 + * @return array + */ +function get_addon_list() +{ + $results = scandir(ADDON_PATH); + $list = []; + foreach ($results as $name) { + if ($name === '.' or $name === '..') { + continue; + } + if (is_file(ADDON_PATH . $name)) { + continue; + } + $addonDir = ADDON_PATH . $name . DS; + if (!is_dir($addonDir)) { + continue; + } + + if (!is_file($addonDir . ucfirst($name) . '.php')) { + continue; + } + + //这里不采用get_addon_info是因为会有缓存 + //$info = get_addon_info($name); + $info_file = $addonDir . 'info.ini'; + if (!is_file($info_file)) { + continue; + } + + $info = Config::parse($info_file, '', "addon-info-{$name}"); + if (!isset($info['name'])) { + continue; + } + $info['url'] = addon_url($name); + $list[$name] = $info; + } + return $list; +} + +/** + * 获得插件自动加载的配置 + * @param bool $truncate 是否清除手动配置的钩子 + * @return array + */ +function get_addon_autoload_config($truncate = false) +{ + // 读取addons的配置 + $config = (array)Config::get('addons'); + if ($truncate) { + // 清空手动配置的钩子 + $config['hooks'] = []; + } + + // 伪静态优先级 + $priority = isset($config['priority']) && $config['priority'] ? is_array($config['priority']) ? $config['priority'] : explode(',', $config['priority']) : []; + + $route = []; + // 读取插件目录及钩子列表 + $base = get_class_methods("\\think\\Addons"); + $base = array_merge($base, ['install', 'uninstall', 'enable', 'disable']); + + $url_domain_deploy = Config::get('url_domain_deploy'); + $addons = get_addon_list(); + $domain = []; + + $priority = array_merge($priority, array_keys($addons)); + + $orderedAddons = array(); + foreach ($priority as $key) { + if (!isset($addons[$key])) { + continue; + } + $orderedAddons[$key] = $addons[$key]; + } + + foreach ($orderedAddons as $name => $addon) { + if (!$addon['state']) { + continue; + } + + // 读取出所有公共方法 + $methods = (array)get_class_methods("\\addons\\" . $name . "\\" . ucfirst($name)); + // 跟插件基类方法做比对,得到差异结果 + $hooks = array_diff($methods, $base); + // 循环将钩子方法写入配置中 + foreach ($hooks as $hook) { + $hook = Loader::parseName($hook, 0, false); + if (!isset($config['hooks'][$hook])) { + $config['hooks'][$hook] = []; + } + // 兼容手动配置项 + if (is_string($config['hooks'][$hook])) { + $config['hooks'][$hook] = explode(',', $config['hooks'][$hook]); + } + if (!in_array($name, $config['hooks'][$hook])) { + $config['hooks'][$hook][] = $name; + } + } + $conf = get_addon_config($addon['name']); + if ($conf) { + $conf['rewrite'] = isset($conf['rewrite']) && is_array($conf['rewrite']) ? $conf['rewrite'] : []; + $rule = array_map(function ($value) use ($addon) { + return "{$addon['name']}/{$value}"; + }, array_flip($conf['rewrite'])); + if ($url_domain_deploy && isset($conf['domain']) && $conf['domain']) { + $domain[] = [ + 'addon' => $addon['name'], + 'domain' => $conf['domain'], + 'rule' => $rule + ]; + } else { + $route = array_merge($route, $rule); + } + } + } + $config['route'] = $route; + $config['route'] = array_merge($config['route'], $domain); + return $config; +} + +/** + * 获取插件类的类名 + * @param string $name 插件名 + * @param string $type 返回命名空间类型 + * @param string $class 当前类名 + * @return string + */ +function get_addon_class($name, $type = 'hook', $class = null) +{ + $name = Loader::parseName($name); + // 处理多级控制器情况 + if (!is_null($class) && strpos($class, '.')) { + $class = explode('.', $class); + + $class[count($class) - 1] = Loader::parseName(end($class), 1); + $class = implode('\\', $class); + } else { + $class = Loader::parseName(is_null($class) ? $name : $class, 1); + } + switch ($type) { + case 'controller': + $namespace = "\\addons\\" . $name . "\\controller\\" . $class; + break; + default: + $namespace = "\\addons\\" . $name . "\\" . $class; + } + return class_exists($namespace) ? $namespace : ''; +} + +/** + * 读取插件的基础信息 + * @param string $name 插件名 + * @return array + */ +function get_addon_info($name) +{ + $addon = get_addon_instance($name); + if (!$addon) { + return []; + } + return $addon->getInfo($name); +} + +/** + * 获取插件类的配置数组 + * @param string $name 插件名 + * @return array + */ +function get_addon_fullconfig($name) +{ + $addon = get_addon_instance($name); + if (!$addon) { + return []; + } + return $addon->getFullConfig($name); +} + +/** + * 获取插件类的配置值值 + * @param string $name 插件名 + * @return array + */ +function get_addon_config($name) +{ + $addon = get_addon_instance($name); + if (!$addon) { + return []; + } + return $addon->getConfig($name); +} + +/** + * 获取插件的单例 + * @param string $name 插件名 + * @return mixed|null + */ +function get_addon_instance($name) +{ + static $_addons = []; + if (isset($_addons[$name])) { + return $_addons[$name]; + } + $class = get_addon_class($name); + if (class_exists($class)) { + $_addons[$name] = new $class(); + return $_addons[$name]; + } else { + return null; + } +} + +/** + * 获取插件创建的表 + * @param string $name 插件名 + * @return array + */ +function get_addon_tables($name) +{ + $addonInfo = get_addon_info($name); + if (!$addonInfo) { + return []; + } + $regex = "/^CREATE\s+TABLE\s+(IF\s+NOT\s+EXISTS\s+)?`?([a-zA-Z_]+)`?/mi"; + $sqlFile = ADDON_PATH . $name . DS . 'install.sql'; + $tables = []; + if (is_file($sqlFile)) { + preg_match_all($regex, file_get_contents($sqlFile), $matches); + if ($matches && isset($matches[2]) && $matches[2]) { + $prefix = config('database.prefix'); + $tables = array_map(function ($item) use ($prefix) { + return str_replace("__PREFIX__", $prefix, $item); + }, $matches[2]); + } + } + return $tables; +} + +/** + * 插件显示内容里生成访问插件的url + * @param string $url 地址 格式:插件名/控制器/方法 + * @param array $vars 变量参数 + * @param bool|string $suffix 生成的URL后缀 + * @param bool|string $domain 域名 + * @return bool|string + */ +function addon_url($url, $vars = [], $suffix = true, $domain = false) +{ + $url = ltrim($url, '/'); + $addon = substr($url, 0, stripos($url, '/')); + if (!is_array($vars)) { + parse_str($vars, $params); + $vars = $params; + } + $params = []; + foreach ($vars as $k => $v) { + if (substr($k, 0, 1) === ':') { + $params[$k] = $v; + unset($vars[$k]); + } + } + $val = "@addons/{$url}"; + $config = get_addon_config($addon); + $dispatch = think\Request::instance()->dispatch(); + $indomain = isset($dispatch['var']['indomain']) && $dispatch['var']['indomain'] && $dispatch['var']['addon'] == $addon ? true : false; + //优先取插件配置中的domain,没有的情况下取全局的域名前缀配置 + $domainprefix = $config && isset($config['domain']) && $config['domain'] ? $config['domain'] : Config::get('addons.domain'); + $domain = $domainprefix && Config::get('url_domain_deploy') ? $domainprefix : $domain; + $rewrite = $config && isset($config['rewrite']) && $config['rewrite'] ? $config['rewrite'] : []; + if ($rewrite) { + $path = substr($url, stripos($url, '/') + 1); + if (isset($rewrite[$path]) && $rewrite[$path]) { + $val = $rewrite[$path]; + array_walk($params, function ($value, $key) use (&$val) { + $val = str_replace("[{$key}]", $value, $val); + }); + $val = str_replace(['^', '$'], '', $val); + if (substr($val, -1) === '/') { + $suffix = false; + } + } else { + // 如果采用了域名部署,则需要去掉前两段 + if ($indomain && $domainprefix) { + $arr = explode("/", $val); + $val = implode("/", array_slice($arr, 2)); + } + } + } else { + // 如果采用了域名部署,则需要去掉前两段 + if ($indomain && $domainprefix) { + $arr = explode("/", $val); + $val = implode("/", array_slice($arr, 2)); + } + foreach ($params as $k => $v) { + $vars[substr($k, 1)] = $v; + } + } + $url = url($val, [], $suffix, $domain) . ($vars ? '?' . http_build_query($vars) : ''); + $url = preg_replace("/\/((?!index)[\w]+)\.php\//i", "/", $url); + return $url; +} + +/** + * 设置基础配置信息 + * @param string $name 插件名 + * @param array $array 配置数据 + * @return boolean + * @throws Exception + */ +function set_addon_info($name, $array) +{ + $file = ADDON_PATH . $name . DS . 'info.ini'; + $addon = get_addon_instance($name); + $array = $addon->setInfo($name, $array); + if (!isset($array['name']) || !isset($array['title']) || !isset($array['version'])) { + throw new Exception("插件配置写入失败"); + } + $res = array(); + foreach ($array as $key => $val) { + if (is_array($val)) { + $res[] = "[$key]"; + foreach ($val as $skey => $sval) { + $res[] = "$skey = " . (is_numeric($sval) ? $sval : $sval); + } + } else { + $res[] = "$key = " . (is_numeric($val) ? $val : $val); + } + } + if (file_put_contents($file, implode("\n", $res) . "\n", LOCK_EX)) { + //清空当前配置缓存 + Config::set($name, null, 'addoninfo'); + } else { + throw new Exception("文件没有写入权限"); + } + return true; +} + +/** + * 写入配置文件 + * @param string $name 插件名 + * @param array $config 配置数据 + * @param boolean $writefile 是否写入配置文件 + * @return bool + * @throws Exception + */ +function set_addon_config($name, $config, $writefile = true) +{ + $addon = get_addon_instance($name); + $addon->setConfig($name, $config); + $fullconfig = get_addon_fullconfig($name); + foreach ($fullconfig as $k => &$v) { + if (isset($config[$v['name']])) { + $value = $v['type'] !== 'array' && is_array($config[$v['name']]) ? implode(',', $config[$v['name']]) : $config[$v['name']]; + $v['value'] = $value; + } + } + if ($writefile) { + // 写入配置文件 + set_addon_fullconfig($name, $fullconfig); + } + return true; +} + +/** + * 写入配置文件 + * + * @param string $name 插件名 + * @param array $array 配置数据 + * @return boolean + * @throws Exception + */ +function set_addon_fullconfig($name, $array) +{ + $file = ADDON_PATH . $name . DS . 'config.php'; + $ret = file_put_contents($file, " true, +]; diff --git a/vendor/markbaker/complex/README.md b/vendor/markbaker/complex/README.md new file mode 100644 index 0000000..c306394 --- /dev/null +++ b/vendor/markbaker/complex/README.md @@ -0,0 +1,156 @@ +PHPComplex +========== + +--- + +PHP Class for handling Complex numbers + +Master: [![Build Status](https://travis-ci.org/MarkBaker/PHPComplex.png?branch=master)](http://travis-ci.org/MarkBaker/PHPComplex) + +Develop: [![Build Status](https://travis-ci.org/MarkBaker/PHPComplex.png?branch=develop)](http://travis-ci.org/MarkBaker/PHPComplex) + +[![Complex Numbers](https://imgs.xkcd.com/comics/complex_numbers_2x.png)](https://xkcd.com/2028/) + +--- + +The library currently provides the following operations: + + - addition + - subtraction + - multiplication + - division + - division by + - division into + +together with functions for + + - theta (polar theta angle) + - rho (polar distance/radius) + - conjugate + * negative + - inverse (1 / complex) + - cos (cosine) + - acos (inverse cosine) + - cosh (hyperbolic cosine) + - acosh (inverse hyperbolic cosine) + - sin (sine) + - asin (inverse sine) + - sinh (hyperbolic sine) + - asinh (inverse hyperbolic sine) + - sec (secant) + - asec (inverse secant) + - sech (hyperbolic secant) + - asech (inverse hyperbolic secant) + - csc (cosecant) + - acsc (inverse cosecant) + - csch (hyperbolic secant) + - acsch (inverse hyperbolic secant) + - tan (tangent) + - atan (inverse tangent) + - tanh (hyperbolic tangent) + - atanh (inverse hyperbolic tangent) + - cot (cotangent) + - acot (inverse cotangent) + - coth (hyperbolic cotangent) + - acoth (inverse hyperbolic cotangent) + - sqrt (square root) + - exp (exponential) + - ln (natural log) + - log10 (base-10 log) + - log2 (base-2 log) + - pow (raised to the power of a real number) + + +--- + +# Usage + +To create a new complex object, you can provide either the real, imaginary and suffix parts as individual values, or as an array of values passed passed to the constructor; or a string representing the value. e.g + +``` +$real = 1.23; +$imaginary = -4.56; +$suffix = 'i'; + +$complexObject = new Complex\Complex($real, $imaginary, $suffix); +``` +or +``` +$real = 1.23; +$imaginary = -4.56; +$suffix = 'i'; + +$arguments = [$real, $imaginary, $suffix]; + +$complexObject = new Complex\Complex($arguments); +``` +or +``` +$complexString = '1.23-4.56i'; + +$complexObject = new Complex\Complex($complexString); +``` + +Complex objects are immutable: whenever you call a method or pass a complex value to a function that returns a complex value, a new Complex object will be returned, and the original will remain unchanged. +This also allows you to chain multiple methods as you would for a fluent interface (as long as they are methods that will return a Complex result). + +## Performing Mathematical Operations + +To perform mathematical operations with Complex values, you can call the appropriate method against a complex value, passing other values as arguments + +``` +$complexString1 = '1.23-4.56i'; +$complexString2 = '2.34+5.67i'; + +$complexObject = new Complex\Complex($complexString1); +echo $complexObject->add($complexString2); +``` +or pass all values to the appropriate function +``` +$complexString1 = '1.23-4.56i'; +$complexString2 = '2.34+5.67i'; + +echo Complex\add($complexString1, $complexString2); +``` +If you want to perform the same operation against multiple values (e.g. to add three or more complex numbers), then you can pass multiple arguments to any of the operations. + +You can pass these arguments as Complex objects, or as an array or string that will parse to a complex object. + +## Using functions + +When calling any of the available functions for a complex value, you can either call the relevant method for the Complex object +``` +$complexString = '1.23-4.56i'; + +$complexObject = new Complex\Complex($complexString); +echo $complexObject->sinh(); +``` +or you can call the function as you would in procedural code, passing the Complex object as an argument +``` +$complexString = '1.23-4.56i'; + +$complexObject = new Complex\Complex($complexString); +echo Complex\sinh($complexObject); +``` +When called procedurally using the function, you can pass in the argument as a Complex object, or as an array or string that will parse to a complex object. +``` +$complexString = '1.23-4.56i'; + +echo Complex\sinh($complexString); +``` + +In the case of the `pow()` function (the only implemented function that requires an additional argument) you need to pass both arguments when calling the function procedurally + +``` +$complexString = '1.23-4.56i'; + +$complexObject = new Complex\Complex($complexString); +echo Complex\pow($complexObject, 2); +``` +or pass the additional argument when calling the method +``` +$complexString = '1.23-4.56i'; + +$complexObject = new Complex\Complex($complexString); +echo $complexObject->pow(2); +``` diff --git a/vendor/markbaker/complex/classes/Autoloader.php b/vendor/markbaker/complex/classes/Autoloader.php new file mode 100644 index 0000000..792ecef --- /dev/null +++ b/vendor/markbaker/complex/classes/Autoloader.php @@ -0,0 +1,53 @@ +regex = $regex; + parent::__construct($it, $regex); + } +} + +class FilenameFilter extends FilesystemRegexFilter +{ + // Filter files against the regex + public function accept() + { + return (!$this->isFile() || preg_match($this->regex, $this->getFilename())); + } +} + + +$srcFolder = __DIR__ . DIRECTORY_SEPARATOR . 'src'; +$srcDirectory = new RecursiveDirectoryIterator($srcFolder); + +$filteredFileList = new FilenameFilter($srcDirectory, '/(?:php)$/i'); +$filteredFileList = new FilenameFilter($filteredFileList, '/^(?!.*(Complex|Exception)\.php).*$/i'); + +foreach (new RecursiveIteratorIterator($filteredFileList) as $file) { + if ($file->isFile()) { + include_once $file; + } +} diff --git a/vendor/markbaker/complex/classes/src/Complex.php b/vendor/markbaker/complex/classes/src/Complex.php new file mode 100644 index 0000000..3639712 --- /dev/null +++ b/vendor/markbaker/complex/classes/src/Complex.php @@ -0,0 +1,390 @@ +realPart = (float) $realPart; + $this->imaginaryPart = (float) $imaginaryPart; + $this->suffix = strtolower($suffix); + } + + /** + * Gets the real part of this complex number + * + * @return Float + */ + public function getReal() + { + return $this->realPart; + } + + /** + * Gets the imaginary part of this complex number + * + * @return Float + */ + public function getImaginary() + { + return $this->imaginaryPart; + } + + /** + * Gets the suffix of this complex number + * + * @return String + */ + public function getSuffix() + { + return $this->suffix; + } + + /** + * Returns true if this is a real value, false if a complex value + * + * @return Bool + */ + public function isReal() + { + return $this->imaginaryPart == 0.0; + } + + /** + * Returns true if this is a complex value, false if a real value + * + * @return Bool + */ + public function isComplex() + { + return !$this->isReal(); + } + + public function format() + { + $str = ""; + if ($this->imaginaryPart != 0.0) { + if (\abs($this->imaginaryPart) != 1.0) { + $str .= $this->imaginaryPart . $this->suffix; + } else { + $str .= (($this->imaginaryPart < 0.0) ? '-' : '') . $this->suffix; + } + } + if ($this->realPart != 0.0) { + if (($str) && ($this->imaginaryPart > 0.0)) { + $str = "+" . $str; + } + $str = $this->realPart . $str; + } + if (!$str) { + $str = "0.0"; + } + + return $str; + } + + public function __toString() + { + return $this->format(); + } + + /** + * Validates whether the argument is a valid complex number, converting scalar or array values if possible + * + * @param mixed $complex The value to validate + * @return Complex + * @throws Exception If the argument isn't a Complex number or cannot be converted to one + */ + public static function validateComplexArgument($complex) + { + if (is_scalar($complex) || is_array($complex)) { + $complex = new Complex($complex); + } elseif (!is_object($complex) || !($complex instanceof Complex)) { + throw new Exception('Value is not a valid complex number'); + } + + return $complex; + } + + /** + * Returns the reverse of this complex number + * + * @return Complex + */ + public function reverse() + { + return new Complex( + $this->imaginaryPart, + $this->realPart, + ($this->realPart == 0.0) ? null : $this->suffix + ); + } + + public function invertImaginary() + { + return new Complex( + $this->realPart, + $this->imaginaryPart * -1, + ($this->imaginaryPart == 0.0) ? null : $this->suffix + ); + } + + public function invertReal() + { + return new Complex( + $this->realPart * -1, + $this->imaginaryPart, + ($this->imaginaryPart == 0.0) ? null : $this->suffix + ); + } + + protected static $functions = [ + 'abs', + 'acos', + 'acosh', + 'acot', + 'acoth', + 'acsc', + 'acsch', + 'argument', + 'asec', + 'asech', + 'asin', + 'asinh', + 'atan', + 'atanh', + 'conjugate', + 'cos', + 'cosh', + 'cot', + 'coth', + 'csc', + 'csch', + 'exp', + 'inverse', + 'ln', + 'log2', + 'log10', + 'negative', + 'pow', + 'rho', + 'sec', + 'sech', + 'sin', + 'sinh', + 'sqrt', + 'tan', + 'tanh', + 'theta', + ]; + + protected static $operations = [ + 'add', + 'subtract', + 'multiply', + 'divideby', + 'divideinto', + ]; + + /** + * Returns the result of the function call or operation + * + * @return Complex|float + * @throws Exception|\InvalidArgumentException + */ + public function __call($functionName, $arguments) + { + $functionName = strtolower(str_replace('_', '', $functionName)); + + // Test for function calls + if (in_array($functionName, self::$functions, true)) { + $functionName = "\\" . __NAMESPACE__ . "\\{$functionName}"; + return $functionName($this, ...$arguments); + } + // Test for operation calls + if (in_array($functionName, self::$operations, true)) { + $functionName = "\\" . __NAMESPACE__ . "\\{$functionName}"; + return $functionName($this, ...$arguments); + } + throw new Exception('Function or Operation does not exist'); + } +} diff --git a/vendor/markbaker/complex/classes/src/Exception.php b/vendor/markbaker/complex/classes/src/Exception.php new file mode 100644 index 0000000..a2beb73 --- /dev/null +++ b/vendor/markbaker/complex/classes/src/Exception.php @@ -0,0 +1,13 @@ +getReal() - $invsqrt->getImaginary(), + $complex->getImaginary() + $invsqrt->getReal() + ); + $log = ln($adjust); + + return new Complex( + $log->getImaginary(), + -1 * $log->getReal() + ); +} diff --git a/vendor/markbaker/complex/classes/src/functions/acosh.php b/vendor/markbaker/complex/classes/src/functions/acosh.php new file mode 100644 index 0000000..18a992e --- /dev/null +++ b/vendor/markbaker/complex/classes/src/functions/acosh.php @@ -0,0 +1,34 @@ +isReal() && ($complex->getReal() > 1)) { + return new Complex(\acosh($complex->getReal())); + } + + $acosh = acos($complex) + ->reverse(); + if ($acosh->getReal() < 0.0) { + $acosh = $acosh->invertReal(); + } + + return $acosh; +} diff --git a/vendor/markbaker/complex/classes/src/functions/acot.php b/vendor/markbaker/complex/classes/src/functions/acot.php new file mode 100644 index 0000000..11bee46 --- /dev/null +++ b/vendor/markbaker/complex/classes/src/functions/acot.php @@ -0,0 +1,25 @@ +getReal() == 0.0 && $complex->getImaginary() == 0.0) { + return INF; + } + + return asin(inverse($complex)); +} diff --git a/vendor/markbaker/complex/classes/src/functions/acsch.php b/vendor/markbaker/complex/classes/src/functions/acsch.php new file mode 100644 index 0000000..bb45d34 --- /dev/null +++ b/vendor/markbaker/complex/classes/src/functions/acsch.php @@ -0,0 +1,29 @@ +getReal() == 0.0 && $complex->getImaginary() == 0.0) { + return INF; + } + + return asinh(inverse($complex)); +} diff --git a/vendor/markbaker/complex/classes/src/functions/argument.php b/vendor/markbaker/complex/classes/src/functions/argument.php new file mode 100644 index 0000000..d7209cc --- /dev/null +++ b/vendor/markbaker/complex/classes/src/functions/argument.php @@ -0,0 +1,28 @@ +getReal() == 0.0 && $complex->getImaginary() == 0.0) { + return INF; + } + + return acos(inverse($complex)); +} diff --git a/vendor/markbaker/complex/classes/src/functions/asech.php b/vendor/markbaker/complex/classes/src/functions/asech.php new file mode 100644 index 0000000..b36c40e --- /dev/null +++ b/vendor/markbaker/complex/classes/src/functions/asech.php @@ -0,0 +1,29 @@ +getReal() == 0.0 && $complex->getImaginary() == 0.0) { + return INF; + } + + return acosh(inverse($complex)); +} diff --git a/vendor/markbaker/complex/classes/src/functions/asin.php b/vendor/markbaker/complex/classes/src/functions/asin.php new file mode 100644 index 0000000..9c982ac --- /dev/null +++ b/vendor/markbaker/complex/classes/src/functions/asin.php @@ -0,0 +1,37 @@ +getReal() - $complex->getImaginary(), + $invsqrt->getImaginary() + $complex->getReal() + ); + $log = ln($adjust); + + return new Complex( + $log->getImaginary(), + -1 * $log->getReal() + ); +} diff --git a/vendor/markbaker/complex/classes/src/functions/asinh.php b/vendor/markbaker/complex/classes/src/functions/asinh.php new file mode 100644 index 0000000..c1243fd --- /dev/null +++ b/vendor/markbaker/complex/classes/src/functions/asinh.php @@ -0,0 +1,33 @@ +isReal() && ($complex->getReal() > 1)) { + return new Complex(\asinh($complex->getReal())); + } + + $asinh = clone $complex; + $asinh = $asinh->reverse() + ->invertReal(); + $asinh = asin($asinh); + return $asinh->reverse() + ->invertImaginary(); +} diff --git a/vendor/markbaker/complex/classes/src/functions/atan.php b/vendor/markbaker/complex/classes/src/functions/atan.php new file mode 100644 index 0000000..2c75dcf --- /dev/null +++ b/vendor/markbaker/complex/classes/src/functions/atan.php @@ -0,0 +1,45 @@ +isReal()) { + return new Complex(\atan($complex->getReal())); + } + + $t1Value = new Complex(-1 * $complex->getImaginary(), $complex->getReal()); + $uValue = new Complex(1, 0); + + $d1Value = clone $uValue; + $d1Value = subtract($d1Value, $t1Value); + $d2Value = add($t1Value, $uValue); + $uResult = $d1Value->divideBy($d2Value); + $uResult = ln($uResult); + + return new Complex( + (($uResult->getImaginary() == M_PI) ? -M_PI : $uResult->getImaginary()) * -0.5, + $uResult->getReal() * 0.5, + $complex->getSuffix() + ); +} diff --git a/vendor/markbaker/complex/classes/src/functions/atanh.php b/vendor/markbaker/complex/classes/src/functions/atanh.php new file mode 100644 index 0000000..c53f2a9 --- /dev/null +++ b/vendor/markbaker/complex/classes/src/functions/atanh.php @@ -0,0 +1,38 @@ +isReal()) { + $real = $complex->getReal(); + if ($real >= -1.0 && $real <= 1.0) { + return new Complex(\atanh($real)); + } else { + return new Complex(\atanh(1 / $real), (($real < 0.0) ? M_PI_2 : -1 * M_PI_2)); + } + } + + $iComplex = clone $complex; + $iComplex = $iComplex->invertImaginary() + ->reverse(); + return atan($iComplex) + ->invertReal() + ->reverse(); +} diff --git a/vendor/markbaker/complex/classes/src/functions/conjugate.php b/vendor/markbaker/complex/classes/src/functions/conjugate.php new file mode 100644 index 0000000..bd1984b --- /dev/null +++ b/vendor/markbaker/complex/classes/src/functions/conjugate.php @@ -0,0 +1,28 @@ +getReal(), + -1 * $complex->getImaginary(), + $complex->getSuffix() + ); +} diff --git a/vendor/markbaker/complex/classes/src/functions/cos.php b/vendor/markbaker/complex/classes/src/functions/cos.php new file mode 100644 index 0000000..80a4683 --- /dev/null +++ b/vendor/markbaker/complex/classes/src/functions/cos.php @@ -0,0 +1,34 @@ +isReal()) { + return new Complex(\cos($complex->getReal())); + } + + return conjugate( + new Complex( + \cos($complex->getReal()) * \cosh($complex->getImaginary()), + \sin($complex->getReal()) * \sinh($complex->getImaginary()), + $complex->getSuffix() + ) + ); +} diff --git a/vendor/markbaker/complex/classes/src/functions/cosh.php b/vendor/markbaker/complex/classes/src/functions/cosh.php new file mode 100644 index 0000000..a4bea65 --- /dev/null +++ b/vendor/markbaker/complex/classes/src/functions/cosh.php @@ -0,0 +1,32 @@ +isReal()) { + return new Complex(\cosh($complex->getReal())); + } + + return new Complex( + \cosh($complex->getReal()) * \cos($complex->getImaginary()), + \sinh($complex->getReal()) * \sin($complex->getImaginary()), + $complex->getSuffix() + ); +} diff --git a/vendor/markbaker/complex/classes/src/functions/cot.php b/vendor/markbaker/complex/classes/src/functions/cot.php new file mode 100644 index 0000000..339101e --- /dev/null +++ b/vendor/markbaker/complex/classes/src/functions/cot.php @@ -0,0 +1,29 @@ +getReal() == 0.0 && $complex->getImaginary() == 0.0) { + return new Complex(INF); + } + + return inverse(tan($complex)); +} diff --git a/vendor/markbaker/complex/classes/src/functions/coth.php b/vendor/markbaker/complex/classes/src/functions/coth.php new file mode 100644 index 0000000..7fe705a --- /dev/null +++ b/vendor/markbaker/complex/classes/src/functions/coth.php @@ -0,0 +1,24 @@ +getReal() == 0.0 && $complex->getImaginary() == 0.0) { + return INF; + } + + return inverse(sin($complex)); +} diff --git a/vendor/markbaker/complex/classes/src/functions/csch.php b/vendor/markbaker/complex/classes/src/functions/csch.php new file mode 100644 index 0000000..f450098 --- /dev/null +++ b/vendor/markbaker/complex/classes/src/functions/csch.php @@ -0,0 +1,29 @@ +getReal() == 0.0 && $complex->getImaginary() == 0.0) { + return INF; + } + + return inverse(sinh($complex)); +} diff --git a/vendor/markbaker/complex/classes/src/functions/exp.php b/vendor/markbaker/complex/classes/src/functions/exp.php new file mode 100644 index 0000000..4cac696 --- /dev/null +++ b/vendor/markbaker/complex/classes/src/functions/exp.php @@ -0,0 +1,34 @@ +getReal() == 0.0) && (\abs($complex->getImaginary()) == M_PI)) { + return new Complex(-1.0, 0.0); + } + + $rho = \exp($complex->getReal()); + + return new Complex( + $rho * \cos($complex->getImaginary()), + $rho * \sin($complex->getImaginary()), + $complex->getSuffix() + ); +} diff --git a/vendor/markbaker/complex/classes/src/functions/inverse.php b/vendor/markbaker/complex/classes/src/functions/inverse.php new file mode 100644 index 0000000..7d3182a --- /dev/null +++ b/vendor/markbaker/complex/classes/src/functions/inverse.php @@ -0,0 +1,29 @@ +getReal() == 0.0 && $complex->getImaginary() == 0.0) { + throw new \InvalidArgumentException('Division by zero'); + } + + return $complex->divideInto(1.0); +} diff --git a/vendor/markbaker/complex/classes/src/functions/ln.php b/vendor/markbaker/complex/classes/src/functions/ln.php new file mode 100644 index 0000000..39071cf --- /dev/null +++ b/vendor/markbaker/complex/classes/src/functions/ln.php @@ -0,0 +1,33 @@ +getReal() == 0.0) && ($complex->getImaginary() == 0.0)) { + throw new \InvalidArgumentException(); + } + + return new Complex( + \log(rho($complex)), + theta($complex), + $complex->getSuffix() + ); +} diff --git a/vendor/markbaker/complex/classes/src/functions/log10.php b/vendor/markbaker/complex/classes/src/functions/log10.php new file mode 100644 index 0000000..694d3d0 --- /dev/null +++ b/vendor/markbaker/complex/classes/src/functions/log10.php @@ -0,0 +1,32 @@ +getReal() == 0.0) && ($complex->getImaginary() == 0.0)) { + throw new \InvalidArgumentException(); + } elseif (($complex->getReal() > 0.0) && ($complex->getImaginary() == 0.0)) { + return new Complex(\log10($complex->getReal()), 0.0, $complex->getSuffix()); + } + + return ln($complex) + ->multiply(\log10(Complex::EULER)); +} diff --git a/vendor/markbaker/complex/classes/src/functions/log2.php b/vendor/markbaker/complex/classes/src/functions/log2.php new file mode 100644 index 0000000..081f2c4 --- /dev/null +++ b/vendor/markbaker/complex/classes/src/functions/log2.php @@ -0,0 +1,32 @@ +getReal() == 0.0) && ($complex->getImaginary() == 0.0)) { + throw new \InvalidArgumentException(); + } elseif (($complex->getReal() > 0.0) && ($complex->getImaginary() == 0.0)) { + return new Complex(\log($complex->getReal(), 2), 0.0, $complex->getSuffix()); + } + + return ln($complex) + ->multiply(\log(Complex::EULER, 2)); +} diff --git a/vendor/markbaker/complex/classes/src/functions/negative.php b/vendor/markbaker/complex/classes/src/functions/negative.php new file mode 100644 index 0000000..dbd1192 --- /dev/null +++ b/vendor/markbaker/complex/classes/src/functions/negative.php @@ -0,0 +1,31 @@ +getReal(), + -1 * $complex->getImaginary(), + $complex->getSuffix() + ); +} diff --git a/vendor/markbaker/complex/classes/src/functions/pow.php b/vendor/markbaker/complex/classes/src/functions/pow.php new file mode 100644 index 0000000..18ee269 --- /dev/null +++ b/vendor/markbaker/complex/classes/src/functions/pow.php @@ -0,0 +1,40 @@ +getImaginary() == 0.0 && $complex->getReal() >= 0.0) { + return new Complex(\pow($complex->getReal(), $power)); + } + + $rValue = \sqrt(($complex->getReal() * $complex->getReal()) + ($complex->getImaginary() * $complex->getImaginary())); + $rPower = \pow($rValue, $power); + $theta = $complex->argument() * $power; + if ($theta == 0) { + return new Complex(1); + } + + return new Complex($rPower * \cos($theta), $rPower * \sin($theta), $complex->getSuffix()); +} diff --git a/vendor/markbaker/complex/classes/src/functions/rho.php b/vendor/markbaker/complex/classes/src/functions/rho.php new file mode 100644 index 0000000..750f3f9 --- /dev/null +++ b/vendor/markbaker/complex/classes/src/functions/rho.php @@ -0,0 +1,28 @@ +getReal() * $complex->getReal()) + + ($complex->getImaginary() * $complex->getImaginary()) + ); +} diff --git a/vendor/markbaker/complex/classes/src/functions/sec.php b/vendor/markbaker/complex/classes/src/functions/sec.php new file mode 100644 index 0000000..7dd43ea --- /dev/null +++ b/vendor/markbaker/complex/classes/src/functions/sec.php @@ -0,0 +1,25 @@ +isReal()) { + return new Complex(\sin($complex->getReal())); + } + + return new Complex( + \sin($complex->getReal()) * \cosh($complex->getImaginary()), + \cos($complex->getReal()) * \sinh($complex->getImaginary()), + $complex->getSuffix() + ); +} diff --git a/vendor/markbaker/complex/classes/src/functions/sinh.php b/vendor/markbaker/complex/classes/src/functions/sinh.php new file mode 100644 index 0000000..4c0f650 --- /dev/null +++ b/vendor/markbaker/complex/classes/src/functions/sinh.php @@ -0,0 +1,32 @@ +isReal()) { + return new Complex(\sinh($complex->getReal())); + } + + return new Complex( + \sinh($complex->getReal()) * \cos($complex->getImaginary()), + \cosh($complex->getReal()) * \sin($complex->getImaginary()), + $complex->getSuffix() + ); +} diff --git a/vendor/markbaker/complex/classes/src/functions/sqrt.php b/vendor/markbaker/complex/classes/src/functions/sqrt.php new file mode 100644 index 0000000..9c171b8 --- /dev/null +++ b/vendor/markbaker/complex/classes/src/functions/sqrt.php @@ -0,0 +1,29 @@ +getSuffix()); +} diff --git a/vendor/markbaker/complex/classes/src/functions/tan.php b/vendor/markbaker/complex/classes/src/functions/tan.php new file mode 100644 index 0000000..014d798 --- /dev/null +++ b/vendor/markbaker/complex/classes/src/functions/tan.php @@ -0,0 +1,40 @@ +isReal()) { + return new Complex(\tan($complex->getReal())); + } + + $real = $complex->getReal(); + $imaginary = $complex->getImaginary(); + $divisor = 1 + \pow(\tan($real), 2) * \pow(\tanh($imaginary), 2); + if ($divisor == 0.0) { + throw new \InvalidArgumentException('Division by zero'); + } + + return new Complex( + \pow(sech($imaginary)->getReal(), 2) * \tan($real) / $divisor, + \pow(sec($real)->getReal(), 2) * \tanh($imaginary) / $divisor, + $complex->getSuffix() + ); +} diff --git a/vendor/markbaker/complex/classes/src/functions/tanh.php b/vendor/markbaker/complex/classes/src/functions/tanh.php new file mode 100644 index 0000000..028741d --- /dev/null +++ b/vendor/markbaker/complex/classes/src/functions/tanh.php @@ -0,0 +1,35 @@ +getReal(); + $imaginary = $complex->getImaginary(); + $divisor = \cos($imaginary) * \cos($imaginary) + \sinh($real) * \sinh($real); + if ($divisor == 0.0) { + throw new \InvalidArgumentException('Division by zero'); + } + + return new Complex( + \sinh($real) * \cosh($real) / $divisor, + 0.5 * \sin(2 * $imaginary) / $divisor, + $complex->getSuffix() + ); +} diff --git a/vendor/markbaker/complex/classes/src/functions/theta.php b/vendor/markbaker/complex/classes/src/functions/theta.php new file mode 100644 index 0000000..d12866c --- /dev/null +++ b/vendor/markbaker/complex/classes/src/functions/theta.php @@ -0,0 +1,38 @@ +getReal() == 0.0) { + if ($complex->isReal()) { + return 0.0; + } elseif ($complex->getImaginary() < 0.0) { + return M_PI / -2; + } + return M_PI / 2; + } elseif ($complex->getReal() > 0.0) { + return \atan($complex->getImaginary() / $complex->getReal()); + } elseif ($complex->getImaginary() < 0.0) { + return -(M_PI - \atan(\abs($complex->getImaginary()) / \abs($complex->getReal()))); + } + + return M_PI - \atan($complex->getImaginary() / \abs($complex->getReal())); +} diff --git a/vendor/markbaker/complex/classes/src/operations/add.php b/vendor/markbaker/complex/classes/src/operations/add.php new file mode 100644 index 0000000..10bd42f --- /dev/null +++ b/vendor/markbaker/complex/classes/src/operations/add.php @@ -0,0 +1,46 @@ +isComplex() && $complex->isComplex() && + $result->getSuffix() !== $complex->getSuffix()) { + throw new Exception('Suffix Mismatch'); + } + + $real = $result->getReal() + $complex->getReal(); + $imaginary = $result->getImaginary() + $complex->getImaginary(); + + $result = new Complex( + $real, + $imaginary, + ($imaginary == 0.0) ? null : max($result->getSuffix(), $complex->getSuffix()) + ); + } + + return $result; +} diff --git a/vendor/markbaker/complex/classes/src/operations/divideby.php b/vendor/markbaker/complex/classes/src/operations/divideby.php new file mode 100644 index 0000000..089e0ef --- /dev/null +++ b/vendor/markbaker/complex/classes/src/operations/divideby.php @@ -0,0 +1,56 @@ +isComplex() && $complex->isComplex() && + $result->getSuffix() !== $complex->getSuffix()) { + throw new Exception('Suffix Mismatch'); + } + if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { + throw new \InvalidArgumentException('Division by zero'); + } + + $delta1 = ($result->getReal() * $complex->getReal()) + + ($result->getImaginary() * $complex->getImaginary()); + $delta2 = ($result->getImaginary() * $complex->getReal()) - + ($result->getReal() * $complex->getImaginary()); + $delta3 = ($complex->getReal() * $complex->getReal()) + + ($complex->getImaginary() * $complex->getImaginary()); + + $real = $delta1 / $delta3; + $imaginary = $delta2 / $delta3; + + $result = new Complex( + $real, + $imaginary, + ($imaginary == 0.0) ? null : max($result->getSuffix(), $complex->getSuffix()) + ); + } + + return $result; +} diff --git a/vendor/markbaker/complex/classes/src/operations/divideinto.php b/vendor/markbaker/complex/classes/src/operations/divideinto.php new file mode 100644 index 0000000..3dfe085 --- /dev/null +++ b/vendor/markbaker/complex/classes/src/operations/divideinto.php @@ -0,0 +1,56 @@ +isComplex() && $complex->isComplex() && + $result->getSuffix() !== $complex->getSuffix()) { + throw new Exception('Suffix Mismatch'); + } + if ($result->getReal() == 0.0 && $result->getImaginary() == 0.0) { + throw new \InvalidArgumentException('Division by zero'); + } + + $delta1 = ($complex->getReal() * $result->getReal()) + + ($complex->getImaginary() * $result->getImaginary()); + $delta2 = ($complex->getImaginary() * $result->getReal()) - + ($complex->getReal() * $result->getImaginary()); + $delta3 = ($result->getReal() * $result->getReal()) + + ($result->getImaginary() * $result->getImaginary()); + + $real = $delta1 / $delta3; + $imaginary = $delta2 / $delta3; + + $result = new Complex( + $real, + $imaginary, + ($imaginary == 0.0) ? null : max($result->getSuffix(), $complex->getSuffix()) + ); + } + + return $result; +} diff --git a/vendor/markbaker/complex/classes/src/operations/multiply.php b/vendor/markbaker/complex/classes/src/operations/multiply.php new file mode 100644 index 0000000..bf2473e --- /dev/null +++ b/vendor/markbaker/complex/classes/src/operations/multiply.php @@ -0,0 +1,48 @@ +isComplex() && $complex->isComplex() && + $result->getSuffix() !== $complex->getSuffix()) { + throw new Exception('Suffix Mismatch'); + } + + $real = ($result->getReal() * $complex->getReal()) - + ($result->getImaginary() * $complex->getImaginary()); + $imaginary = ($result->getReal() * $complex->getImaginary()) + + ($result->getImaginary() * $complex->getReal()); + + $result = new Complex( + $real, + $imaginary, + ($imaginary == 0.0) ? null : max($result->getSuffix(), $complex->getSuffix()) + ); + } + + return $result; +} diff --git a/vendor/markbaker/complex/classes/src/operations/subtract.php b/vendor/markbaker/complex/classes/src/operations/subtract.php new file mode 100644 index 0000000..075ef44 --- /dev/null +++ b/vendor/markbaker/complex/classes/src/operations/subtract.php @@ -0,0 +1,46 @@ +isComplex() && $complex->isComplex() && + $result->getSuffix() !== $complex->getSuffix()) { + throw new Exception('Suffix Mismatch'); + } + + $real = $result->getReal() - $complex->getReal(); + $imaginary = $result->getImaginary() - $complex->getImaginary(); + + $result = new Complex( + $real, + $imaginary, + ($imaginary == 0.0) ? null : max($result->getSuffix(), $complex->getSuffix()) + ); + } + + return $result; +} diff --git a/vendor/markbaker/complex/composer.json b/vendor/markbaker/complex/composer.json new file mode 100644 index 0000000..e8a507f --- /dev/null +++ b/vendor/markbaker/complex/composer.json @@ -0,0 +1,94 @@ +{ + "name": "markbaker/complex", + "type": "library", + "description": "PHP Class for working with complex numbers", + "keywords": ["complex", "mathematics"], + "homepage": "https://github.com/MarkBaker/PHPComplex", + "license": "MIT", + "authors": [ + { + "name": "Mark Baker", + "email": "mark@lange.demon.co.uk" + } + ], + "require": { + "php": "^5.6.0|^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|^5.0|^6.0|^7.0", + "phpdocumentor/phpdocumentor":"2.*", + "phpmd/phpmd": "2.*", + "sebastian/phpcpd": "2.*", + "phploc/phploc": "^4.0|^5.0|^6.0|^7.0", + "squizlabs/php_codesniffer": "^3.4.0", + "phpcompatibility/php-compatibility": "^9.0", + "dealerdirect/phpcodesniffer-composer-installer": "^0.5.0" + }, + "autoload": { + "psr-4": { + "Complex\\": "classes/src/" + }, + "files": [ + "classes/src/functions/abs.php", + "classes/src/functions/acos.php", + "classes/src/functions/acosh.php", + "classes/src/functions/acot.php", + "classes/src/functions/acoth.php", + "classes/src/functions/acsc.php", + "classes/src/functions/acsch.php", + "classes/src/functions/argument.php", + "classes/src/functions/asec.php", + "classes/src/functions/asech.php", + "classes/src/functions/asin.php", + "classes/src/functions/asinh.php", + "classes/src/functions/atan.php", + "classes/src/functions/atanh.php", + "classes/src/functions/conjugate.php", + "classes/src/functions/cos.php", + "classes/src/functions/cosh.php", + "classes/src/functions/cot.php", + "classes/src/functions/coth.php", + "classes/src/functions/csc.php", + "classes/src/functions/csch.php", + "classes/src/functions/exp.php", + "classes/src/functions/inverse.php", + "classes/src/functions/ln.php", + "classes/src/functions/log2.php", + "classes/src/functions/log10.php", + "classes/src/functions/negative.php", + "classes/src/functions/pow.php", + "classes/src/functions/rho.php", + "classes/src/functions/sec.php", + "classes/src/functions/sech.php", + "classes/src/functions/sin.php", + "classes/src/functions/sinh.php", + "classes/src/functions/sqrt.php", + "classes/src/functions/tan.php", + "classes/src/functions/tanh.php", + "classes/src/functions/theta.php", + "classes/src/operations/add.php", + "classes/src/operations/subtract.php", + "classes/src/operations/multiply.php", + "classes/src/operations/divideby.php", + "classes/src/operations/divideinto.php" + ] + }, + "scripts": { + "style": [ + "phpcs --report-width=200 --report=summary,full -n" + ], + "mess": [ + "phpmd classes/src/ xml codesize,unusedcode,design,naming -n" + ], + "lines": [ + "phploc classes/src/ -n" + ], + "cpd": [ + "phpcpd classes/src/ -n" + ], + "versions": [ + "phpcs --report-width=200 --report=summary,full classes/src/ --standard=PHPCompatibility --runtime-set testVersion 5.6- -n" + ] + }, + "minimum-stability": "dev" +} \ No newline at end of file diff --git a/vendor/markbaker/complex/examples/complexTest.php b/vendor/markbaker/complex/examples/complexTest.php new file mode 100644 index 0000000..7dafd8a --- /dev/null +++ b/vendor/markbaker/complex/examples/complexTest.php @@ -0,0 +1,154 @@ +add(456); +echo $x, PHP_EOL; + +$x = new Complex(123.456); +$x->add(789.012); +echo $x, PHP_EOL; + +$x = new Complex(123.456, 78.90); +$x->add(new Complex(-987.654, -32.1)); +echo $x, PHP_EOL; + +$x = new Complex(123.456, 78.90); +$x->add(-987.654); +echo $x, PHP_EOL; + +$x = new Complex(-987.654, -32.1); +$x->add(new Complex(0, 1)); +echo $x, PHP_EOL; + +$x = new Complex(-987.654, -32.1); +$x->add(new Complex(0, -1)); +echo $x, PHP_EOL; + + +echo PHP_EOL, 'Subtract', PHP_EOL; + +$x = new Complex(123); +$x->subtract(456); +echo $x, PHP_EOL; + +$x = new Complex(123.456); +$x->subtract(789.012); +echo $x, PHP_EOL; + +$x = new Complex(123.456, 78.90); +$x->subtract(new Complex(-987.654, -32.1)); +echo $x, PHP_EOL; + +$x = new Complex(123.456, 78.90); +$x->subtract(-987.654); +echo $x, PHP_EOL; + +$x = new Complex(-987.654, -32.1); +$x->subtract(new Complex(0, 1)); +echo $x, PHP_EOL; + +$x = new Complex(-987.654, -32.1); +$x->subtract(new Complex(0, -1)); +echo $x, PHP_EOL; + + +echo PHP_EOL, 'Multiply', PHP_EOL; + +$x = new Complex(123); +$x->multiply(456); +echo $x, PHP_EOL; + +$x = new Complex(123.456); +$x->multiply(789.012); +echo $x, PHP_EOL; + +$x = new Complex(123.456, 78.90); +$x->multiply(new Complex(-987.654, -32.1)); +echo $x, PHP_EOL; + +$x = new Complex(123.456, 78.90); +$x->multiply(-987.654); +echo $x, PHP_EOL; + +$x = new Complex(-987.654, -32.1); +$x->multiply(new Complex(0, 1)); +echo $x, PHP_EOL; + +$x = new Complex(-987.654, -32.1); +$x->multiply(new Complex(0, -1)); +echo $x, PHP_EOL; + + +echo PHP_EOL, 'Divide By', PHP_EOL; + +$x = new Complex(123); +$x->divideBy(456); +echo $x, PHP_EOL; + +$x = new Complex(123.456); +$x->divideBy(789.012); +echo $x, PHP_EOL; + +$x = new Complex(123.456, 78.90); +$x->divideBy(new Complex(-987.654, -32.1)); +echo $x, PHP_EOL; + +$x = new Complex(123.456, 78.90); +$x->divideBy(-987.654); +echo $x, PHP_EOL; + +$x = new Complex(-987.654, -32.1); +$x->divideBy(new Complex(0, 1)); +echo $x, PHP_EOL; + +$x = new Complex(-987.654, -32.1); +$x->divideBy(new Complex(0, -1)); +echo $x, PHP_EOL; + + +echo PHP_EOL, 'Divide Into', PHP_EOL; + +$x = new Complex(123); +$x->divideInto(456); +echo $x, PHP_EOL; + +$x = new Complex(123.456); +$x->divideInto(789.012); +echo $x, PHP_EOL; + +$x = new Complex(123.456, 78.90); +$x->divideInto(new Complex(-987.654, -32.1)); +echo $x, PHP_EOL; + +$x = new Complex(123.456, 78.90); +$x->divideInto(-987.654); +echo $x, PHP_EOL; + +$x = new Complex(-987.654, -32.1); +$x->divideInto(new Complex(0, 1)); +echo $x, PHP_EOL; + +$x = new Complex(-987.654, -32.1); +$x->divideInto(new Complex(0, -1)); +echo $x, PHP_EOL; diff --git a/vendor/markbaker/complex/examples/testFunctions.php b/vendor/markbaker/complex/examples/testFunctions.php new file mode 100644 index 0000000..4d5ed73 --- /dev/null +++ b/vendor/markbaker/complex/examples/testFunctions.php @@ -0,0 +1,52 @@ +getMessage(), PHP_EOL; + } + } + echo PHP_EOL; + } +} diff --git a/vendor/markbaker/complex/examples/testOperations.php b/vendor/markbaker/complex/examples/testOperations.php new file mode 100644 index 0000000..f791263 --- /dev/null +++ b/vendor/markbaker/complex/examples/testOperations.php @@ -0,0 +1,34 @@ + ', $result, PHP_EOL; + +echo PHP_EOL; + +echo 'Subtraction', PHP_EOL; + +$result = \Complex\subtract(...$values); +echo '=> ', $result, PHP_EOL; + +echo PHP_EOL; + +echo 'Multiplication', PHP_EOL; + +$result = \Complex\multiply(...$values); +echo '=> ', $result, PHP_EOL; diff --git a/vendor/markbaker/complex/license.md b/vendor/markbaker/complex/license.md new file mode 100644 index 0000000..5b4b156 --- /dev/null +++ b/vendor/markbaker/complex/license.md @@ -0,0 +1,25 @@ +The MIT License (MIT) +===================== + +Copyright © `2017` `Mark Baker` + +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/markbaker/matrix/.github/workflows/main.yaml b/vendor/markbaker/matrix/.github/workflows/main.yaml new file mode 100644 index 0000000..e7a24bb --- /dev/null +++ b/vendor/markbaker/matrix/.github/workflows/main.yaml @@ -0,0 +1,112 @@ +name: main +on: [ push, pull_request ] +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + php-version: + - '5.6' + - '7.0' + - '7.1' + - '7.2' + - '7.3' + - '7.4' + + name: PHP ${{ matrix.php-version }} + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP, with composer and extensions + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + extensions: ctype, dom, gd, iconv, fileinfo, libxml, mbstring, simplexml, xml, xmlreader, xmlwriter, zip, zlib + coverage: none + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache composer dependencies + uses: actions/cache@v2 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies + run: composer install --no-progress --prefer-dist --optimize-autoloader ${{ steps.composer-lock.outputs.flags }} + + - name: Setup problem matchers for PHP + run: echo "::add-matcher::${{ runner.tool_cache }}/php.json" + + - name: Setup problem matchers for PHPUnit + run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" + + - name: Test with PHPUnit + run: ./vendor/bin/phpunit + + phpcs: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP, with composer and extensions + uses: shivammathur/setup-php@v2 + with: + php-version: 7.4 + extensions: ctype, dom, gd, iconv, fileinfo, libxml, mbstring, simplexml, xml, xmlreader, xmlwriter, zip, zlib + coverage: none + tools: cs2pr + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache composer dependencies + uses: actions/cache@v2 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies + run: composer install --no-progress --prefer-dist --optimize-autoloader + + - name: Code style with PHP_CodeSniffer + run: ./vendor/bin/phpcs -q --report=checkstyle | cs2pr --graceful-warnings --colorize + + coverage: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP, with composer and extensions + uses: shivammathur/setup-php@v2 + with: + php-version: 7.4 + extensions: ctype, dom, gd, iconv, fileinfo, libxml, mbstring, simplexml, xml, xmlreader, xmlwriter, zip, zlib + coverage: pcov + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache composer dependencies + uses: actions/cache@v2 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies + run: composer install --no-progress --prefer-dist --optimize-autoloader + + - name: Coverage + run: | + ./vendor/bin/phpunit --coverage-text diff --git a/vendor/markbaker/matrix/README.md b/vendor/markbaker/matrix/README.md new file mode 100644 index 0000000..60cb75f --- /dev/null +++ b/vendor/markbaker/matrix/README.md @@ -0,0 +1,207 @@ +PHPMatrix +========== + +--- + +PHP Class for handling Matrices + +[![Build Status](https://travis-ci.org/MarkBaker/PHPMatrix.png?branch=1.2)](http://travis-ci.org/MarkBaker/PHPMatrix) + +[![Matrix Transform](https://imgs.xkcd.com/comics/matrix_transform.png)](https://xkcd.com/184/) + +Matrix Transform + +--- + +This library currently provides the following operations: + +- addition +- direct sum +- subtraction +- multiplication +- division (using [A].[B]-1) + - division by + - division into + +together with functions for + +- adjoint +- antidiagonal +- cofactors +- determinant +- diagonal +- identity +- inverse +- minors +- trace +- transpose +- solve + + Given Matrices A and B, calculate X for A.X = B + +and classes for + +- Decomposition + - LU Decomposition with partial row pivoting, + + such that [P].[A] = [L].[U] and [A] = [P]|.[L].[U] + - QR Decomposition + + such that [A] = [Q].[R] + +## TO DO + +- power() function +- Decomposition + - Cholesky Decomposition + - EigenValue Decomposition + - EigenValues + - EigenVectors + +--- + +# Usage + +To create a new Matrix object, provide an array as the constructor argument + +```php +$grid = [ + [16, 3, 2, 13], + [ 5, 10, 11, 8], + [ 9, 6, 7, 12], + [ 4, 15, 14, 1], +]; + +$matrix = new Matrix\Matrix($grid); +``` +The `Builder` class provides helper methods for creating specific matrices, specifically an identity matrix of a specified size; or a matrix of a specified dimensions, with every cell containing a set value. +```php +$matrix = Matrix\Builder::createFilledMatrix(1, 5, 3); +``` +Will create a matrix of 5 rows and 3 columns, filled with a `1` in every cell; while +```php +$matrix = Matrix\Builder::createIdentityMatrix(3); +``` +will create a 3x3 identity matrix. + + +Matrix objects are immutable: whenever you call a method or pass a grid to a function that returns a matrix value, a new Matrix object will be returned, and the original will remain unchanged. This also allows you to chain multiple methods as you would for a fluent interface (as long as they are methods that will return a Matrix result). + +## Performing Mathematical Operations + +To perform mathematical operations with Matrices, you can call the appropriate method against a matrix value, passing other values as arguments + +```php +$matrix1 = new Matrix\Matrix([ + [2, 7, 6], + [9, 5, 1], + [4, 3, 8], +]); +$matrix2 = new Matrix\Matrix([ + [1, 2, 3], + [4, 5, 6], + [7, 8, 9], +]); + +var_dump($matrix1->multiply($matrix2)->toArray()); +``` +or pass all values to the appropriate function +```php +$matrix1 = new Matrix\Matrix([ + [2, 7, 6], + [9, 5, 1], + [4, 3, 8], +]); +$matrix2 = new Matrix\Matrix([ + [1, 2, 3], + [4, 5, 6], + [7, 8, 9], +]); + +var_dump(Matrix\multiply($matrix1, $matrix2)->toArray()); +``` +You can pass in the arguments as Matrix objects, or as arrays. + +If you want to perform the same operation against multiple values (e.g. to add three or more matrices), then you can pass multiple arguments to any of the operations. + +## Using functions + +When calling any of the available functions for a matrix value, you can either call the relevant method for the Matrix object +```php +$grid = [ + [16, 3, 2, 13], + [ 5, 10, 11, 8], + [ 9, 6, 7, 12], + [ 4, 15, 14, 1], +]; + +$matrix = new Matrix\Matrix($grid); + +echo $matrix->trace(); +``` +or you can call the function as you would in procedural code, passing the Matrix object as an argument +```php +$grid = [ + [16, 3, 2, 13], + [ 5, 10, 11, 8], + [ 9, 6, 7, 12], + [ 4, 15, 14, 1], +]; + +$matrix = new Matrix\Matrix($grid); +echo Matrix\trace($matrix); +``` +When called procedurally using the function, you can pass in the argument as a Matrix object, or as an array. +```php +$grid = [ + [16, 3, 2, 13], + [ 5, 10, 11, 8], + [ 9, 6, 7, 12], + [ 4, 15, 14, 1], +]; + +echo Matrix\trace($grid); +``` +As an alternative, it is also possible to call the method directly from the `Functions` class. +```php +$grid = [ + [16, 3, 2, 13], + [ 5, 10, 11, 8], + [ 9, 6, 7, 12], + [ 4, 15, 14, 1], +]; + +$matrix = new Matrix\Matrix($grid); +echo Matrix\Functions::trace($matrix); +``` +Used this way, methods must be called statically, and the argument must be the Matrix object, and cannot be an array. + +## Decomposition + +The library also provides classes for matrix decomposition. You can access these using +```php +$grid = [ + [1, 2], + [3, 4], +]; + +$matrix = new Matrix\Matrix($grid); + +$decomposition = new Matrix\Decomposition\QR($matrix); +$Q = $decomposition->getQ(); +$R = $decomposition->getR(); +``` + +or alternatively us the `Decomposition` factory, identifying which form of decomposition you want to use +```php +$grid = [ + [1, 2], + [3, 4], +]; + +$matrix = new Matrix\Matrix($grid); + +$decomposition = Matrix\Decomposition\Decomposition::decomposition(Matrix\Decomposition\Decomposition::QR, $matrix); +$Q = $decomposition->getQ(); +$R = $decomposition->getR(); +``` diff --git a/vendor/markbaker/matrix/buildPhar.php b/vendor/markbaker/matrix/buildPhar.php new file mode 100644 index 0000000..e1b8f96 --- /dev/null +++ b/vendor/markbaker/matrix/buildPhar.php @@ -0,0 +1,62 @@ + 'Mark Baker ', + 'Description' => 'PHP Class for working with Matrix numbers', + 'Copyright' => 'Mark Baker (c) 2013-' . date('Y'), + 'Timestamp' => time(), + 'Version' => '0.1.0', + 'Date' => date('Y-m-d') +); + +// cleanup +if (file_exists($pharName)) { + echo "Removed: {$pharName}\n"; + unlink($pharName); +} + +echo "Building phar file...\n"; + +// the phar object +$phar = new Phar($pharName, null, 'Matrix'); +$phar->buildFromDirectory($sourceDir); +$phar->setStub( +<<<'EOT' +getMessage()); + exit(1); + } + + include 'phar://functions/sqrt.php'; + + __HALT_COMPILER(); +EOT +); +$phar->setMetadata($metaData); +$phar->compressFiles(Phar::GZ); + +echo "Complete.\n"; + +exit(); diff --git a/vendor/markbaker/matrix/classes/src/Builder.php b/vendor/markbaker/matrix/classes/src/Builder.php new file mode 100644 index 0000000..6bc334a --- /dev/null +++ b/vendor/markbaker/matrix/classes/src/Builder.php @@ -0,0 +1,70 @@ +toArray(); + + for ($x = 0; $x < $dimensions; ++$x) { + $grid[$x][$x] = 1; + } + + return new Matrix($grid); + } +} diff --git a/vendor/markbaker/matrix/classes/src/Decomposition/Decomposition.php b/vendor/markbaker/matrix/classes/src/Decomposition/Decomposition.php new file mode 100644 index 0000000..f014448 --- /dev/null +++ b/vendor/markbaker/matrix/classes/src/Decomposition/Decomposition.php @@ -0,0 +1,27 @@ +luMatrix = $matrix->toArray(); + $this->rows = $matrix->rows; + $this->columns = $matrix->columns; + + $this->buildPivot(); + } + + /** + * Get lower triangular factor. + * + * @return Matrix Lower triangular factor + */ + public function getL() + { + $lower = []; + + $columns = min($this->rows, $this->columns); + for ($row = 0; $row < $this->rows; ++$row) { + for ($column = 0; $column < $columns; ++$column) { + if ($row > $column) { + $lower[$row][$column] = $this->luMatrix[$row][$column]; + } elseif ($row === $column) { + $lower[$row][$column] = 1.0; + } else { + $lower[$row][$column] = 0.0; + } + } + } + + return new Matrix($lower); + } + + /** + * Get upper triangular factor. + * + * @return Matrix Upper triangular factor + */ + public function getU() + { + $upper = []; + + $rows = min($this->rows, $this->columns); + for ($row = 0; $row < $rows; ++$row) { + for ($column = 0; $column < $this->columns; ++$column) { + if ($row <= $column) { + $upper[$row][$column] = $this->luMatrix[$row][$column]; + } else { + $upper[$row][$column] = 0.0; + } + } + } + + return new Matrix($upper); + } + + /** + * Return pivot permutation vector. + * + * @return Matrix Pivot matrix + */ + public function getP() + { + $pMatrix = []; + + $pivots = $this->pivot; + $pivotCount = count($pivots); + foreach ($pivots as $row => $pivot) { + $pMatrix[$row] = array_fill(0, $pivotCount, 0); + $pMatrix[$row][$pivot] = 1; + } + + return new Matrix($pMatrix); + } + + /** + * Return pivot permutation vector. + * + * @return array Pivot vector + */ + public function getPivot() + { + return $this->pivot; + } + + /** + * Is the matrix nonsingular? + * + * @return bool true if U, and hence A, is nonsingular + */ + public function isNonsingular() + { + for ($diagonal = 0; $diagonal < $this->columns; ++$diagonal) { + if ($this->luMatrix[$diagonal][$diagonal] === 0.0) { + return false; + } + } + + return true; + } + + private function buildPivot() + { + for ($row = 0; $row < $this->rows; ++$row) { + $this->pivot[$row] = $row; + } + + for ($column = 0; $column < $this->columns; ++$column) { + $luColumn = $this->localisedReferenceColumn($column); + + $this->applyTransformations($column, $luColumn); + + $pivot = $this->findPivot($column, $luColumn); + if ($pivot !== $column) { + $this->pivotExchange($pivot, $column); + } + + $this->computeMultipliers($column); + + unset($luColumn); + } + } + + private function localisedReferenceColumn($column) + { + $luColumn = []; + + for ($row = 0; $row < $this->rows; ++$row) { + $luColumn[$row] = &$this->luMatrix[$row][$column]; + } + + return $luColumn; + } + + private function applyTransformations($column, array $luColumn) + { + for ($row = 0; $row < $this->rows; ++$row) { + $luRow = $this->luMatrix[$row]; + // Most of the time is spent in the following dot product. + $kmax = min($row, $column); + $sValue = 0.0; + for ($kValue = 0; $kValue < $kmax; ++$kValue) { + $sValue += $luRow[$kValue] * $luColumn[$kValue]; + } + $luRow[$column] = $luColumn[$row] -= $sValue; + } + } + + private function findPivot($column, array $luColumn) + { + $pivot = $column; + for ($row = $column + 1; $row < $this->rows; ++$row) { + if (abs($luColumn[$row]) > abs($luColumn[$pivot])) { + $pivot = $row; + } + } + + return $pivot; + } + + private function pivotExchange($pivot, $column) + { + for ($kValue = 0; $kValue < $this->columns; ++$kValue) { + $tValue = $this->luMatrix[$pivot][$kValue]; + $this->luMatrix[$pivot][$kValue] = $this->luMatrix[$column][$kValue]; + $this->luMatrix[$column][$kValue] = $tValue; + } + + $lValue = $this->pivot[$pivot]; + $this->pivot[$pivot] = $this->pivot[$column]; + $this->pivot[$column] = $lValue; + } + + private function computeMultipliers($diagonal) + { + if (($diagonal < $this->rows) && ($this->luMatrix[$diagonal][$diagonal] != 0.0)) { + for ($row = $diagonal + 1; $row < $this->rows; ++$row) { + $this->luMatrix[$row][$diagonal] /= $this->luMatrix[$diagonal][$diagonal]; + } + } + } + + private function pivotB(Matrix $B) + { + $X = []; + foreach ($this->pivot as $rowId) { + $row = $B->getRows($rowId + 1)->toArray(); + $X[] = array_pop($row); + } + + return $X; + } + + /** + * Solve A*X = B. + * + * @param Matrix $B a Matrix with as many rows as A and any number of columns + * + * @throws Exception + * + * @return Matrix X so that L*U*X = B(piv,:) + */ + public function solve(Matrix $B) + { + if ($B->rows !== $this->rows) { + throw new Exception('Matrix row dimensions are not equal'); + } + + if ($this->rows !== $this->columns) { + throw new Exception('LU solve() only works on square matrices'); + } + + if (!$this->isNonsingular()) { + throw new Exception('Can only perform operation on singular matrix'); + } + + // Copy right hand side with pivoting + $nx = $B->columns; + $X = $this->pivotB($B); + + // Solve L*Y = B(piv,:) + for ($k = 0; $k < $this->columns; ++$k) { + for ($i = $k + 1; $i < $this->columns; ++$i) { + for ($j = 0; $j < $nx; ++$j) { + $X[$i][$j] -= $X[$k][$j] * $this->luMatrix[$i][$k]; + } + } + } + + // Solve U*X = Y; + for ($k = $this->columns - 1; $k >= 0; --$k) { + for ($j = 0; $j < $nx; ++$j) { + $X[$k][$j] /= $this->luMatrix[$k][$k]; + } + for ($i = 0; $i < $k; ++$i) { + for ($j = 0; $j < $nx; ++$j) { + $X[$i][$j] -= $X[$k][$j] * $this->luMatrix[$i][$k]; + } + } + } + + return new Matrix($X); + } +} diff --git a/vendor/markbaker/matrix/classes/src/Decomposition/QR.php b/vendor/markbaker/matrix/classes/src/Decomposition/QR.php new file mode 100644 index 0000000..433281b --- /dev/null +++ b/vendor/markbaker/matrix/classes/src/Decomposition/QR.php @@ -0,0 +1,194 @@ +qrMatrix = $matrix->toArray(); + $this->rows = $matrix->rows; + $this->columns = $matrix->columns; + + $this->decompose(); + } + + public function getHouseholdVectors() + { + $householdVectors = []; + for ($row = 0; $row < $this->rows; ++$row) { + for ($column = 0; $column < $this->columns; ++$column) { + if ($row >= $column) { + $householdVectors[$row][$column] = $this->qrMatrix[$row][$column]; + } else { + $householdVectors[$row][$column] = 0.0; + } + } + } + + return new Matrix($householdVectors); + } + + public function getQ() + { + $qGrid = []; + + $rowCount = $this->rows; + for ($k = $this->columns - 1; $k >= 0; --$k) { + for ($i = 0; $i < $this->rows; ++$i) { + $qGrid[$i][$k] = 0.0; + } + $qGrid[$k][$k] = 1.0; + if ($this->columns > $this->rows) { + $qGrid = array_slice($qGrid, 0, $this->rows); + } + + for ($j = $k; $j < $this->columns; ++$j) { + if (isset($this->qrMatrix[$k], $this->qrMatrix[$k][$k]) && $this->qrMatrix[$k][$k] != 0.0) { + $s = 0.0; + for ($i = $k; $i < $this->rows; ++$i) { + $s += $this->qrMatrix[$i][$k] * $qGrid[$i][$j]; + } + $s = -$s / $this->qrMatrix[$k][$k]; + for ($i = $k; $i < $this->rows; ++$i) { + $qGrid[$i][$j] += $s * $this->qrMatrix[$i][$k]; + } + } + } + } + + array_walk( + $qGrid, + function (&$row) use ($rowCount) { + $row = array_reverse($row); + $row = array_slice($row, 0, $rowCount); + } + ); + + return new Matrix($qGrid); + } + + public function getR() + { + $rGrid = []; + + for ($row = 0; $row < $this->columns; ++$row) { + for ($column = 0; $column < $this->columns; ++$column) { + if ($row < $column) { + $rGrid[$row][$column] = isset($this->qrMatrix[$row][$column]) ? $this->qrMatrix[$row][$column] : 0.0; + } elseif ($row === $column) { + $rGrid[$row][$column] = isset($this->rDiagonal[$row]) ? $this->rDiagonal[$row] : 0.0; + } else { + $rGrid[$row][$column] = 0.0; + } + } + } + + if ($this->columns > $this->rows) { + $rGrid = array_slice($rGrid, 0, $this->rows); + } + + return new Matrix($rGrid); + } + + private function hypo($a, $b) + { + if (abs($a) > abs($b)) { + $r = $b / $a; + $r = abs($a) * sqrt(1 + $r * $r); + } elseif ($b != 0.0) { + $r = $a / $b; + $r = abs($b) * sqrt(1 + $r * $r); + } else { + $r = 0.0; + } + + return $r; + } + + /** + * QR Decomposition computed by Householder reflections. + */ + private function decompose() + { + for ($k = 0; $k < $this->columns; ++$k) { + // Compute 2-norm of k-th column without under/overflow. + $norm = 0.0; + for ($i = $k; $i < $this->rows; ++$i) { + $norm = $this->hypo($norm, $this->qrMatrix[$i][$k]); + } + if ($norm != 0.0) { + // Form k-th Householder vector. + if ($this->qrMatrix[$k][$k] < 0.0) { + $norm = -$norm; + } + for ($i = $k; $i < $this->rows; ++$i) { + $this->qrMatrix[$i][$k] /= $norm; + } + $this->qrMatrix[$k][$k] += 1.0; + // Apply transformation to remaining columns. + for ($j = $k + 1; $j < $this->columns; ++$j) { + $s = 0.0; + for ($i = $k; $i < $this->rows; ++$i) { + $s += $this->qrMatrix[$i][$k] * $this->qrMatrix[$i][$j]; + } + $s = -$s / $this->qrMatrix[$k][$k]; + for ($i = $k; $i < $this->rows; ++$i) { + $this->qrMatrix[$i][$j] += $s * $this->qrMatrix[$i][$k]; + } + } + } + $this->rDiagonal[$k] = -$norm; + } + } + + /** + * @return bool + */ + public function isFullRank() + { + for ($j = 0; $j < $this->columns; ++$j) { + if ($this->rDiagonal[$j] == 0.0) { + return false; + } + } + + return true; + } + + /** + * Least squares solution of A*X = B. + * + * @param Matrix $B a Matrix with as many rows as A and any number of columns + * + * @throws Exception + * + * @return Matrix matrix that minimizes the two norm of Q*R*X-B + */ + public function solve(Matrix $B) + { + if ($B->rows !== $this->rows) { + throw new Exception('Matrix row dimensions are not equal'); + } + + if (!$this->isFullRank()) { + throw new Exception('Can only perform this operation on a full-rank matrix'); + } + + // Compute Y = transpose(Q)*B + $Y = $this->getQ()->transpose() + ->multiply($B); + // Solve R*X = Y; + return $this->getR()->inverse() + ->multiply($Y); + } +} diff --git a/vendor/markbaker/matrix/classes/src/Exception.php b/vendor/markbaker/matrix/classes/src/Exception.php new file mode 100644 index 0000000..55a428c --- /dev/null +++ b/vendor/markbaker/matrix/classes/src/Exception.php @@ -0,0 +1,13 @@ +isSquare()) { + throw new Exception('Adjoint can only be calculated for a square matrix'); + } + + return self::getAdjoint($matrix); + } + + /** + * Calculate the cofactors of the matrix + * + * @param Matrix $matrix The matrix whose cofactors we wish to calculate + * @return Matrix + * + * @throws Exception + */ + private static function getCofactors(Matrix $matrix) + { + $cofactors = self::getMinors($matrix); + $dimensions = $matrix->rows; + + $cof = 1; + for ($i = 0; $i < $dimensions; ++$i) { + $cofs = $cof; + for ($j = 0; $j < $dimensions; ++$j) { + $cofactors[$i][$j] *= $cofs; + $cofs = -$cofs; + } + $cof = -$cof; + } + + return new Matrix($cofactors); + } + + /** + * Return the cofactors of this matrix + * + * @param Matrix $matrix The matrix whose cofactors we wish to calculate + * @return Matrix + * + * @throws Exception + */ + public static function cofactors(Matrix $matrix) + { + if (!$matrix->isSquare()) { + throw new Exception('Cofactors can only be calculated for a square matrix'); + } + + return self::getCofactors($matrix); + } + + /** + * @param Matrix $matrix + * @param int $row + * @param int $column + * @return float + * @throws Exception + */ + private static function getDeterminantSegment(Matrix $matrix, $row, $column) + { + $tmpMatrix = $matrix->toArray(); + unset($tmpMatrix[$row]); + array_walk( + $tmpMatrix, + function (&$row) use ($column) { + unset($row[$column]); + } + ); + + return self::getDeterminant(new Matrix($tmpMatrix)); + } + + /** + * Calculate the determinant of the matrix + * + * @param Matrix $matrix The matrix whose determinant we wish to calculate + * @return float + * + * @throws Exception + */ + private static function getDeterminant(Matrix $matrix) + { + $dimensions = $matrix->rows; + $determinant = 0; + + switch ($dimensions) { + case 1: + $determinant = $matrix->getValue(1, 1); + break; + case 2: + $determinant = $matrix->getValue(1, 1) * $matrix->getValue(2, 2) - + $matrix->getValue(1, 2) * $matrix->getValue(2, 1); + break; + default: + for ($i = 1; $i <= $dimensions; ++$i) { + $det = $matrix->getValue(1, $i) * self::getDeterminantSegment($matrix, 0, $i - 1); + if (($i % 2) == 0) { + $determinant -= $det; + } else { + $determinant += $det; + } + } + break; + } + + return $determinant; + } + + /** + * Return the determinant of this matrix + * + * @param Matrix $matrix The matrix whose determinant we wish to calculate + * @return float + * @throws Exception + **/ + public static function determinant(Matrix $matrix) + { + if (!$matrix->isSquare()) { + throw new Exception('Determinant can only be calculated for a square matrix'); + } + + return self::getDeterminant($matrix); + } + + /** + * Return the diagonal of this matrix + * + * @param Matrix $matrix The matrix whose diagonal we wish to calculate + * @return Matrix + * @throws Exception + **/ + public static function diagonal(Matrix $matrix) + { + if (!$matrix->isSquare()) { + throw new Exception('Diagonal can only be extracted from a square matrix'); + } + + $dimensions = $matrix->rows; + $grid = Builder::createFilledMatrix(0, $dimensions, $dimensions) + ->toArray(); + + for ($i = 0; $i < $dimensions; ++$i) { + $grid[$i][$i] = $matrix->getValue($i + 1, $i + 1); + } + + return new Matrix($grid); + } + + /** + * Return the antidiagonal of this matrix + * + * @param Matrix $matrix The matrix whose antidiagonal we wish to calculate + * @return Matrix + * @throws Exception + **/ + public static function antidiagonal(Matrix $matrix) + { + if (!$matrix->isSquare()) { + throw new Exception('Anti-Diagonal can only be extracted from a square matrix'); + } + + $dimensions = $matrix->rows; + $grid = Builder::createFilledMatrix(0, $dimensions, $dimensions) + ->toArray(); + + for ($i = 0; $i < $dimensions; ++$i) { + $grid[$i][$dimensions - $i - 1] = $matrix->getValue($i + 1, $dimensions - $i); + } + + return new Matrix($grid); + } + + /** + * Return the identity matrix + * The identity matrix, or sometimes ambiguously called a unit matrix, of size n is the n × n square matrix + * with ones on the main diagonal and zeros elsewhere + * + * @param Matrix $matrix The matrix whose identity we wish to calculate + * @return Matrix + * @throws Exception + **/ + public static function identity(Matrix $matrix) + { + if (!$matrix->isSquare()) { + throw new Exception('Identity can only be created for a square matrix'); + } + + $dimensions = $matrix->rows; + + return Builder::createIdentityMatrix($dimensions); + } + + /** + * Return the inverse of this matrix + * + * @param Matrix $matrix The matrix whose inverse we wish to calculate + * @return Matrix + * @throws Exception + **/ + public static function inverse(Matrix $matrix) + { + if (!$matrix->isSquare()) { + throw new Exception('Inverse can only be calculated for a square matrix'); + } + + $determinant = self::getDeterminant($matrix); + if ($determinant == 0.0) { + throw new Exception('Inverse can only be calculated for a matrix with a non-zero determinant'); + } + + if ($matrix->rows == 1) { + return new Matrix([[1 / $matrix->getValue(1, 1)]]); + } + + return self::getAdjoint($matrix) + ->multiply(1 / $determinant); + } + + /** + * Calculate the minors of the matrix + * + * @param Matrix $matrix The matrix whose minors we wish to calculate + * @return array[] + * + * @throws Exception + */ + protected static function getMinors(Matrix $matrix) + { + $minors = $matrix->toArray(); + $dimensions = $matrix->rows; + if ($dimensions == 1) { + return $minors; + } + + for ($i = 0; $i < $dimensions; ++$i) { + for ($j = 0; $j < $dimensions; ++$j) { + $minors[$i][$j] = self::getDeterminantSegment($matrix, $i, $j); + } + } + + return $minors; + } + + /** + * Return the minors of the matrix + * The minor of a matrix A is the determinant of some smaller square matrix, cut down from A by removing one or + * more of its rows or columns. + * Minors obtained by removing just one row and one column from square matrices (first minors) are required for + * calculating matrix cofactors, which in turn are useful for computing both the determinant and inverse of + * square matrices. + * + * @param Matrix $matrix The matrix whose minors we wish to calculate + * @return Matrix + * @throws Exception + **/ + public static function minors(Matrix $matrix) + { + if (!$matrix->isSquare()) { + throw new Exception('Minors can only be calculated for a square matrix'); + } + + return new Matrix(self::getMinors($matrix)); + } + + /** + * Return the trace of this matrix + * The trace is defined as the sum of the elements on the main diagonal (the diagonal from the upper left to the lower right) + * of the matrix + * + * @param Matrix $matrix The matrix whose trace we wish to calculate + * @return float + * @throws Exception + **/ + public static function trace(Matrix $matrix) + { + if (!$matrix->isSquare()) { + throw new Exception('Trace can only be extracted from a square matrix'); + } + + $dimensions = $matrix->rows; + $result = 0; + for ($i = 1; $i <= $dimensions; ++$i) { + $result += $matrix->getValue($i, $i); + } + + return $result; + } + + /** + * Return the transpose of this matrix + * + * @param Matrix $matrix The matrix whose transpose we wish to calculate + * @return Matrix + **/ + public static function transpose(Matrix $matrix) + { + $array = array_values(array_merge([null], $matrix->toArray())); + $grid = call_user_func_array( + 'array_map', + $array + ); + + return new Matrix($grid); + } +} diff --git a/vendor/markbaker/matrix/classes/src/Functions/adjoint.php b/vendor/markbaker/matrix/classes/src/Functions/adjoint.php new file mode 100644 index 0000000..fc1e169 --- /dev/null +++ b/vendor/markbaker/matrix/classes/src/Functions/adjoint.php @@ -0,0 +1,30 @@ +buildFromArray(array_values($grid)); + } + + /* + * Create a new Matrix object from an array of values + * + * @param array $grid + */ + protected function buildFromArray(array $grid) + { + $this->rows = count($grid); + $columns = array_reduce( + $grid, + function ($carry, $value) { + return max($carry, is_array($value) ? count($value) : 1); + } + ); + $this->columns = $columns; + + array_walk( + $grid, + function (&$value) use ($columns) { + if (!is_array($value)) { + $value = [$value]; + } + $value = array_pad(array_values($value), $columns, null); + } + ); + + $this->grid = $grid; + } + + /** + * Validate that a row number is a positive integer + * + * @param int $row + * @return int + * @throws Exception + */ + public static function validateRow($row) + { + if ((!is_numeric($row)) || (intval($row) < 1)) { + throw new Exception('Invalid Row'); + } + + return (int)$row; + } + + /** + * Validate that a column number is a positive integer + * + * @param int $column + * @return int + * @throws Exception + */ + public static function validateColumn($column) + { + if ((!is_numeric($column)) || (intval($column) < 1)) { + throw new Exception('Invalid Column'); + } + + return (int)$column; + } + + /** + * Validate that a row number falls within the set of rows for this matrix + * + * @param int $row + * @return int + * @throws Exception + */ + protected function validateRowInRange($row) + { + $row = static::validateRow($row); + if ($row > $this->rows) { + throw new Exception('Requested Row exceeds matrix size'); + } + + return $row; + } + + /** + * Validate that a column number falls within the set of columns for this matrix + * + * @param int $column + * @return int + * @throws Exception + */ + protected function validateColumnInRange($column) + { + $column = static::validateColumn($column); + if ($column > $this->columns) { + throw new Exception('Requested Column exceeds matrix size'); + } + + return $column; + } + + /** + * Return a new matrix as a subset of rows from this matrix, starting at row number $row, and $rowCount rows + * A $rowCount value of 0 will return all rows of the matrix from $row + * A negative $rowCount value will return rows until that many rows from the end of the matrix + * + * Note that row numbers start from 1, not from 0 + * + * @param int $row + * @param int $rowCount + * @return static + * @throws Exception + */ + public function getRows($row, $rowCount = 1) + { + $row = $this->validateRowInRange($row); + if ($rowCount === 0) { + $rowCount = $this->rows - $row + 1; + } + + return new static(array_slice($this->grid, $row - 1, (int)$rowCount)); + } + + /** + * Return a new matrix as a subset of columns from this matrix, starting at column number $column, and $columnCount columns + * A $columnCount value of 0 will return all columns of the matrix from $column + * A negative $columnCount value will return columns until that many columns from the end of the matrix + * + * Note that column numbers start from 1, not from 0 + * + * @param int $column + * @param int $columnCount + * @return Matrix + * @throws Exception + */ + public function getColumns($column, $columnCount = 1) + { + $column = $this->validateColumnInRange($column); + if ($columnCount < 1) { + $columnCount = $this->columns + $columnCount - $column + 1; + } + + $grid = []; + for ($i = $column - 1; $i < $column + $columnCount - 1; ++$i) { + $grid[] = array_column($this->grid, $i); + } + + return (new static($grid))->transpose(); + } + + /** + * Return a new matrix as a subset of rows from this matrix, dropping rows starting at row number $row, + * and $rowCount rows + * A negative $rowCount value will drop rows until that many rows from the end of the matrix + * A $rowCount value of 0 will remove all rows of the matrix from $row + * + * Note that row numbers start from 1, not from 0 + * + * @param int $row + * @param int $rowCount + * @return static + * @throws Exception + */ + public function dropRows($row, $rowCount = 1) + { + $this->validateRowInRange($row); + if ($rowCount === 0) { + $rowCount = $this->rows - $row + 1; + } + + $grid = $this->grid; + array_splice($grid, $row - 1, (int)$rowCount); + + return new static($grid); + } + + /** + * Return a new matrix as a subset of columns from this matrix, dropping columns starting at column number $column, + * and $columnCount columns + * A negative $columnCount value will drop columns until that many columns from the end of the matrix + * A $columnCount value of 0 will remove all columns of the matrix from $column + * + * Note that column numbers start from 1, not from 0 + * + * @param int $column + * @param int $columnCount + * @return static + * @throws Exception + */ + public function dropColumns($column, $columnCount = 1) + { + $this->validateColumnInRange($column); + if ($columnCount < 1) { + $columnCount = $this->columns + $columnCount - $column + 1; + } + + $grid = $this->grid; + array_walk( + $grid, + function (&$row) use ($column, $columnCount) { + array_splice($row, $column - 1, (int)$columnCount); + } + ); + + return new static($grid); + } + + /** + * Return a value from this matrix, from the "cell" identified by the row and column numbers + * Note that row and column numbers start from 1, not from 0 + * + * @param int $row + * @param int $column + * @return mixed + * @throws Exception + */ + public function getValue($row, $column) + { + $row = $this->validateRowInRange($row); + $column = $this->validateColumnInRange($column); + + return $this->grid[$row - 1][$column - 1]; + } + + /** + * Returns a Generator that will yield each row of the matrix in turn as a vector matrix + * or the value of each cell if the matrix is a column vector + * + * @return Generator|Matrix[]|mixed[] + */ + public function rows() + { + foreach ($this->grid as $i => $row) { + yield $i + 1 => ($this->columns == 1) + ? $row[0] + : new static([$row]); + } + } + + /** + * Returns a Generator that will yield each column of the matrix in turn as a vector matrix + * or the value of each cell if the matrix is a row vector + * + * @return Generator|Matrix[]|mixed[] + */ + public function columns() + { + for ($i = 0; $i < $this->columns; ++$i) { + yield $i + 1 => ($this->rows == 1) + ? $this->grid[0][$i] + : new static(array_column($this->grid, $i)); + } + } + + /** + * Identify if the row and column dimensions of this matrix are equal, + * i.e. if it is a "square" matrix + * + * @return bool + */ + public function isSquare() + { + return $this->rows == $this->columns; + } + + /** + * Identify if this matrix is a vector + * i.e. if it comprises only a single row or a single column + * + * @return bool + */ + public function isVector() + { + return $this->rows == 1 || $this->columns == 1; + } + + /** + * Return the matrix as a 2-dimensional array + * + * @return array + */ + public function toArray() + { + return $this->grid; + } + + /** + * Solve A*X = B. + * + * @param Matrix $B Right hand side + * + * @throws Exception + * + * @return Matrix ... Solution if A is square, least squares solution otherwise + */ + public function solve(Matrix $B) + { + if ($this->columns === $this->rows) { + return (new LU($this))->solve($B); + } + + return (new QR($this))->solve($B); + } + + protected static $getters = [ + 'rows', + 'columns', + ]; + + /** + * Access specific properties as read-only (no setters) + * + * @param string $propertyName + * @return mixed + * @throws Exception + */ + public function __get($propertyName) + { + $propertyName = strtolower($propertyName); + + // Test for function calls + if (in_array($propertyName, self::$getters)) { + return $this->$propertyName; + } + + throw new Exception('Property does not exist'); + } + + protected static $functions = [ + 'antidiagonal', + 'adjoint', + 'cofactors', + 'determinant', + 'diagonal', + 'identity', + 'inverse', + 'minors', + 'trace', + 'transpose', + ]; + + protected static $operations = [ + 'add', + 'subtract', + 'multiply', + 'divideby', + 'divideinto', + 'directsum', + ]; + + /** + * Returns the result of the function call or operation + * + * @param string $functionName + * @param mixed[] $arguments + * @return Matrix|float + * @throws Exception + */ + public function __call($functionName, $arguments) + { + $functionName = strtolower(str_replace('_', '', $functionName)); + + if (in_array($functionName, self::$functions, true) || in_array($functionName, self::$operations, true)) { + $functionName = "\\" . __NAMESPACE__ . "\\{$functionName}"; + if (is_callable($functionName)) { + $arguments = array_values(array_merge([$this], $arguments)); + return call_user_func_array($functionName, $arguments); + } + } + throw new Exception('Function or Operation does not exist'); + } +} diff --git a/vendor/markbaker/matrix/classes/src/Operations/add.php b/vendor/markbaker/matrix/classes/src/Operations/add.php new file mode 100644 index 0000000..4ae7312 --- /dev/null +++ b/vendor/markbaker/matrix/classes/src/Operations/add.php @@ -0,0 +1,44 @@ + $matrixValues The matrices to add + * @return Matrix + * @throws Exception + */ +function add(...$matrixValues) +{ + if (count($matrixValues) < 2) { + throw new Exception('Addition operation requires at least 2 arguments'); + } + + $matrix = array_shift($matrixValues); + + if (is_array($matrix)) { + $matrix = new Matrix($matrix); + } + if (!$matrix instanceof Matrix) { + throw new Exception('Addition arguments must be Matrix or array'); + } + + $result = new Addition($matrix); + + foreach ($matrixValues as $matrix) { + $result->execute($matrix); + } + + return $result->result(); +} diff --git a/vendor/markbaker/matrix/classes/src/Operations/directsum.php b/vendor/markbaker/matrix/classes/src/Operations/directsum.php new file mode 100644 index 0000000..9d15b89 --- /dev/null +++ b/vendor/markbaker/matrix/classes/src/Operations/directsum.php @@ -0,0 +1,44 @@ + $matrixValues The matrices to add + * @return Matrix + * @throws Exception + */ +function directsum(...$matrixValues) +{ + if (count($matrixValues) < 2) { + throw new Exception('DirectSum operation requires at least 2 arguments'); + } + + $matrix = array_shift($matrixValues); + + if (is_array($matrix)) { + $matrix = new Matrix($matrix); + } + if (!$matrix instanceof Matrix) { + throw new Exception('DirectSum arguments must be Matrix or array'); + } + + $result = new DirectSum($matrix); + + foreach ($matrixValues as $matrix) { + $result->execute($matrix); + } + + return $result->result(); +} diff --git a/vendor/markbaker/matrix/classes/src/Operations/divideby.php b/vendor/markbaker/matrix/classes/src/Operations/divideby.php new file mode 100644 index 0000000..767d03a --- /dev/null +++ b/vendor/markbaker/matrix/classes/src/Operations/divideby.php @@ -0,0 +1,44 @@ + $matrixValues The matrices to divide + * @return Matrix + * @throws Exception + */ +function divideby(...$matrixValues) +{ + if (count($matrixValues) < 2) { + throw new Exception('Division operation requires at least 2 arguments'); + } + + $matrix = array_shift($matrixValues); + + if (is_array($matrix)) { + $matrix = new Matrix($matrix); + } + if (!$matrix instanceof Matrix) { + throw new Exception('Division arguments must be Matrix or array'); + } + + $result = new Division($matrix); + + foreach ($matrixValues as $matrix) { + $result->execute($matrix); + } + + return $result->result(); +} diff --git a/vendor/markbaker/matrix/classes/src/Operations/divideinto.php b/vendor/markbaker/matrix/classes/src/Operations/divideinto.php new file mode 100644 index 0000000..c6ce633 --- /dev/null +++ b/vendor/markbaker/matrix/classes/src/Operations/divideinto.php @@ -0,0 +1,45 @@ + $matrixValues The numbers to divide + * @return Matrix + * @throws Exception + */ +function divideinto(...$matrixValues) +{ + if (count($matrixValues) < 2) { + throw new Exception('Division operation requires at least 2 arguments'); + } + + $matrix = array_pop($matrixValues); + $matrixValues = array_reverse($matrixValues); + + if (is_array($matrix)) { + $matrix = new Matrix($matrix); + } + if (!$matrix instanceof Matrix) { + throw new Exception('Division arguments must be Matrix or array'); + } + + $result = new Division($matrix); + + foreach ($matrixValues as $matrix) { + $result->execute($matrix); + } + + return $result->result(); +} diff --git a/vendor/markbaker/matrix/classes/src/Operations/multiply.php b/vendor/markbaker/matrix/classes/src/Operations/multiply.php new file mode 100644 index 0000000..1428f09 --- /dev/null +++ b/vendor/markbaker/matrix/classes/src/Operations/multiply.php @@ -0,0 +1,44 @@ + $matrixValues The matrices to multiply + * @return Matrix + * @throws Exception + */ +function multiply(...$matrixValues) +{ + if (count($matrixValues) < 2) { + throw new Exception('Multiplication operation requires at least 2 arguments'); + } + + $matrix = array_shift($matrixValues); + + if (is_array($matrix)) { + $matrix = new Matrix($matrix); + } + if (!$matrix instanceof Matrix) { + throw new Exception('Multiplication arguments must be Matrix or array'); + } + + $result = new Multiplication($matrix); + + foreach ($matrixValues as $matrix) { + $result->execute($matrix); + } + + return $result->result(); +} diff --git a/vendor/markbaker/matrix/classes/src/Operations/subtract.php b/vendor/markbaker/matrix/classes/src/Operations/subtract.php new file mode 100644 index 0000000..3123b61 --- /dev/null +++ b/vendor/markbaker/matrix/classes/src/Operations/subtract.php @@ -0,0 +1,44 @@ + $matrixValues The matrices to subtract + * @return Matrix + * @throws Exception + */ +function subtract(...$matrixValues) +{ + if (count($matrixValues) < 2) { + throw new Exception('Subtraction operation requires at least 2 arguments'); + } + + $matrix = array_shift($matrixValues); + + if (is_array($matrix)) { + $matrix = new Matrix($matrix); + } + if (!$matrix instanceof Matrix) { + throw new Exception('Subtraction arguments must be Matrix or array'); + } + + $result = new Subtraction($matrix); + + foreach ($matrixValues as $matrix) { + $result->execute($matrix); + } + + return $result->result(); +} diff --git a/vendor/markbaker/matrix/classes/src/Operators/Addition.php b/vendor/markbaker/matrix/classes/src/Operators/Addition.php new file mode 100644 index 0000000..e78c6d7 --- /dev/null +++ b/vendor/markbaker/matrix/classes/src/Operators/Addition.php @@ -0,0 +1,68 @@ +addMatrix($value); + } elseif (is_numeric($value)) { + return $this->addScalar($value); + } + + throw new Exception('Invalid argument for addition'); + } + + /** + * Execute the addition for a scalar + * + * @param mixed $value The numeric value to add to the current base value + * @return $this The operation object, allowing multiple additions to be chained + **/ + protected function addScalar($value) + { + for ($row = 0; $row < $this->rows; ++$row) { + for ($column = 0; $column < $this->columns; ++$column) { + $this->matrix[$row][$column] += $value; + } + } + + return $this; + } + + /** + * Execute the addition for a matrix + * + * @param Matrix $value The numeric value to add to the current base value + * @return $this The operation object, allowing multiple additions to be chained + * @throws Exception If the provided argument is not appropriate for the operation + **/ + protected function addMatrix(Matrix $value) + { + $this->validateMatchingDimensions($value); + + for ($row = 0; $row < $this->rows; ++$row) { + for ($column = 0; $column < $this->columns; ++$column) { + $this->matrix[$row][$column] += $value->getValue($row + 1, $column + 1); + } + } + + return $this; + } +} diff --git a/vendor/markbaker/matrix/classes/src/Operators/DirectSum.php b/vendor/markbaker/matrix/classes/src/Operators/DirectSum.php new file mode 100644 index 0000000..6db0853 --- /dev/null +++ b/vendor/markbaker/matrix/classes/src/Operators/DirectSum.php @@ -0,0 +1,64 @@ +directSumMatrix($value); + } + + throw new Exception('Invalid argument for addition'); + } + + /** + * Execute the direct sum for a matrix + * + * @param Matrix $value The numeric value to concatenate/direct sum with the current base value + * @return $this The operation object, allowing multiple additions to be chained + **/ + private function directSumMatrix($value) + { + $originalColumnCount = count($this->matrix[0]); + $originalRowCount = count($this->matrix); + $valColumnCount = $value->columns; + $valRowCount = $value->rows; + $value = $value->toArray(); + + for ($row = 0; $row < $this->rows; ++$row) { + $this->matrix[$row] = array_merge($this->matrix[$row], array_fill(0, $valColumnCount, 0)); + } + + $this->matrix = array_merge( + $this->matrix, + array_fill(0, $valRowCount, array_fill(0, $originalColumnCount, 0)) + ); + + for ($row = $originalRowCount; $row < $originalRowCount + $valRowCount; ++$row) { + array_splice( + $this->matrix[$row], + $originalColumnCount, + $valColumnCount, + $value[$row - $originalRowCount] + ); + } + + return $this; + } +} diff --git a/vendor/markbaker/matrix/classes/src/Operators/Division.php b/vendor/markbaker/matrix/classes/src/Operators/Division.php new file mode 100644 index 0000000..2a573f5 --- /dev/null +++ b/vendor/markbaker/matrix/classes/src/Operators/Division.php @@ -0,0 +1,38 @@ +multiplyMatrix($value); + } elseif (is_numeric($value)) { + return $this->multiplyScalar(1 / $value); + } + + throw new Exception('Invalid argument for division'); + } +} diff --git a/vendor/markbaker/matrix/classes/src/Operators/Multiplication.php b/vendor/markbaker/matrix/classes/src/Operators/Multiplication.php new file mode 100644 index 0000000..63df162 --- /dev/null +++ b/vendor/markbaker/matrix/classes/src/Operators/Multiplication.php @@ -0,0 +1,77 @@ +multiplyMatrix($value); + } elseif (is_numeric($value)) { + return $this->multiplyScalar($value); + } + + throw new Exception('Invalid argument for multiplication'); + } + + /** + * Execute the multiplication for a scalar + * + * @param mixed $value The numeric value to multiply with the current base value + * @return $this The operation object, allowing multiple mutiplications to be chained + **/ + protected function multiplyScalar($value) + { + for ($row = 0; $row < $this->rows; ++$row) { + for ($column = 0; $column < $this->columns; ++$column) { + $this->matrix[$row][$column] *= $value; + } + } + + return $this; + } + + /** + * Execute the multiplication for a matrix + * + * @param Matrix $value The numeric value to multiply with the current base value + * @return $this The operation object, allowing multiple mutiplications to be chained + * @throws Exception If the provided argument is not appropriate for the operation + **/ + protected function multiplyMatrix(Matrix $value) + { + $this->validateReflectingDimensions($value); + + $newRows = $this->rows; + $newColumns = $value->columns; + $matrix = Builder::createFilledMatrix(0, $newRows, $newColumns) + ->toArray(); + for ($row = 0; $row < $newRows; ++$row) { + for ($column = 0; $column < $newColumns; ++$column) { + $columnData = $value->getColumns($column + 1)->toArray(); + foreach ($this->matrix[$row] as $key => $valueData) { + $matrix[$row][$column] += $valueData * $columnData[$key][0]; + } + } + } + $this->matrix = $matrix; + + return $this; + } +} diff --git a/vendor/markbaker/matrix/classes/src/Operators/Operator.php b/vendor/markbaker/matrix/classes/src/Operators/Operator.php new file mode 100644 index 0000000..87d3f3b --- /dev/null +++ b/vendor/markbaker/matrix/classes/src/Operators/Operator.php @@ -0,0 +1,78 @@ +rows = $matrix->rows; + $this->columns = $matrix->columns; + $this->matrix = $matrix->toArray(); + } + + /** + * Compare the dimensions of the matrices being operated on to see if they are valid for addition/subtraction + * + * @param Matrix $matrix The second Matrix object on which the operation will be performed + * @throws Exception + */ + protected function validateMatchingDimensions(Matrix $matrix) + { + if (($this->rows != $matrix->rows) || ($this->columns != $matrix->columns)) { + throw new Exception('Matrices have mismatched dimensions'); + } + } + + /** + * Compare the dimensions of the matrices being operated on to see if they are valid for multiplication/division + * + * @param Matrix $matrix The second Matrix object on which the operation will be performed + * @throws Exception + */ + protected function validateReflectingDimensions(Matrix $matrix) + { + if ($this->columns != $matrix->rows) { + throw new Exception('Matrices have mismatched dimensions'); + } + } + + /** + * Return the result of the operation + * + * @return Matrix + */ + public function result() + { + return new Matrix($this->matrix); + } +} diff --git a/vendor/markbaker/matrix/classes/src/Operators/Subtraction.php b/vendor/markbaker/matrix/classes/src/Operators/Subtraction.php new file mode 100644 index 0000000..57c0b14 --- /dev/null +++ b/vendor/markbaker/matrix/classes/src/Operators/Subtraction.php @@ -0,0 +1,68 @@ +subtractMatrix($value); + } elseif (is_numeric($value)) { + return $this->subtractScalar($value); + } + + throw new Exception('Invalid argument for subtraction'); + } + + /** + * Execute the subtraction for a scalar + * + * @param mixed $value The numeric value to subtracted from the current base value + * @return $this The operation object, allowing multiple additions to be chained + **/ + protected function subtractScalar($value) + { + for ($row = 0; $row < $this->rows; ++$row) { + for ($column = 0; $column < $this->columns; ++$column) { + $this->matrix[$row][$column] -= $value; + } + } + + return $this; + } + + /** + * Execute the subtraction for a matrix + * + * @param Matrix $value The numeric value to subtract from the current base value + * @return $this The operation object, allowing multiple subtractions to be chained + * @throws Exception If the provided argument is not appropriate for the operation + **/ + protected function subtractMatrix(Matrix $value) + { + $this->validateMatchingDimensions($value); + + for ($row = 0; $row < $this->rows; ++$row) { + for ($column = 0; $column < $this->columns; ++$column) { + $this->matrix[$row][$column] -= $value->getValue($row + 1, $column + 1); + } + } + + return $this; + } +} diff --git a/vendor/markbaker/matrix/composer.json b/vendor/markbaker/matrix/composer.json new file mode 100644 index 0000000..0e0337e --- /dev/null +++ b/vendor/markbaker/matrix/composer.json @@ -0,0 +1,82 @@ +{ + "name": "markbaker/matrix", + "type": "library", + "description": "PHP Class for working with matrices", + "keywords": ["matrix", "vector", "mathematics"], + "homepage": "https://github.com/MarkBaker/PHPMatrix", + "license": "MIT", + "authors": [ + { + "name": "Mark Baker", + "email": "mark@lange.demon.co.uk" + } + ], + "require": { + "php": "^5.6.0|^7.0.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7|^6.0|7.0", + "phpmd/phpmd": "dev-master", + "sebastian/phpcpd": "^3.0", + "phploc/phploc": "^4", + "squizlabs/php_codesniffer": "^3.0@dev", + "phpcompatibility/php-compatibility": "dev-master", + "dealerdirect/phpcodesniffer-composer-installer": "dev-master" + }, + "autoload": { + "psr-4": { + "Matrix\\": "classes/src/" + }, + "files": [ + "classes/src/Functions/adjoint.php", + "classes/src/Functions/antidiagonal.php", + "classes/src/Functions/cofactors.php", + "classes/src/Functions/determinant.php", + "classes/src/Functions/diagonal.php", + "classes/src/Functions/identity.php", + "classes/src/Functions/inverse.php", + "classes/src/Functions/minors.php", + "classes/src/Functions/trace.php", + "classes/src/Functions/transpose.php", + "classes/src/Operations/add.php", + "classes/src/Operations/directsum.php", + "classes/src/Operations/subtract.php", + "classes/src/Operations/multiply.php", + "classes/src/Operations/divideby.php", + "classes/src/Operations/divideinto.php" + ] + }, + "autoload-dev": { + "psr-4": { + "MatrixTest\\": "unitTests/classes/src/" + }, + "files": [ + "classes/src/Functions/adjoint.php", + "classes/src/Functions/antidiagonal.php", + "classes/src/Functions/cofactors.php", + "classes/src/Functions/determinant.php", + "classes/src/Functions/diagonal.php", + "classes/src/Functions/identity.php", + "classes/src/Functions/inverse.php", + "classes/src/Functions/minors.php", + "classes/src/Functions/trace.php", + "classes/src/Functions/transpose.php", + "classes/src/Operations/add.php", + "classes/src/Operations/directsum.php", + "classes/src/Operations/subtract.php", + "classes/src/Operations/multiply.php", + "classes/src/Operations/divideby.php", + "classes/src/Operations/divideinto.php" + ] + }, + "scripts": { + "style": "phpcs --standard=PSR2 --report-width=200 --report=summary,full classes/src/ unitTests/classes/src -n", + "test": "phpunit -c phpunit.xml.dist", + "mess": "phpmd classes/src/ xml codesize,unusedcode,design,naming -n", + "lines": "phploc classes/src/ -n", + "cpd": "phpcpd classes/src/ -n", + "versions": "phpcs --report-width=200 --report=summary,full classes/src/ --standard=PHPCompatibility --runtime-set testVersion 5.6- -n", + "coverage": "phpunit -c phpunit.xml.dist --coverage-text --coverage-html ./build/coverage" + }, + "minimum-stability": "dev" +} diff --git a/vendor/markbaker/matrix/examples/test.php b/vendor/markbaker/matrix/examples/test.php new file mode 100644 index 0000000..b2bb027 --- /dev/null +++ b/vendor/markbaker/matrix/examples/test.php @@ -0,0 +1,19 @@ +directsum(new Matrix\Matrix($grid2)); + +var_dump($new); diff --git a/vendor/markbaker/matrix/infection.json.dist b/vendor/markbaker/matrix/infection.json.dist new file mode 100644 index 0000000..eddaa70 --- /dev/null +++ b/vendor/markbaker/matrix/infection.json.dist @@ -0,0 +1,17 @@ +{ + "timeout": 1, + "source": { + "directories": [ + "classes\/src" + ] + }, + "logs": { + "text": "build/infection/text.log", + "summary": "build/infection/summary.log", + "debug": "build/infection/debug.log", + "perMutator": "build/infection/perMutator.md" + }, + "mutators": { + "@default": true + } +} diff --git a/vendor/markbaker/matrix/license.md b/vendor/markbaker/matrix/license.md new file mode 100644 index 0000000..7329058 --- /dev/null +++ b/vendor/markbaker/matrix/license.md @@ -0,0 +1,25 @@ +The MIT License (MIT) +===================== + +Copyright © `2018` `Mark Baker` + +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/markbaker/matrix/phpstan.neon b/vendor/markbaker/matrix/phpstan.neon new file mode 100644 index 0000000..1607abd --- /dev/null +++ b/vendor/markbaker/matrix/phpstan.neon @@ -0,0 +1,5 @@ +parameters: + ignoreErrors: + - '#Property [A-Za-z\\]+::\$[A-Za-z]+ has no typehint specified#' + - '#Method [A-Za-z\\]+::[A-Za-z]+\(\) has no return typehint specified#' + checkMissingIterableValueType: false diff --git a/vendor/monolog/monolog/CHANGELOG.md b/vendor/monolog/monolog/CHANGELOG.md new file mode 100644 index 0000000..a939b2e --- /dev/null +++ b/vendor/monolog/monolog/CHANGELOG.md @@ -0,0 +1,427 @@ +### 1.27.0 (2022-03-13) + + * Added $maxDepth / setMaxDepth to NormalizerFormatter / JsonFormatter to configure the maximum depth if the default of 9 does not work for you (#1633) + +### 1.26.1 (2021-05-28) + + * Fixed PHP 8.1 deprecation warning + +### 1.26.0 (2020-12-14) + + * Added $dateFormat and $removeUsedContextFields arguments to PsrLogMessageProcessor (backport from 2.x) + +### 1.25.5 (2020-07-23) + + * Fixed array access on null in RavenHandler + * Fixed unique_id in WebProcessor not being disableable + +### 1.25.4 (2020-05-22) + + * Fixed GitProcessor type error when there is no git repo present + * Fixed normalization of SoapFault objects containing deeply nested objects as "detail" + * Fixed support for relative paths in RotatingFileHandler + +### 1.25.3 (2019-12-20) + + * Fixed formatting of resources in JsonFormatter + * Fixed RedisHandler failing to use MULTI properly when passed a proxied Redis instance (e.g. in Symfony with lazy services) + * Fixed FilterHandler triggering a notice when handleBatch was filtering all records passed to it + * Fixed Turkish locale messing up the conversion of level names to their constant values + +### 1.25.2 (2019-11-13) + + * Fixed normalization of Traversables to avoid traversing them as not all of them are rewindable + * Fixed setFormatter/getFormatter to forward to the nested handler in FilterHandler, FingersCrossedHandler, BufferHandler and SamplingHandler + * Fixed BrowserConsoleHandler formatting when using multiple styles + * Fixed normalization of exception codes to be always integers even for PDOException which have them as numeric strings + * Fixed normalization of SoapFault objects containing non-strings as "detail" + * Fixed json encoding across all handlers to always attempt recovery of non-UTF-8 strings instead of failing the whole encoding + +### 1.25.1 (2019-09-06) + + * Fixed forward-compatible interfaces to be compatible with Monolog 1.x too. + +### 1.25.0 (2019-09-06) + + * Deprecated SlackbotHandler, use SlackWebhookHandler or SlackHandler instead + * Deprecated RavenHandler, use sentry/sentry 2.x and their Sentry\Monolog\Handler instead + * Deprecated HipChatHandler, migrate to Slack and use SlackWebhookHandler or SlackHandler instead + * Added forward-compatible interfaces and traits FormattableHandlerInterface, FormattableHandlerTrait, ProcessableHandlerInterface, ProcessableHandlerTrait. If you use modern PHP and want to make code compatible with Monolog 1 and 2 this can help. You will have to require at least Monolog 1.25 though. + * Added support for RFC3164 (outdated BSD syslog protocol) to SyslogUdpHandler + * Fixed issue in GroupHandler and WhatFailureGroupHandler where setting multiple processors would duplicate records + * Fixed issue in SignalHandler restarting syscalls functionality + * Fixed normalizers handling of exception backtraces to avoid serializing arguments in some cases + * Fixed ZendMonitorHandler to work with the latest Zend Server versions + * Fixed ChromePHPHandler to avoid sending more data than latest Chrome versions allow in headers (4KB down from 256KB). + +### 1.24.0 (2018-11-05) + + * BC Notice: If you are extending any of the Monolog's Formatters' `normalize` method, make sure you add the new `$depth = 0` argument to your function signature to avoid strict PHP warnings. + * Added a `ResettableInterface` in order to reset/reset/clear/flush handlers and processors + * Added a `ProcessorInterface` as an optional way to label a class as being a processor (mostly useful for autowiring dependency containers) + * Added a way to log signals being received using Monolog\SignalHandler + * Added ability to customize error handling at the Logger level using Logger::setExceptionHandler + * Added InsightOpsHandler to migrate users of the LogEntriesHandler + * Added protection to NormalizerHandler against circular and very deep structures, it now stops normalizing at a depth of 9 + * Added capture of stack traces to ErrorHandler when logging PHP errors + * Added RavenHandler support for a `contexts` context or extra key to forward that to Sentry's contexts + * Added forwarding of context info to FluentdFormatter + * Added SocketHandler::setChunkSize to override the default chunk size in case you must send large log lines to rsyslog for example + * Added ability to extend/override BrowserConsoleHandler + * Added SlackWebhookHandler::getWebhookUrl and SlackHandler::getToken to enable class extensibility + * Added SwiftMailerHandler::getSubjectFormatter to enable class extensibility + * Dropped official support for HHVM in test builds + * Fixed normalization of exception traces when call_user_func is used to avoid serializing objects and the data they contain + * Fixed naming of fields in Slack handler, all field names are now capitalized in all cases + * Fixed HipChatHandler bug where slack dropped messages randomly + * Fixed normalization of objects in Slack handlers + * Fixed support for PHP7's Throwable in NewRelicHandler + * Fixed race bug when StreamHandler sometimes incorrectly reported it failed to create a directory + * Fixed table row styling issues in HtmlFormatter + * Fixed RavenHandler dropping the message when logging exception + * Fixed WhatFailureGroupHandler skipping processors when using handleBatch + and implement it where possible + * Fixed display of anonymous class names + +### 1.23.0 (2017-06-19) + + * Improved SyslogUdpHandler's support for RFC5424 and added optional `$ident` argument + * Fixed GelfHandler truncation to be per field and not per message + * Fixed compatibility issue with PHP <5.3.6 + * Fixed support for headless Chrome in ChromePHPHandler + * Fixed support for latest Aws SDK in DynamoDbHandler + * Fixed support for SwiftMailer 6.0+ in SwiftMailerHandler + +### 1.22.1 (2017-03-13) + + * Fixed lots of minor issues in the new Slack integrations + * Fixed support for allowInlineLineBreaks in LineFormatter when formatting exception backtraces + +### 1.22.0 (2016-11-26) + + * Added SlackbotHandler and SlackWebhookHandler to set up Slack integration more easily + * Added MercurialProcessor to add mercurial revision and branch names to log records + * Added support for AWS SDK v3 in DynamoDbHandler + * Fixed fatal errors occuring when normalizing generators that have been fully consumed + * Fixed RollbarHandler to include a level (rollbar level), monolog_level (original name), channel and datetime (unix) + * Fixed RollbarHandler not flushing records automatically, calling close() explicitly is not necessary anymore + * Fixed SyslogUdpHandler to avoid sending empty frames + * Fixed a few PHP 7.0 and 7.1 compatibility issues + +### 1.21.0 (2016-07-29) + + * Break: Reverted the addition of $context when the ErrorHandler handles regular php errors from 1.20.0 as it was causing issues + * Added support for more formats in RotatingFileHandler::setFilenameFormat as long as they have Y, m and d in order + * Added ability to format the main line of text the SlackHandler sends by explictly setting a formatter on the handler + * Added information about SoapFault instances in NormalizerFormatter + * Added $handleOnlyReportedErrors option on ErrorHandler::registerErrorHandler (default true) to allow logging of all errors no matter the error_reporting level + +### 1.20.0 (2016-07-02) + + * Added FingersCrossedHandler::activate() to manually trigger the handler regardless of the activation policy + * Added StreamHandler::getUrl to retrieve the stream's URL + * Added ability to override addRow/addTitle in HtmlFormatter + * Added the $context to context information when the ErrorHandler handles a regular php error + * Deprecated RotatingFileHandler::setFilenameFormat to only support 3 formats: Y, Y-m and Y-m-d + * Fixed WhatFailureGroupHandler to work with PHP7 throwables + * Fixed a few minor bugs + +### 1.19.0 (2016-04-12) + + * Break: StreamHandler will not close streams automatically that it does not own. If you pass in a stream (not a path/url), then it will not close it for you. You can retrieve those using getStream() if needed + * Added DeduplicationHandler to remove duplicate records from notifications across multiple requests, useful for email or other notifications on errors + * Added ability to use `%message%` and other LineFormatter replacements in the subject line of emails sent with NativeMailHandler and SwiftMailerHandler + * Fixed HipChatHandler handling of long messages + +### 1.18.2 (2016-04-02) + + * Fixed ElasticaFormatter to use more precise dates + * Fixed GelfMessageFormatter sending too long messages + +### 1.18.1 (2016-03-13) + + * Fixed SlackHandler bug where slack dropped messages randomly + * Fixed RedisHandler issue when using with the PHPRedis extension + * Fixed AmqpHandler content-type being incorrectly set when using with the AMQP extension + * Fixed BrowserConsoleHandler regression + +### 1.18.0 (2016-03-01) + + * Added optional reduction of timestamp precision via `Logger->useMicrosecondTimestamps(false)`, disabling it gets you a bit of performance boost but reduces the precision to the second instead of microsecond + * Added possibility to skip some extra stack frames in IntrospectionProcessor if you have some library wrapping Monolog that is always adding frames + * Added `Logger->withName` to clone a logger (keeping all handlers) with a new name + * Added FluentdFormatter for the Fluentd unix socket protocol + * Added HandlerWrapper base class to ease the creation of handler wrappers, just extend it and override as needed + * Added support for replacing context sub-keys using `%context.*%` in LineFormatter + * Added support for `payload` context value in RollbarHandler + * Added setRelease to RavenHandler to describe the application version, sent with every log + * Added support for `fingerprint` context value in RavenHandler + * Fixed JSON encoding errors that would gobble up the whole log record, we now handle those more gracefully by dropping chars as needed + * Fixed write timeouts in SocketHandler and derivatives, set to 10sec by default, lower it with `setWritingTimeout()` + * Fixed PHP7 compatibility with regard to Exception/Throwable handling in a few places + +### 1.17.2 (2015-10-14) + + * Fixed ErrorHandler compatibility with non-Monolog PSR-3 loggers + * Fixed SlackHandler handling to use slack functionalities better + * Fixed SwiftMailerHandler bug when sending multiple emails they all had the same id + * Fixed 5.3 compatibility regression + +### 1.17.1 (2015-08-31) + + * Fixed RollbarHandler triggering PHP notices + +### 1.17.0 (2015-08-30) + + * Added support for `checksum` and `release` context/extra values in RavenHandler + * Added better support for exceptions in RollbarHandler + * Added UidProcessor::getUid + * Added support for showing the resource type in NormalizedFormatter + * Fixed IntrospectionProcessor triggering PHP notices + +### 1.16.0 (2015-08-09) + + * Added IFTTTHandler to notify ifttt.com triggers + * Added Logger::setHandlers() to allow setting/replacing all handlers + * Added $capSize in RedisHandler to cap the log size + * Fixed StreamHandler creation of directory to only trigger when the first log write happens + * Fixed bug in the handling of curl failures + * Fixed duplicate logging of fatal errors when both error and fatal error handlers are registered in monolog's ErrorHandler + * Fixed missing fatal errors records with handlers that need to be closed to flush log records + * Fixed TagProcessor::addTags support for associative arrays + +### 1.15.0 (2015-07-12) + + * Added addTags and setTags methods to change a TagProcessor + * Added automatic creation of directories if they are missing for a StreamHandler to open a log file + * Added retry functionality to Loggly, Cube and Mandrill handlers so they retry up to 5 times in case of network failure + * Fixed process exit code being incorrectly reset to 0 if ErrorHandler::registerExceptionHandler was used + * Fixed HTML/JS escaping in BrowserConsoleHandler + * Fixed JSON encoding errors being silently suppressed (PHP 5.5+ only) + +### 1.14.0 (2015-06-19) + + * Added PHPConsoleHandler to send record to Chrome's PHP Console extension and library + * Added support for objects implementing __toString in the NormalizerFormatter + * Added support for HipChat's v2 API in HipChatHandler + * Added Logger::setTimezone() to initialize the timezone monolog should use in case date.timezone isn't correct for your app + * Added an option to send formatted message instead of the raw record on PushoverHandler via ->useFormattedMessage(true) + * Fixed curl errors being silently suppressed + +### 1.13.1 (2015-03-09) + + * Fixed regression in HipChat requiring a new token to be created + +### 1.13.0 (2015-03-05) + + * Added Registry::hasLogger to check for the presence of a logger instance + * Added context.user support to RavenHandler + * Added HipChat API v2 support in the HipChatHandler + * Added NativeMailerHandler::addParameter to pass params to the mail() process + * Added context data to SlackHandler when $includeContextAndExtra is true + * Added ability to customize the Swift_Message per-email in SwiftMailerHandler + * Fixed SwiftMailerHandler to lazily create message instances if a callback is provided + * Fixed serialization of INF and NaN values in Normalizer and LineFormatter + +### 1.12.0 (2014-12-29) + + * Break: HandlerInterface::isHandling now receives a partial record containing only a level key. This was always the intent and does not break any Monolog handler but is strictly speaking a BC break and you should check if you relied on any other field in your own handlers. + * Added PsrHandler to forward records to another PSR-3 logger + * Added SamplingHandler to wrap around a handler and include only every Nth record + * Added MongoDBFormatter to support better storage with MongoDBHandler (it must be enabled manually for now) + * Added exception codes in the output of most formatters + * Added LineFormatter::includeStacktraces to enable exception stack traces in logs (uses more than one line) + * Added $useShortAttachment to SlackHandler to minify attachment size and $includeExtra to append extra data + * Added $host to HipChatHandler for users of private instances + * Added $transactionName to NewRelicHandler and support for a transaction_name context value + * Fixed MandrillHandler to avoid outputing API call responses + * Fixed some non-standard behaviors in SyslogUdpHandler + +### 1.11.0 (2014-09-30) + + * Break: The NewRelicHandler extra and context data are now prefixed with extra_ and context_ to avoid clashes. Watch out if you have scripts reading those from the API and rely on names + * Added WhatFailureGroupHandler to suppress any exception coming from the wrapped handlers and avoid chain failures if a logging service fails + * Added MandrillHandler to send emails via the Mandrillapp.com API + * Added SlackHandler to log records to a Slack.com account + * Added FleepHookHandler to log records to a Fleep.io account + * Added LogglyHandler::addTag to allow adding tags to an existing handler + * Added $ignoreEmptyContextAndExtra to LineFormatter to avoid empty [] at the end + * Added $useLocking to StreamHandler and RotatingFileHandler to enable flock() while writing + * Added support for PhpAmqpLib in the AmqpHandler + * Added FingersCrossedHandler::clear and BufferHandler::clear to reset them between batches in long running jobs + * Added support for adding extra fields from $_SERVER in the WebProcessor + * Fixed support for non-string values in PrsLogMessageProcessor + * Fixed SwiftMailer messages being sent with the wrong date in long running scripts + * Fixed minor PHP 5.6 compatibility issues + * Fixed BufferHandler::close being called twice + +### 1.10.0 (2014-06-04) + + * Added Logger::getHandlers() and Logger::getProcessors() methods + * Added $passthruLevel argument to FingersCrossedHandler to let it always pass some records through even if the trigger level is not reached + * Added support for extra data in NewRelicHandler + * Added $expandNewlines flag to the ErrorLogHandler to create multiple log entries when a message has multiple lines + +### 1.9.1 (2014-04-24) + + * Fixed regression in RotatingFileHandler file permissions + * Fixed initialization of the BufferHandler to make sure it gets flushed after receiving records + * Fixed ChromePHPHandler and FirePHPHandler's activation strategies to be more conservative + +### 1.9.0 (2014-04-20) + + * Added LogEntriesHandler to send logs to a LogEntries account + * Added $filePermissions to tweak file mode on StreamHandler and RotatingFileHandler + * Added $useFormatting flag to MemoryProcessor to make it send raw data in bytes + * Added support for table formatting in FirePHPHandler via the table context key + * Added a TagProcessor to add tags to records, and support for tags in RavenHandler + * Added $appendNewline flag to the JsonFormatter to enable using it when logging to files + * Added sound support to the PushoverHandler + * Fixed multi-threading support in StreamHandler + * Fixed empty headers issue when ChromePHPHandler received no records + * Fixed default format of the ErrorLogHandler + +### 1.8.0 (2014-03-23) + + * Break: the LineFormatter now strips newlines by default because this was a bug, set $allowInlineLineBreaks to true if you need them + * Added BrowserConsoleHandler to send logs to any browser's console via console.log() injection in the output + * Added FilterHandler to filter records and only allow those of a given list of levels through to the wrapped handler + * Added FlowdockHandler to send logs to a Flowdock account + * Added RollbarHandler to send logs to a Rollbar account + * Added HtmlFormatter to send prettier log emails with colors for each log level + * Added GitProcessor to add the current branch/commit to extra record data + * Added a Monolog\Registry class to allow easier global access to pre-configured loggers + * Added support for the new official graylog2/gelf-php lib for GelfHandler, upgrade if you can by replacing the mlehner/gelf-php requirement + * Added support for HHVM + * Added support for Loggly batch uploads + * Added support for tweaking the content type and encoding in NativeMailerHandler + * Added $skipClassesPartials to tweak the ignored classes in the IntrospectionProcessor + * Fixed batch request support in GelfHandler + +### 1.7.0 (2013-11-14) + + * Added ElasticSearchHandler to send logs to an Elastic Search server + * Added DynamoDbHandler and ScalarFormatter to send logs to Amazon's Dynamo DB + * Added SyslogUdpHandler to send logs to a remote syslogd server + * Added LogglyHandler to send logs to a Loggly account + * Added $level to IntrospectionProcessor so it only adds backtraces when needed + * Added $version to LogstashFormatter to allow using the new v1 Logstash format + * Added $appName to NewRelicHandler + * Added configuration of Pushover notification retries/expiry + * Added $maxColumnWidth to NativeMailerHandler to change the 70 chars default + * Added chainability to most setters for all handlers + * Fixed RavenHandler batch processing so it takes the message from the record with highest priority + * Fixed HipChatHandler batch processing so it sends all messages at once + * Fixed issues with eAccelerator + * Fixed and improved many small things + +### 1.6.0 (2013-07-29) + + * Added HipChatHandler to send logs to a HipChat chat room + * Added ErrorLogHandler to send logs to PHP's error_log function + * Added NewRelicHandler to send logs to NewRelic's service + * Added Monolog\ErrorHandler helper class to register a Logger as exception/error/fatal handler + * Added ChannelLevelActivationStrategy for the FingersCrossedHandler to customize levels by channel + * Added stack traces output when normalizing exceptions (json output & co) + * Added Monolog\Logger::API constant (currently 1) + * Added support for ChromePHP's v4.0 extension + * Added support for message priorities in PushoverHandler, see $highPriorityLevel and $emergencyLevel + * Added support for sending messages to multiple users at once with the PushoverHandler + * Fixed RavenHandler's support for batch sending of messages (when behind a Buffer or FingersCrossedHandler) + * Fixed normalization of Traversables with very large data sets, only the first 1000 items are shown now + * Fixed issue in RotatingFileHandler when an open_basedir restriction is active + * Fixed minor issues in RavenHandler and bumped the API to Raven 0.5.0 + * Fixed SyslogHandler issue when many were used concurrently with different facilities + +### 1.5.0 (2013-04-23) + + * Added ProcessIdProcessor to inject the PID in log records + * Added UidProcessor to inject a unique identifier to all log records of one request/run + * Added support for previous exceptions in the LineFormatter exception serialization + * Added Monolog\Logger::getLevels() to get all available levels + * Fixed ChromePHPHandler so it avoids sending headers larger than Chrome can handle + +### 1.4.1 (2013-04-01) + + * Fixed exception formatting in the LineFormatter to be more minimalistic + * Fixed RavenHandler's handling of context/extra data, requires Raven client >0.1.0 + * Fixed log rotation in RotatingFileHandler to work with long running scripts spanning multiple days + * Fixed WebProcessor array access so it checks for data presence + * Fixed Buffer, Group and FingersCrossed handlers to make use of their processors + +### 1.4.0 (2013-02-13) + + * Added RedisHandler to log to Redis via the Predis library or the phpredis extension + * Added ZendMonitorHandler to log to the Zend Server monitor + * Added the possibility to pass arrays of handlers and processors directly in the Logger constructor + * Added `$useSSL` option to the PushoverHandler which is enabled by default + * Fixed ChromePHPHandler and FirePHPHandler issue when multiple instances are used simultaneously + * Fixed header injection capability in the NativeMailHandler + +### 1.3.1 (2013-01-11) + + * Fixed LogstashFormatter to be usable with stream handlers + * Fixed GelfMessageFormatter levels on Windows + +### 1.3.0 (2013-01-08) + + * Added PSR-3 compliance, the `Monolog\Logger` class is now an instance of `Psr\Log\LoggerInterface` + * Added PsrLogMessageProcessor that you can selectively enable for full PSR-3 compliance + * Added LogstashFormatter (combine with SocketHandler or StreamHandler to send logs to Logstash) + * Added PushoverHandler to send mobile notifications + * Added CouchDBHandler and DoctrineCouchDBHandler + * Added RavenHandler to send data to Sentry servers + * Added support for the new MongoClient class in MongoDBHandler + * Added microsecond precision to log records' timestamps + * Added `$flushOnOverflow` param to BufferHandler to flush by batches instead of losing + the oldest entries + * Fixed normalization of objects with cyclic references + +### 1.2.1 (2012-08-29) + + * Added new $logopts arg to SyslogHandler to provide custom openlog options + * Fixed fatal error in SyslogHandler + +### 1.2.0 (2012-08-18) + + * Added AmqpHandler (for use with AMQP servers) + * Added CubeHandler + * Added NativeMailerHandler::addHeader() to send custom headers in mails + * Added the possibility to specify more than one recipient in NativeMailerHandler + * Added the possibility to specify float timeouts in SocketHandler + * Added NOTICE and EMERGENCY levels to conform with RFC 5424 + * Fixed the log records to use the php default timezone instead of UTC + * Fixed BufferHandler not being flushed properly on PHP fatal errors + * Fixed normalization of exotic resource types + * Fixed the default format of the SyslogHandler to avoid duplicating datetimes in syslog + +### 1.1.0 (2012-04-23) + + * Added Monolog\Logger::isHandling() to check if a handler will + handle the given log level + * Added ChromePHPHandler + * Added MongoDBHandler + * Added GelfHandler (for use with Graylog2 servers) + * Added SocketHandler (for use with syslog-ng for example) + * Added NormalizerFormatter + * Added the possibility to change the activation strategy of the FingersCrossedHandler + * Added possibility to show microseconds in logs + * Added `server` and `referer` to WebProcessor output + +### 1.0.2 (2011-10-24) + + * Fixed bug in IE with large response headers and FirePHPHandler + +### 1.0.1 (2011-08-25) + + * Added MemoryPeakUsageProcessor and MemoryUsageProcessor + * Added Monolog\Logger::getName() to get a logger's channel name + +### 1.0.0 (2011-07-06) + + * Added IntrospectionProcessor to get info from where the logger was called + * Fixed WebProcessor in CLI + +### 1.0.0-RC1 (2011-07-01) + + * Initial release diff --git a/vendor/monolog/monolog/LICENSE b/vendor/monolog/monolog/LICENSE new file mode 100644 index 0000000..1647321 --- /dev/null +++ b/vendor/monolog/monolog/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2011-2016 Jordi Boggiano + +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/monolog/monolog/README.md b/vendor/monolog/monolog/README.md new file mode 100644 index 0000000..a578eb2 --- /dev/null +++ b/vendor/monolog/monolog/README.md @@ -0,0 +1,94 @@ +# Monolog - Logging for PHP [![Build Status](https://img.shields.io/travis/Seldaek/monolog.svg)](https://travis-ci.org/Seldaek/monolog) + +[![Total Downloads](https://img.shields.io/packagist/dt/monolog/monolog.svg)](https://packagist.org/packages/monolog/monolog) +[![Latest Stable Version](https://img.shields.io/packagist/v/monolog/monolog.svg)](https://packagist.org/packages/monolog/monolog) + + +Monolog sends your logs to files, sockets, inboxes, databases and various +web services. See the complete list of handlers below. Special handlers +allow you to build advanced logging strategies. + +This library implements the [PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md) +interface that you can type-hint against in your own libraries to keep +a maximum of interoperability. You can also use it in your applications to +make sure you can always use another compatible logger at a later time. +As of 1.11.0 Monolog public APIs will also accept PSR-3 log levels. +Internally Monolog still uses its own level scheme since it predates PSR-3. + +## Installation + +Install the latest version with + +```bash +$ composer require monolog/monolog +``` + +## Basic Usage + +```php +pushHandler(new StreamHandler('path/to/your.log', Logger::WARNING)); + +// add records to the log +$log->addWarning('Foo'); +$log->addError('Bar'); +``` + +## Documentation + +- [Usage Instructions](doc/01-usage.md) +- [Handlers, Formatters and Processors](doc/02-handlers-formatters-processors.md) +- [Utility classes](doc/03-utilities.md) +- [Extending Monolog](doc/04-extending.md) + +## Third Party Packages + +Third party handlers, formatters and processors are +[listed in the wiki](https://github.com/Seldaek/monolog/wiki/Third-Party-Packages). You +can also add your own there if you publish one. + +## About + +### Requirements + +- Monolog works with PHP 5.3 or above, and is also tested to work with HHVM. + +### Submitting bugs and feature requests + +Bugs and feature request are tracked on [GitHub](https://github.com/Seldaek/monolog/issues) + +### Framework Integrations + +- Frameworks and libraries using [PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md) + can be used very easily with Monolog since it implements the interface. +- [Symfony2](http://symfony.com) comes out of the box with Monolog. +- [Silex](http://silex.sensiolabs.org/) comes out of the box with Monolog. +- [Laravel 4 & 5](http://laravel.com/) come out of the box with Monolog. +- [Lumen](http://lumen.laravel.com/) comes out of the box with Monolog. +- [PPI](http://www.ppi.io/) comes out of the box with Monolog. +- [CakePHP](http://cakephp.org/) is usable with Monolog via the [cakephp-monolog](https://github.com/jadb/cakephp-monolog) plugin. +- [Slim](http://www.slimframework.com/) is usable with Monolog via the [Slim-Monolog](https://github.com/Flynsarmy/Slim-Monolog) log writer. +- [XOOPS 2.6](http://xoops.org/) comes out of the box with Monolog. +- [Aura.Web_Project](https://github.com/auraphp/Aura.Web_Project) comes out of the box with Monolog. +- [Nette Framework](http://nette.org/en/) can be used with Monolog via [Kdyby/Monolog](https://github.com/Kdyby/Monolog) extension. +- [Proton Micro Framework](https://github.com/alexbilbie/Proton) comes out of the box with Monolog. + +### Author + +Jordi Boggiano - -
    +See also the list of [contributors](https://github.com/Seldaek/monolog/contributors) which participated in this project. + +### License + +Monolog is licensed under the MIT License - see the `LICENSE` file for details + +### Acknowledgements + +This library is heavily inspired by Python's [Logbook](https://logbook.readthedocs.io/en/stable/) +library, although most concepts have been adjusted to fit to the PHP world. diff --git a/vendor/monolog/monolog/composer.json b/vendor/monolog/monolog/composer.json new file mode 100644 index 0000000..aecc40f --- /dev/null +++ b/vendor/monolog/monolog/composer.json @@ -0,0 +1,60 @@ +{ + "name": "monolog/monolog", + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "keywords": ["log", "logging", "psr-3"], + "homepage": "http://github.com/Seldaek/monolog", + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "require": { + "php": ">=5.3.0", + "psr/log": "~1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.5", + "graylog2/gelf-php": "~1.0", + "sentry/sentry": "^0.13", + "ruflin/elastica": ">=0.90 <3.0", + "doctrine/couchdb": "~1.0@dev", + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "php-amqplib/php-amqplib": "~2.4", + "swiftmailer/swiftmailer": "^5.3|^6.0", + "php-console/php-console": "^3.1.3", + "phpstan/phpstan": "^0.12.59" + }, + "suggest": { + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "sentry/sentry": "Allow sending log messages to a Sentry server", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-mongo": "Allow sending log messages to a MongoDB server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "php-console/php-console": "Allow sending log messages to Google Chrome" + }, + "autoload": { + "psr-4": {"Monolog\\": "src/Monolog"} + }, + "autoload-dev": { + "psr-4": {"Monolog\\": "tests/Monolog"} + }, + "provide": { + "psr/log-implementation": "1.0.0" + }, + "scripts": { + "test": "vendor/bin/phpunit", + "phpstan": "vendor/bin/phpstan analyse" + }, + "config": { + "lock": false + } +} diff --git a/vendor/monolog/monolog/phpstan.neon.dist b/vendor/monolog/monolog/phpstan.neon.dist new file mode 100644 index 0000000..1fe45df --- /dev/null +++ b/vendor/monolog/monolog/phpstan.neon.dist @@ -0,0 +1,16 @@ +parameters: + level: 3 + + paths: + - src/ +# - tests/ + + + ignoreErrors: + - '#zend_monitor_|ZEND_MONITOR_#' + - '#RollbarNotifier#' + - '#Predis\\Client#' + - '#^Cannot call method ltrim\(\) on int\|false.$#' + - '#^Access to an undefined property Raven_Client::\$context.$#' + - '#MongoDB\\(Client|Collection)#' + - '#Gelf\\IMessagePublisher#' diff --git a/vendor/monolog/monolog/src/Monolog/ErrorHandler.php b/vendor/monolog/monolog/src/Monolog/ErrorHandler.php new file mode 100644 index 0000000..5121c2c --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/ErrorHandler.php @@ -0,0 +1,239 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use Psr\Log\LoggerInterface; +use Psr\Log\LogLevel; +use Monolog\Handler\AbstractHandler; + +/** + * Monolog error handler + * + * A facility to enable logging of runtime errors, exceptions and fatal errors. + * + * Quick setup: ErrorHandler::register($logger); + * + * @author Jordi Boggiano + */ +class ErrorHandler +{ + private $logger; + + private $previousExceptionHandler; + private $uncaughtExceptionLevel; + + private $previousErrorHandler; + private $errorLevelMap; + private $handleOnlyReportedErrors; + + private $hasFatalErrorHandler; + private $fatalLevel; + private $reservedMemory; + private $lastFatalTrace; + private static $fatalErrors = array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR); + + public function __construct(LoggerInterface $logger) + { + $this->logger = $logger; + } + + /** + * Registers a new ErrorHandler for a given Logger + * + * By default it will handle errors, exceptions and fatal errors + * + * @param LoggerInterface $logger + * @param array|false $errorLevelMap an array of E_* constant to LogLevel::* constant mapping, or false to disable error handling + * @param int|false $exceptionLevel a LogLevel::* constant, or false to disable exception handling + * @param int|false $fatalLevel a LogLevel::* constant, or false to disable fatal error handling + * @return ErrorHandler + */ + public static function register(LoggerInterface $logger, $errorLevelMap = array(), $exceptionLevel = null, $fatalLevel = null) + { + //Forces the autoloader to run for LogLevel. Fixes an autoload issue at compile-time on PHP5.3. See https://github.com/Seldaek/monolog/pull/929 + class_exists('\\Psr\\Log\\LogLevel', true); + + /** @phpstan-ignore-next-line */ + $handler = new static($logger); + if ($errorLevelMap !== false) { + $handler->registerErrorHandler($errorLevelMap); + } + if ($exceptionLevel !== false) { + $handler->registerExceptionHandler($exceptionLevel); + } + if ($fatalLevel !== false) { + $handler->registerFatalHandler($fatalLevel); + } + + return $handler; + } + + public function registerExceptionHandler($level = null, $callPrevious = true) + { + $prev = set_exception_handler(array($this, 'handleException')); + $this->uncaughtExceptionLevel = $level; + if ($callPrevious && $prev) { + $this->previousExceptionHandler = $prev; + } + } + + public function registerErrorHandler(array $levelMap = array(), $callPrevious = true, $errorTypes = -1, $handleOnlyReportedErrors = true) + { + $prev = set_error_handler(array($this, 'handleError'), $errorTypes); + $this->errorLevelMap = array_replace($this->defaultErrorLevelMap(), $levelMap); + if ($callPrevious) { + $this->previousErrorHandler = $prev ?: true; + } + + $this->handleOnlyReportedErrors = $handleOnlyReportedErrors; + } + + public function registerFatalHandler($level = null, $reservedMemorySize = 20) + { + register_shutdown_function(array($this, 'handleFatalError')); + + $this->reservedMemory = str_repeat(' ', 1024 * $reservedMemorySize); + $this->fatalLevel = $level; + $this->hasFatalErrorHandler = true; + } + + protected function defaultErrorLevelMap() + { + return array( + E_ERROR => LogLevel::CRITICAL, + E_WARNING => LogLevel::WARNING, + E_PARSE => LogLevel::ALERT, + E_NOTICE => LogLevel::NOTICE, + E_CORE_ERROR => LogLevel::CRITICAL, + E_CORE_WARNING => LogLevel::WARNING, + E_COMPILE_ERROR => LogLevel::ALERT, + E_COMPILE_WARNING => LogLevel::WARNING, + E_USER_ERROR => LogLevel::ERROR, + E_USER_WARNING => LogLevel::WARNING, + E_USER_NOTICE => LogLevel::NOTICE, + E_STRICT => LogLevel::NOTICE, + E_RECOVERABLE_ERROR => LogLevel::ERROR, + E_DEPRECATED => LogLevel::NOTICE, + E_USER_DEPRECATED => LogLevel::NOTICE, + ); + } + + /** + * @private + */ + public function handleException($e) + { + $this->logger->log( + $this->uncaughtExceptionLevel === null ? LogLevel::ERROR : $this->uncaughtExceptionLevel, + sprintf('Uncaught Exception %s: "%s" at %s line %s', Utils::getClass($e), $e->getMessage(), $e->getFile(), $e->getLine()), + array('exception' => $e) + ); + + if ($this->previousExceptionHandler) { + call_user_func($this->previousExceptionHandler, $e); + } + + exit(255); + } + + /** + * @private + */ + public function handleError($code, $message, $file = '', $line = 0, $context = array()) + { + if ($this->handleOnlyReportedErrors && !(error_reporting() & $code)) { + return; + } + + // fatal error codes are ignored if a fatal error handler is present as well to avoid duplicate log entries + if (!$this->hasFatalErrorHandler || !in_array($code, self::$fatalErrors, true)) { + $level = isset($this->errorLevelMap[$code]) ? $this->errorLevelMap[$code] : LogLevel::CRITICAL; + $this->logger->log($level, self::codeToString($code).': '.$message, array('code' => $code, 'message' => $message, 'file' => $file, 'line' => $line)); + } else { + // http://php.net/manual/en/function.debug-backtrace.php + // As of 5.3.6, DEBUG_BACKTRACE_IGNORE_ARGS option was added. + // Any version less than 5.3.6 must use the DEBUG_BACKTRACE_IGNORE_ARGS constant value '2'. + $trace = debug_backtrace((PHP_VERSION_ID < 50306) ? 2 : DEBUG_BACKTRACE_IGNORE_ARGS); + array_shift($trace); // Exclude handleError from trace + $this->lastFatalTrace = $trace; + } + + if ($this->previousErrorHandler === true) { + return false; + } elseif ($this->previousErrorHandler) { + return call_user_func($this->previousErrorHandler, $code, $message, $file, $line, $context); + } + } + + /** + * @private + */ + public function handleFatalError() + { + $this->reservedMemory = null; + + $lastError = error_get_last(); + if ($lastError && in_array($lastError['type'], self::$fatalErrors, true)) { + $this->logger->log( + $this->fatalLevel === null ? LogLevel::ALERT : $this->fatalLevel, + 'Fatal Error ('.self::codeToString($lastError['type']).'): '.$lastError['message'], + array('code' => $lastError['type'], 'message' => $lastError['message'], 'file' => $lastError['file'], 'line' => $lastError['line'], 'trace' => $this->lastFatalTrace) + ); + + if ($this->logger instanceof Logger) { + foreach ($this->logger->getHandlers() as $handler) { + if ($handler instanceof AbstractHandler) { + $handler->close(); + } + } + } + } + } + + private static function codeToString($code) + { + switch ($code) { + case E_ERROR: + return 'E_ERROR'; + case E_WARNING: + return 'E_WARNING'; + case E_PARSE: + return 'E_PARSE'; + case E_NOTICE: + return 'E_NOTICE'; + case E_CORE_ERROR: + return 'E_CORE_ERROR'; + case E_CORE_WARNING: + return 'E_CORE_WARNING'; + case E_COMPILE_ERROR: + return 'E_COMPILE_ERROR'; + case E_COMPILE_WARNING: + return 'E_COMPILE_WARNING'; + case E_USER_ERROR: + return 'E_USER_ERROR'; + case E_USER_WARNING: + return 'E_USER_WARNING'; + case E_USER_NOTICE: + return 'E_USER_NOTICE'; + case E_STRICT: + return 'E_STRICT'; + case E_RECOVERABLE_ERROR: + return 'E_RECOVERABLE_ERROR'; + case E_DEPRECATED: + return 'E_DEPRECATED'; + case E_USER_DEPRECATED: + return 'E_USER_DEPRECATED'; + } + + return 'Unknown PHP error'; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php new file mode 100644 index 0000000..9beda1e --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +/** + * Formats a log message according to the ChromePHP array format + * + * @author Christophe Coevoet + */ +class ChromePHPFormatter implements FormatterInterface +{ + /** + * Translates Monolog log levels to Wildfire levels. + */ + private $logLevels = array( + Logger::DEBUG => 'log', + Logger::INFO => 'info', + Logger::NOTICE => 'info', + Logger::WARNING => 'warn', + Logger::ERROR => 'error', + Logger::CRITICAL => 'error', + Logger::ALERT => 'error', + Logger::EMERGENCY => 'error', + ); + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + // Retrieve the line and file if set and remove them from the formatted extra + $backtrace = 'unknown'; + if (isset($record['extra']['file'], $record['extra']['line'])) { + $backtrace = $record['extra']['file'].' : '.$record['extra']['line']; + unset($record['extra']['file'], $record['extra']['line']); + } + + $message = array('message' => $record['message']); + if ($record['context']) { + $message['context'] = $record['context']; + } + if ($record['extra']) { + $message['extra'] = $record['extra']; + } + if (count($message) === 1) { + $message = reset($message); + } + + return array( + $record['channel'], + $message, + $backtrace, + $this->logLevels[$record['level']], + ); + } + + public function formatBatch(array $records) + { + $formatted = array(); + + foreach ($records as $record) { + $formatted[] = $this->format($record); + } + + return $formatted; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php new file mode 100644 index 0000000..4c556cf --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Elastica\Document; + +/** + * Format a log message into an Elastica Document + * + * @author Jelle Vink + */ +class ElasticaFormatter extends NormalizerFormatter +{ + /** + * @var string Elastic search index name + */ + protected $index; + + /** + * @var string Elastic search document type + */ + protected $type; + + /** + * @param string $index Elastic Search index name + * @param string $type Elastic Search document type + */ + public function __construct($index, $type) + { + // elasticsearch requires a ISO 8601 format date with optional millisecond precision. + parent::__construct('Y-m-d\TH:i:s.uP'); + + $this->index = $index; + $this->type = $type; + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + $record = parent::format($record); + + return $this->getDocument($record); + } + + /** + * Getter index + * @return string + */ + public function getIndex() + { + return $this->index; + } + + /** + * Getter type + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * Convert a log message into an Elastica Document + * + * @param array $record Log message + * @return Document + */ + protected function getDocument($record) + { + $document = new Document(); + $document->setData($record); + $document->setType($this->type); + $document->setIndex($this->index); + + return $document; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php new file mode 100644 index 0000000..5094af3 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * formats the record to be used in the FlowdockHandler + * + * @author Dominik Liebler + */ +class FlowdockFormatter implements FormatterInterface +{ + /** + * @var string + */ + private $source; + + /** + * @var string + */ + private $sourceEmail; + + /** + * @param string $source + * @param string $sourceEmail + */ + public function __construct($source, $sourceEmail) + { + $this->source = $source; + $this->sourceEmail = $sourceEmail; + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + $tags = array( + '#logs', + '#' . strtolower($record['level_name']), + '#' . $record['channel'], + ); + + foreach ($record['extra'] as $value) { + $tags[] = '#' . $value; + } + + $subject = sprintf( + 'in %s: %s - %s', + $this->source, + $record['level_name'], + $this->getShortMessage($record['message']) + ); + + $record['flowdock'] = array( + 'source' => $this->source, + 'from_address' => $this->sourceEmail, + 'subject' => $subject, + 'content' => $record['message'], + 'tags' => $tags, + 'project' => $this->source, + ); + + return $record; + } + + /** + * {@inheritdoc} + */ + public function formatBatch(array $records) + { + $formatted = array(); + + foreach ($records as $record) { + $formatted[] = $this->format($record); + } + + return $formatted; + } + + /** + * @param string $message + * + * @return string + */ + public function getShortMessage($message) + { + static $hasMbString; + + if (null === $hasMbString) { + $hasMbString = function_exists('mb_strlen'); + } + + $maxLength = 45; + + if ($hasMbString) { + if (mb_strlen($message, 'UTF-8') > $maxLength) { + $message = mb_substr($message, 0, $maxLength - 4, 'UTF-8') . ' ...'; + } + } else { + if (strlen($message) > $maxLength) { + $message = substr($message, 0, $maxLength - 4) . ' ...'; + } + } + + return $message; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.php new file mode 100644 index 0000000..f8ead47 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Utils; + +/** + * Class FluentdFormatter + * + * Serializes a log message to Fluentd unix socket protocol + * + * Fluentd config: + * + * + * type unix + * path /var/run/td-agent/td-agent.sock + * + * + * Monolog setup: + * + * $logger = new Monolog\Logger('fluent.tag'); + * $fluentHandler = new Monolog\Handler\SocketHandler('unix:///var/run/td-agent/td-agent.sock'); + * $fluentHandler->setFormatter(new Monolog\Formatter\FluentdFormatter()); + * $logger->pushHandler($fluentHandler); + * + * @author Andrius Putna + */ +class FluentdFormatter implements FormatterInterface +{ + /** + * @var bool $levelTag should message level be a part of the fluentd tag + */ + protected $levelTag = false; + + public function __construct($levelTag = false) + { + if (!function_exists('json_encode')) { + throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s FluentdUnixFormatter'); + } + + $this->levelTag = (bool) $levelTag; + } + + public function isUsingLevelsInTag() + { + return $this->levelTag; + } + + public function format(array $record) + { + $tag = $record['channel']; + if ($this->levelTag) { + $tag .= '.' . strtolower($record['level_name']); + } + + $message = array( + 'message' => $record['message'], + 'context' => $record['context'], + 'extra' => $record['extra'], + ); + + if (!$this->levelTag) { + $message['level'] = $record['level']; + $message['level_name'] = $record['level_name']; + } + + return Utils::jsonEncode(array($tag, $record['datetime']->getTimestamp(), $message)); + } + + public function formatBatch(array $records) + { + $message = ''; + foreach ($records as $record) { + $message .= $this->format($record); + } + + return $message; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php b/vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php new file mode 100644 index 0000000..b5de751 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * Interface for formatters + * + * @author Jordi Boggiano + */ +interface FormatterInterface +{ + /** + * Formats a log record. + * + * @param array $record A record to format + * @return mixed The formatted record + */ + public function format(array $record); + + /** + * Formats a set of log records. + * + * @param array $records A set of records to format + * @return mixed The formatted set of records + */ + public function formatBatch(array $records); +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php new file mode 100644 index 0000000..2c1b0e8 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php @@ -0,0 +1,138 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; +use Gelf\Message; + +/** + * Serializes a log message to GELF + * @see http://www.graylog2.org/about/gelf + * + * @author Matt Lehner + */ +class GelfMessageFormatter extends NormalizerFormatter +{ + const DEFAULT_MAX_LENGTH = 32766; + + /** + * @var string the name of the system for the Gelf log message + */ + protected $systemName; + + /** + * @var string a prefix for 'extra' fields from the Monolog record (optional) + */ + protected $extraPrefix; + + /** + * @var string a prefix for 'context' fields from the Monolog record (optional) + */ + protected $contextPrefix; + + /** + * @var int max length per field + */ + protected $maxLength; + + /** + * Translates Monolog log levels to Graylog2 log priorities. + */ + private $logLevels = array( + Logger::DEBUG => 7, + Logger::INFO => 6, + Logger::NOTICE => 5, + Logger::WARNING => 4, + Logger::ERROR => 3, + Logger::CRITICAL => 2, + Logger::ALERT => 1, + Logger::EMERGENCY => 0, + ); + + public function __construct($systemName = null, $extraPrefix = null, $contextPrefix = 'ctxt_', $maxLength = null) + { + parent::__construct('U.u'); + + $this->systemName = $systemName ?: gethostname(); + + $this->extraPrefix = $extraPrefix; + $this->contextPrefix = $contextPrefix; + $this->maxLength = is_null($maxLength) ? self::DEFAULT_MAX_LENGTH : $maxLength; + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + $record = parent::format($record); + + if (!isset($record['datetime'], $record['message'], $record['level'])) { + throw new \InvalidArgumentException('The record should at least contain datetime, message and level keys, '.var_export($record, true).' given'); + } + + $message = new Message(); + $message + ->setTimestamp($record['datetime']) + ->setShortMessage((string) $record['message']) + ->setHost($this->systemName) + ->setLevel($this->logLevels[$record['level']]); + + // message length + system name length + 200 for padding / metadata + $len = 200 + strlen((string) $record['message']) + strlen($this->systemName); + + if ($len > $this->maxLength) { + $message->setShortMessage(substr($record['message'], 0, $this->maxLength)); + } + + if (isset($record['channel'])) { + $message->setFacility($record['channel']); + } + if (isset($record['extra']['line'])) { + $message->setLine($record['extra']['line']); + unset($record['extra']['line']); + } + if (isset($record['extra']['file'])) { + $message->setFile($record['extra']['file']); + unset($record['extra']['file']); + } + + foreach ($record['extra'] as $key => $val) { + $val = is_scalar($val) || null === $val ? $val : $this->toJson($val); + $len = strlen($this->extraPrefix . $key . $val); + if ($len > $this->maxLength) { + $message->setAdditional($this->extraPrefix . $key, substr($val, 0, $this->maxLength)); + break; + } + $message->setAdditional($this->extraPrefix . $key, $val); + } + + foreach ($record['context'] as $key => $val) { + $val = is_scalar($val) || null === $val ? $val : $this->toJson($val); + $len = strlen($this->contextPrefix . $key . $val); + if ($len > $this->maxLength) { + $message->setAdditional($this->contextPrefix . $key, substr($val, 0, $this->maxLength)); + break; + } + $message->setAdditional($this->contextPrefix . $key, $val); + } + + if (null === $message->getFile() && isset($record['context']['exception']['file'])) { + if (preg_match("/^(.+):([0-9]+)$/", $record['context']['exception']['file'], $matches)) { + $message->setFile($matches[1]); + $message->setLine($matches[2]); + } + } + + return $message; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php new file mode 100644 index 0000000..9e8d2d0 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php @@ -0,0 +1,142 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; +use Monolog\Utils; + +/** + * Formats incoming records into an HTML table + * + * This is especially useful for html email logging + * + * @author Tiago Brito + */ +class HtmlFormatter extends NormalizerFormatter +{ + /** + * Translates Monolog log levels to html color priorities. + */ + protected $logLevels = array( + Logger::DEBUG => '#cccccc', + Logger::INFO => '#468847', + Logger::NOTICE => '#3a87ad', + Logger::WARNING => '#c09853', + Logger::ERROR => '#f0ad4e', + Logger::CRITICAL => '#FF7708', + Logger::ALERT => '#C12A19', + Logger::EMERGENCY => '#000000', + ); + + /** + * @param string $dateFormat The format of the timestamp: one supported by DateTime::format + */ + public function __construct($dateFormat = null) + { + parent::__construct($dateFormat); + } + + /** + * Creates an HTML table row + * + * @param string $th Row header content + * @param string $td Row standard cell content + * @param bool $escapeTd false if td content must not be html escaped + * @return string + */ + protected function addRow($th, $td = ' ', $escapeTd = true) + { + $th = htmlspecialchars($th, ENT_NOQUOTES, 'UTF-8'); + if ($escapeTd) { + $td = '
    '.htmlspecialchars($td, ENT_NOQUOTES, 'UTF-8').'
    '; + } + + return "\n$th:\n".$td."\n"; + } + + /** + * Create a HTML h1 tag + * + * @param string $title Text to be in the h1 + * @param int $level Error level + * @return string + */ + protected function addTitle($title, $level) + { + $title = htmlspecialchars($title, ENT_NOQUOTES, 'UTF-8'); + + return '

    '.$title.'

    '; + } + + /** + * Formats a log record. + * + * @param array $record A record to format + * @return mixed The formatted record + */ + public function format(array $record) + { + $output = $this->addTitle($record['level_name'], $record['level']); + $output .= ''; + + $output .= $this->addRow('Message', (string) $record['message']); + $output .= $this->addRow('Time', $record['datetime']->format($this->dateFormat)); + $output .= $this->addRow('Channel', $record['channel']); + if ($record['context']) { + $embeddedTable = '
    '; + foreach ($record['context'] as $key => $value) { + $embeddedTable .= $this->addRow($key, $this->convertToString($value)); + } + $embeddedTable .= '
    '; + $output .= $this->addRow('Context', $embeddedTable, false); + } + if ($record['extra']) { + $embeddedTable = ''; + foreach ($record['extra'] as $key => $value) { + $embeddedTable .= $this->addRow($key, $this->convertToString($value)); + } + $embeddedTable .= '
    '; + $output .= $this->addRow('Extra', $embeddedTable, false); + } + + return $output.''; + } + + /** + * Formats a set of log records. + * + * @param array $records A set of records to format + * @return mixed The formatted set of records + */ + public function formatBatch(array $records) + { + $message = ''; + foreach ($records as $record) { + $message .= $this->format($record); + } + + return $message; + } + + protected function convertToString($data) + { + if (null === $data || is_scalar($data)) { + return (string) $data; + } + + $data = $this->normalize($data); + if (version_compare(PHP_VERSION, '5.4.0', '>=')) { + return Utils::jsonEncode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE, true); + } + + return str_replace('\\/', '/', Utils::jsonEncode($data, null, true)); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php new file mode 100644 index 0000000..4c5422d --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php @@ -0,0 +1,214 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Exception; +use Monolog\Utils; +use Throwable; + +/** + * Encodes whatever record data is passed to it as json + * + * This can be useful to log to databases or remote APIs + * + * @author Jordi Boggiano + */ +class JsonFormatter extends NormalizerFormatter +{ + const BATCH_MODE_JSON = 1; + const BATCH_MODE_NEWLINES = 2; + + protected $batchMode; + protected $appendNewline; + + /** + * @var bool + */ + protected $includeStacktraces = false; + + /** + * @param int $batchMode + * @param bool $appendNewline + * @param int $maxDepth + */ + public function __construct($batchMode = self::BATCH_MODE_JSON, $appendNewline = true, $maxDepth = 9) + { + parent::__construct(null, $maxDepth); + $this->batchMode = $batchMode; + $this->appendNewline = $appendNewline; + } + + /** + * The batch mode option configures the formatting style for + * multiple records. By default, multiple records will be + * formatted as a JSON-encoded array. However, for + * compatibility with some API endpoints, alternative styles + * are available. + * + * @return int + */ + public function getBatchMode() + { + return $this->batchMode; + } + + /** + * True if newlines are appended to every formatted record + * + * @return bool + */ + public function isAppendingNewlines() + { + return $this->appendNewline; + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + return $this->toJson($this->normalize($record), true) . ($this->appendNewline ? "\n" : ''); + } + + /** + * {@inheritdoc} + */ + public function formatBatch(array $records) + { + switch ($this->batchMode) { + case static::BATCH_MODE_NEWLINES: + return $this->formatBatchNewlines($records); + + case static::BATCH_MODE_JSON: + default: + return $this->formatBatchJson($records); + } + } + + /** + * @param bool $include + */ + public function includeStacktraces($include = true) + { + $this->includeStacktraces = $include; + } + + /** + * Return a JSON-encoded array of records. + * + * @param array $records + * @return string + */ + protected function formatBatchJson(array $records) + { + return $this->toJson($this->normalize($records), true); + } + + /** + * Use new lines to separate records instead of a + * JSON-encoded array. + * + * @param array $records + * @return string + */ + protected function formatBatchNewlines(array $records) + { + $instance = $this; + + $oldNewline = $this->appendNewline; + $this->appendNewline = false; + array_walk($records, function (&$value, $key) use ($instance) { + $value = $instance->format($value); + }); + $this->appendNewline = $oldNewline; + + return implode("\n", $records); + } + + /** + * Normalizes given $data. + * + * @param mixed $data + * + * @return mixed + */ + protected function normalize($data, $depth = 0) + { + if ($depth > $this->maxDepth) { + return 'Over '.$this->maxDepth.' levels deep, aborting normalization'; + } + + if (is_array($data)) { + $normalized = array(); + + $count = 1; + foreach ($data as $key => $value) { + if ($count++ > 1000) { + $normalized['...'] = 'Over 1000 items ('.count($data).' total), aborting normalization'; + break; + } + + $normalized[$key] = $this->normalize($value, $depth+1); + } + + return $normalized; + } + + if ($data instanceof Exception || $data instanceof Throwable) { + return $this->normalizeException($data); + } + + if (is_resource($data)) { + return parent::normalize($data); + } + + return $data; + } + + /** + * Normalizes given exception with or without its own stack trace based on + * `includeStacktraces` property. + * + * @param Exception|Throwable $e + * + * @return array + */ + protected function normalizeException($e) + { + // TODO 2.0 only check for Throwable + if (!$e instanceof Exception && !$e instanceof Throwable) { + throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.Utils::getClass($e)); + } + + $data = array( + 'class' => Utils::getClass($e), + 'message' => $e->getMessage(), + 'code' => (int) $e->getCode(), + 'file' => $e->getFile().':'.$e->getLine(), + ); + + if ($this->includeStacktraces) { + $trace = $e->getTrace(); + foreach ($trace as $frame) { + if (isset($frame['file'])) { + $data['trace'][] = $frame['file'].':'.$frame['line']; + } + } + } + + if ($previous = $e->getPrevious()) { + $data['previous'] = $this->normalizeException($previous); + } + + return $data; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php new file mode 100644 index 0000000..acc1fd3 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php @@ -0,0 +1,181 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Utils; + +/** + * Formats incoming records into a one-line string + * + * This is especially useful for logging to files + * + * @author Jordi Boggiano + * @author Christophe Coevoet + */ +class LineFormatter extends NormalizerFormatter +{ + const SIMPLE_FORMAT = "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n"; + + protected $format; + protected $allowInlineLineBreaks; + protected $ignoreEmptyContextAndExtra; + protected $includeStacktraces; + + /** + * @param string $format The format of the message + * @param string $dateFormat The format of the timestamp: one supported by DateTime::format + * @param bool $allowInlineLineBreaks Whether to allow inline line breaks in log entries + * @param bool $ignoreEmptyContextAndExtra + */ + public function __construct($format = null, $dateFormat = null, $allowInlineLineBreaks = false, $ignoreEmptyContextAndExtra = false) + { + $this->format = $format ?: static::SIMPLE_FORMAT; + $this->allowInlineLineBreaks = $allowInlineLineBreaks; + $this->ignoreEmptyContextAndExtra = $ignoreEmptyContextAndExtra; + parent::__construct($dateFormat); + } + + public function includeStacktraces($include = true) + { + $this->includeStacktraces = $include; + if ($this->includeStacktraces) { + $this->allowInlineLineBreaks = true; + } + } + + public function allowInlineLineBreaks($allow = true) + { + $this->allowInlineLineBreaks = $allow; + } + + public function ignoreEmptyContextAndExtra($ignore = true) + { + $this->ignoreEmptyContextAndExtra = $ignore; + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + $vars = parent::format($record); + + $output = $this->format; + + foreach ($vars['extra'] as $var => $val) { + if (false !== strpos($output, '%extra.'.$var.'%')) { + $output = str_replace('%extra.'.$var.'%', $this->stringify($val), $output); + unset($vars['extra'][$var]); + } + } + + + foreach ($vars['context'] as $var => $val) { + if (false !== strpos($output, '%context.'.$var.'%')) { + $output = str_replace('%context.'.$var.'%', $this->stringify($val), $output); + unset($vars['context'][$var]); + } + } + + if ($this->ignoreEmptyContextAndExtra) { + if (empty($vars['context'])) { + unset($vars['context']); + $output = str_replace('%context%', '', $output); + } + + if (empty($vars['extra'])) { + unset($vars['extra']); + $output = str_replace('%extra%', '', $output); + } + } + + foreach ($vars as $var => $val) { + if (false !== strpos($output, '%'.$var.'%')) { + $output = str_replace('%'.$var.'%', $this->stringify($val), $output); + } + } + + // remove leftover %extra.xxx% and %context.xxx% if any + if (false !== strpos($output, '%')) { + $output = preg_replace('/%(?:extra|context)\..+?%/', '', $output); + } + + return $output; + } + + public function formatBatch(array $records) + { + $message = ''; + foreach ($records as $record) { + $message .= $this->format($record); + } + + return $message; + } + + public function stringify($value) + { + return $this->replaceNewlines($this->convertToString($value)); + } + + protected function normalizeException($e) + { + // TODO 2.0 only check for Throwable + if (!$e instanceof \Exception && !$e instanceof \Throwable) { + throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.Utils::getClass($e)); + } + + $previousText = ''; + if ($previous = $e->getPrevious()) { + do { + $previousText .= ', '.Utils::getClass($previous).'(code: '.$previous->getCode().'): '.$previous->getMessage().' at '.$previous->getFile().':'.$previous->getLine(); + } while ($previous = $previous->getPrevious()); + } + + $str = '[object] ('.Utils::getClass($e).'(code: '.$e->getCode().'): '.$e->getMessage().' at '.$e->getFile().':'.$e->getLine().$previousText.')'; + if ($this->includeStacktraces) { + $str .= "\n[stacktrace]\n".$e->getTraceAsString()."\n"; + } + + return $str; + } + + protected function convertToString($data) + { + if (null === $data || is_bool($data)) { + return var_export($data, true); + } + + if (is_scalar($data)) { + return (string) $data; + } + + if (version_compare(PHP_VERSION, '5.4.0', '>=')) { + return $this->toJson($data, true); + } + + return str_replace('\\/', '/', $this->toJson($data, true)); + } + + protected function replaceNewlines($str) + { + if ($this->allowInlineLineBreaks) { + if (0 === strpos($str, '{')) { + return str_replace(array('\r', '\n'), array("\r", "\n"), $str); + } + + return $str; + } + + return str_replace(array("\r\n", "\r", "\n"), ' ', $str); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php new file mode 100644 index 0000000..401859b --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * Encodes message information into JSON in a format compatible with Loggly. + * + * @author Adam Pancutt + */ +class LogglyFormatter extends JsonFormatter +{ + /** + * Overrides the default batch mode to new lines for compatibility with the + * Loggly bulk API. + * + * @param int $batchMode + */ + public function __construct($batchMode = self::BATCH_MODE_NEWLINES, $appendNewline = false) + { + parent::__construct($batchMode, $appendNewline); + } + + /** + * Appends the 'timestamp' parameter for indexing by Loggly. + * + * @see https://www.loggly.com/docs/automated-parsing/#json + * @see \Monolog\Formatter\JsonFormatter::format() + */ + public function format(array $record) + { + if (isset($record["datetime"]) && ($record["datetime"] instanceof \DateTime)) { + $record["timestamp"] = $record["datetime"]->format("Y-m-d\TH:i:s.uO"); + // TODO 2.0 unset the 'datetime' parameter, retained for BC + } + + return parent::format($record); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php new file mode 100644 index 0000000..8f83bec --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php @@ -0,0 +1,166 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * Serializes a log message to Logstash Event Format + * + * @see http://logstash.net/ + * @see https://github.com/logstash/logstash/blob/master/lib/logstash/event.rb + * + * @author Tim Mower + */ +class LogstashFormatter extends NormalizerFormatter +{ + const V0 = 0; + const V1 = 1; + + /** + * @var string the name of the system for the Logstash log message, used to fill the @source field + */ + protected $systemName; + + /** + * @var string an application name for the Logstash log message, used to fill the @type field + */ + protected $applicationName; + + /** + * @var string a prefix for 'extra' fields from the Monolog record (optional) + */ + protected $extraPrefix; + + /** + * @var string a prefix for 'context' fields from the Monolog record (optional) + */ + protected $contextPrefix; + + /** + * @var int logstash format version to use + */ + protected $version; + + /** + * @param string $applicationName the application that sends the data, used as the "type" field of logstash + * @param string $systemName the system/machine name, used as the "source" field of logstash, defaults to the hostname of the machine + * @param string $extraPrefix prefix for extra keys inside logstash "fields" + * @param string $contextPrefix prefix for context keys inside logstash "fields", defaults to ctxt_ + * @param int $version the logstash format version to use, defaults to 0 + */ + public function __construct($applicationName, $systemName = null, $extraPrefix = null, $contextPrefix = 'ctxt_', $version = self::V0) + { + // logstash requires a ISO 8601 format date with optional millisecond precision. + parent::__construct('Y-m-d\TH:i:s.uP'); + + $this->systemName = $systemName ?: gethostname(); + $this->applicationName = $applicationName; + $this->extraPrefix = $extraPrefix; + $this->contextPrefix = $contextPrefix; + $this->version = $version; + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + $record = parent::format($record); + + if ($this->version === self::V1) { + $message = $this->formatV1($record); + } else { + $message = $this->formatV0($record); + } + + return $this->toJson($message) . "\n"; + } + + protected function formatV0(array $record) + { + if (empty($record['datetime'])) { + $record['datetime'] = gmdate('c'); + } + $message = array( + '@timestamp' => $record['datetime'], + '@source' => $this->systemName, + '@fields' => array(), + ); + if (isset($record['message'])) { + $message['@message'] = $record['message']; + } + if (isset($record['channel'])) { + $message['@tags'] = array($record['channel']); + $message['@fields']['channel'] = $record['channel']; + } + if (isset($record['level'])) { + $message['@fields']['level'] = $record['level']; + } + if ($this->applicationName) { + $message['@type'] = $this->applicationName; + } + if (isset($record['extra']['server'])) { + $message['@source_host'] = $record['extra']['server']; + } + if (isset($record['extra']['url'])) { + $message['@source_path'] = $record['extra']['url']; + } + if (!empty($record['extra'])) { + foreach ($record['extra'] as $key => $val) { + $message['@fields'][$this->extraPrefix . $key] = $val; + } + } + if (!empty($record['context'])) { + foreach ($record['context'] as $key => $val) { + $message['@fields'][$this->contextPrefix . $key] = $val; + } + } + + return $message; + } + + protected function formatV1(array $record) + { + if (empty($record['datetime'])) { + $record['datetime'] = gmdate('c'); + } + $message = array( + '@timestamp' => $record['datetime'], + '@version' => 1, + 'host' => $this->systemName, + ); + if (isset($record['message'])) { + $message['message'] = $record['message']; + } + if (isset($record['channel'])) { + $message['type'] = $record['channel']; + $message['channel'] = $record['channel']; + } + if (isset($record['level_name'])) { + $message['level'] = $record['level_name']; + } + if ($this->applicationName) { + $message['type'] = $this->applicationName; + } + if (!empty($record['extra'])) { + foreach ($record['extra'] as $key => $val) { + $message[$this->extraPrefix . $key] = $val; + } + } + if (!empty($record['context'])) { + foreach ($record['context'] as $key => $val) { + $message[$this->contextPrefix . $key] = $val; + } + } + + return $message; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php new file mode 100644 index 0000000..bd9e4c0 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Utils; + +/** + * Formats a record for use with the MongoDBHandler. + * + * @author Florian Plattner + */ +class MongoDBFormatter implements FormatterInterface +{ + private $exceptionTraceAsString; + private $maxNestingLevel; + + /** + * @param int $maxNestingLevel 0 means infinite nesting, the $record itself is level 1, $record['context'] is 2 + * @param bool $exceptionTraceAsString set to false to log exception traces as a sub documents instead of strings + */ + public function __construct($maxNestingLevel = 3, $exceptionTraceAsString = true) + { + $this->maxNestingLevel = max($maxNestingLevel, 0); + $this->exceptionTraceAsString = (bool) $exceptionTraceAsString; + } + + /** + * {@inheritDoc} + */ + public function format(array $record) + { + return $this->formatArray($record); + } + + /** + * {@inheritDoc} + */ + public function formatBatch(array $records) + { + foreach ($records as $key => $record) { + $records[$key] = $this->format($record); + } + + return $records; + } + + protected function formatArray(array $record, $nestingLevel = 0) + { + if ($this->maxNestingLevel == 0 || $nestingLevel <= $this->maxNestingLevel) { + foreach ($record as $name => $value) { + if ($value instanceof \DateTime) { + $record[$name] = $this->formatDate($value, $nestingLevel + 1); + } elseif ($value instanceof \Exception) { + $record[$name] = $this->formatException($value, $nestingLevel + 1); + } elseif (is_array($value)) { + $record[$name] = $this->formatArray($value, $nestingLevel + 1); + } elseif (is_object($value)) { + $record[$name] = $this->formatObject($value, $nestingLevel + 1); + } + } + } else { + $record = '[...]'; + } + + return $record; + } + + protected function formatObject($value, $nestingLevel) + { + $objectVars = get_object_vars($value); + $objectVars['class'] = Utils::getClass($value); + + return $this->formatArray($objectVars, $nestingLevel); + } + + protected function formatException(\Exception $exception, $nestingLevel) + { + $formattedException = array( + 'class' => Utils::getClass($exception), + 'message' => $exception->getMessage(), + 'code' => (int) $exception->getCode(), + 'file' => $exception->getFile() . ':' . $exception->getLine(), + ); + + if ($this->exceptionTraceAsString === true) { + $formattedException['trace'] = $exception->getTraceAsString(); + } else { + $formattedException['trace'] = $exception->getTrace(); + } + + return $this->formatArray($formattedException, $nestingLevel); + } + + protected function formatDate(\DateTime $value, $nestingLevel) + { + return new \MongoDate($value->getTimestamp()); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php new file mode 100644 index 0000000..010466b --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php @@ -0,0 +1,199 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Exception; +use Monolog\Utils; + +/** + * Normalizes incoming records to remove objects/resources so it's easier to dump to various targets + * + * @author Jordi Boggiano + */ +class NormalizerFormatter implements FormatterInterface +{ + const SIMPLE_DATE = "Y-m-d H:i:s"; + + protected $dateFormat; + protected $maxDepth; + + /** + * @param string $dateFormat The format of the timestamp: one supported by DateTime::format + * @param int $maxDepth + */ + public function __construct($dateFormat = null, $maxDepth = 9) + { + $this->dateFormat = $dateFormat ?: static::SIMPLE_DATE; + $this->maxDepth = $maxDepth; + if (!function_exists('json_encode')) { + throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s NormalizerFormatter'); + } + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + return $this->normalize($record); + } + + /** + * {@inheritdoc} + */ + public function formatBatch(array $records) + { + foreach ($records as $key => $record) { + $records[$key] = $this->format($record); + } + + return $records; + } + + /** + * @return int + */ + public function getMaxDepth() + { + return $this->maxDepth; + } + + /** + * @param int $maxDepth + */ + public function setMaxDepth($maxDepth) + { + $this->maxDepth = $maxDepth; + } + + protected function normalize($data, $depth = 0) + { + if ($depth > $this->maxDepth) { + return 'Over '.$this->maxDepth.' levels deep, aborting normalization'; + } + + if (null === $data || is_scalar($data)) { + if (is_float($data)) { + if (is_infinite($data)) { + return ($data > 0 ? '' : '-') . 'INF'; + } + if (is_nan($data)) { + return 'NaN'; + } + } + + return $data; + } + + if (is_array($data)) { + $normalized = array(); + + $count = 1; + foreach ($data as $key => $value) { + if ($count++ > 1000) { + $normalized['...'] = 'Over 1000 items ('.count($data).' total), aborting normalization'; + break; + } + + $normalized[$key] = $this->normalize($value, $depth+1); + } + + return $normalized; + } + + if ($data instanceof \DateTime) { + return $data->format($this->dateFormat); + } + + if (is_object($data)) { + // TODO 2.0 only check for Throwable + if ($data instanceof Exception || (PHP_VERSION_ID > 70000 && $data instanceof \Throwable)) { + return $this->normalizeException($data); + } + + // non-serializable objects that implement __toString stringified + if (method_exists($data, '__toString') && !$data instanceof \JsonSerializable) { + $value = $data->__toString(); + } else { + // the rest is json-serialized in some way + $value = $this->toJson($data, true); + } + + return sprintf("[object] (%s: %s)", Utils::getClass($data), $value); + } + + if (is_resource($data)) { + return sprintf('[resource] (%s)', get_resource_type($data)); + } + + return '[unknown('.gettype($data).')]'; + } + + protected function normalizeException($e) + { + // TODO 2.0 only check for Throwable + if (!$e instanceof Exception && !$e instanceof \Throwable) { + throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.Utils::getClass($e)); + } + + $data = array( + 'class' => Utils::getClass($e), + 'message' => $e->getMessage(), + 'code' => (int) $e->getCode(), + 'file' => $e->getFile().':'.$e->getLine(), + ); + + if ($e instanceof \SoapFault) { + if (isset($e->faultcode)) { + $data['faultcode'] = $e->faultcode; + } + + if (isset($e->faultactor)) { + $data['faultactor'] = $e->faultactor; + } + + if (isset($e->detail)) { + if (is_string($e->detail)) { + $data['detail'] = $e->detail; + } elseif (is_object($e->detail) || is_array($e->detail)) { + $data['detail'] = $this->toJson($e->detail, true); + } + } + } + + $trace = $e->getTrace(); + foreach ($trace as $frame) { + if (isset($frame['file'])) { + $data['trace'][] = $frame['file'].':'.$frame['line']; + } + } + + if ($previous = $e->getPrevious()) { + $data['previous'] = $this->normalizeException($previous); + } + + return $data; + } + + /** + * Return the JSON representation of a value + * + * @param mixed $data + * @param bool $ignoreErrors + * @throws \RuntimeException if encoding fails and errors are not ignored + * @return string + */ + protected function toJson($data, $ignoreErrors = false) + { + return Utils::jsonEncode($data, null, $ignoreErrors); + } +} \ No newline at end of file diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php new file mode 100644 index 0000000..5d345d5 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * Formats data into an associative array of scalar values. + * Objects and arrays will be JSON encoded. + * + * @author Andrew Lawson + */ +class ScalarFormatter extends NormalizerFormatter +{ + /** + * {@inheritdoc} + */ + public function format(array $record) + { + foreach ($record as $key => $value) { + $record[$key] = $this->normalizeValue($value); + } + + return $record; + } + + /** + * @param mixed $value + * @return mixed + */ + protected function normalizeValue($value) + { + $normalized = $this->normalize($value); + + if (is_array($normalized) || is_object($normalized)) { + return $this->toJson($normalized, true); + } + + return $normalized; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php new file mode 100644 index 0000000..65dba99 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +/** + * Serializes a log message according to Wildfire's header requirements + * + * @author Eric Clemmons (@ericclemmons) + * @author Christophe Coevoet + * @author Kirill chEbba Chebunin + */ +class WildfireFormatter extends NormalizerFormatter +{ + const TABLE = 'table'; + + /** + * Translates Monolog log levels to Wildfire levels. + */ + private $logLevels = array( + Logger::DEBUG => 'LOG', + Logger::INFO => 'INFO', + Logger::NOTICE => 'INFO', + Logger::WARNING => 'WARN', + Logger::ERROR => 'ERROR', + Logger::CRITICAL => 'ERROR', + Logger::ALERT => 'ERROR', + Logger::EMERGENCY => 'ERROR', + ); + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + // Retrieve the line and file if set and remove them from the formatted extra + $file = $line = ''; + if (isset($record['extra']['file'])) { + $file = $record['extra']['file']; + unset($record['extra']['file']); + } + if (isset($record['extra']['line'])) { + $line = $record['extra']['line']; + unset($record['extra']['line']); + } + + $record = $this->normalize($record); + $message = array('message' => $record['message']); + $handleError = false; + if ($record['context']) { + $message['context'] = $record['context']; + $handleError = true; + } + if ($record['extra']) { + $message['extra'] = $record['extra']; + $handleError = true; + } + if (count($message) === 1) { + $message = reset($message); + } + + if (isset($record['context'][self::TABLE])) { + $type = 'TABLE'; + $label = $record['channel'] .': '. $record['message']; + $message = $record['context'][self::TABLE]; + } else { + $type = $this->logLevels[$record['level']]; + $label = $record['channel']; + } + + // Create JSON object describing the appearance of the message in the console + $json = $this->toJson(array( + array( + 'Type' => $type, + 'File' => $file, + 'Line' => $line, + 'Label' => $label, + ), + $message, + ), $handleError); + + // The message itself is a serialization of the above JSON object + it's length + return sprintf( + '%s|%s|', + strlen($json), + $json + ); + } + + public function formatBatch(array $records) + { + throw new \BadMethodCallException('Batch formatting does not make sense for the WildfireFormatter'); + } + + protected function normalize($data, $depth = 0) + { + if (is_object($data) && !$data instanceof \DateTime) { + return $data; + } + + return parent::normalize($data, $depth); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php new file mode 100644 index 0000000..cdd9f7d --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php @@ -0,0 +1,196 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\LineFormatter; +use Monolog\Logger; +use Monolog\ResettableInterface; + +/** + * Base Handler class providing the Handler structure + * + * @author Jordi Boggiano + */ +abstract class AbstractHandler implements HandlerInterface, ResettableInterface +{ + protected $level = Logger::DEBUG; + protected $bubble = true; + + /** + * @var FormatterInterface + */ + protected $formatter; + protected $processors = array(); + + /** + * @param int|string $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($level = Logger::DEBUG, $bubble = true) + { + $this->setLevel($level); + $this->bubble = $bubble; + } + + /** + * {@inheritdoc} + */ + public function isHandling(array $record) + { + return $record['level'] >= $this->level; + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + foreach ($records as $record) { + $this->handle($record); + } + } + + /** + * Closes the handler. + * + * This will be called automatically when the object is destroyed + */ + public function close() + { + } + + /** + * {@inheritdoc} + */ + public function pushProcessor($callback) + { + if (!is_callable($callback)) { + throw new \InvalidArgumentException('Processors must be valid callables (callback or object with an __invoke method), '.var_export($callback, true).' given'); + } + array_unshift($this->processors, $callback); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function popProcessor() + { + if (!$this->processors) { + throw new \LogicException('You tried to pop from an empty processor stack.'); + } + + return array_shift($this->processors); + } + + /** + * {@inheritdoc} + */ + public function setFormatter(FormatterInterface $formatter) + { + $this->formatter = $formatter; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getFormatter() + { + if (!$this->formatter) { + $this->formatter = $this->getDefaultFormatter(); + } + + return $this->formatter; + } + + /** + * Sets minimum logging level at which this handler will be triggered. + * + * @param int|string $level Level or level name + * @return self + */ + public function setLevel($level) + { + $this->level = Logger::toMonologLevel($level); + + return $this; + } + + /** + * Gets minimum logging level at which this handler will be triggered. + * + * @return int + */ + public function getLevel() + { + return $this->level; + } + + /** + * Sets the bubbling behavior. + * + * @param bool $bubble true means that this handler allows bubbling. + * false means that bubbling is not permitted. + * @return self + */ + public function setBubble($bubble) + { + $this->bubble = $bubble; + + return $this; + } + + /** + * Gets the bubbling behavior. + * + * @return bool true means that this handler allows bubbling. + * false means that bubbling is not permitted. + */ + public function getBubble() + { + return $this->bubble; + } + + public function __destruct() + { + try { + $this->close(); + } catch (\Exception $e) { + // do nothing + } catch (\Throwable $e) { + // do nothing + } + } + + public function reset() + { + foreach ($this->processors as $processor) { + if ($processor instanceof ResettableInterface) { + $processor->reset(); + } + } + } + + /** + * Gets the default formatter. + * + * @return FormatterInterface + */ + protected function getDefaultFormatter() + { + return new LineFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php new file mode 100644 index 0000000..e1e8953 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\ResettableInterface; + +/** + * Base Handler class providing the Handler structure + * + * Classes extending it should (in most cases) only implement write($record) + * + * @author Jordi Boggiano + * @author Christophe Coevoet + */ +abstract class AbstractProcessingHandler extends AbstractHandler +{ + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if (!$this->isHandling($record)) { + return false; + } + + $record = $this->processRecord($record); + + $record['formatted'] = $this->getFormatter()->format($record); + + $this->write($record); + + return false === $this->bubble; + } + + /** + * Writes the record down to the log of the implementing handler + * + * @param array $record + * @return void + */ + abstract protected function write(array $record); + + /** + * Processes a record. + * + * @param array $record + * @return array + */ + protected function processRecord(array $record) + { + if ($this->processors) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + } + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php new file mode 100644 index 0000000..8c76aca --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; + +/** + * Common syslog functionality + */ +abstract class AbstractSyslogHandler extends AbstractProcessingHandler +{ + protected $facility; + + /** + * Translates Monolog log levels to syslog log priorities. + */ + protected $logLevels = array( + Logger::DEBUG => LOG_DEBUG, + Logger::INFO => LOG_INFO, + Logger::NOTICE => LOG_NOTICE, + Logger::WARNING => LOG_WARNING, + Logger::ERROR => LOG_ERR, + Logger::CRITICAL => LOG_CRIT, + Logger::ALERT => LOG_ALERT, + Logger::EMERGENCY => LOG_EMERG, + ); + + /** + * List of valid log facility names. + */ + protected $facilities = array( + 'auth' => LOG_AUTH, + 'authpriv' => LOG_AUTHPRIV, + 'cron' => LOG_CRON, + 'daemon' => LOG_DAEMON, + 'kern' => LOG_KERN, + 'lpr' => LOG_LPR, + 'mail' => LOG_MAIL, + 'news' => LOG_NEWS, + 'syslog' => LOG_SYSLOG, + 'user' => LOG_USER, + 'uucp' => LOG_UUCP, + ); + + /** + * @param mixed $facility + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($facility = LOG_USER, $level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + + if (!defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->facilities['local0'] = LOG_LOCAL0; + $this->facilities['local1'] = LOG_LOCAL1; + $this->facilities['local2'] = LOG_LOCAL2; + $this->facilities['local3'] = LOG_LOCAL3; + $this->facilities['local4'] = LOG_LOCAL4; + $this->facilities['local5'] = LOG_LOCAL5; + $this->facilities['local6'] = LOG_LOCAL6; + $this->facilities['local7'] = LOG_LOCAL7; + } else { + $this->facilities['local0'] = 128; // LOG_LOCAL0 + $this->facilities['local1'] = 136; // LOG_LOCAL1 + $this->facilities['local2'] = 144; // LOG_LOCAL2 + $this->facilities['local3'] = 152; // LOG_LOCAL3 + $this->facilities['local4'] = 160; // LOG_LOCAL4 + $this->facilities['local5'] = 168; // LOG_LOCAL5 + $this->facilities['local6'] = 176; // LOG_LOCAL6 + $this->facilities['local7'] = 184; // LOG_LOCAL7 + } + + // convert textual description of facility to syslog constant + if (array_key_exists(strtolower($facility), $this->facilities)) { + $facility = $this->facilities[strtolower($facility)]; + } elseif (!in_array($facility, array_values($this->facilities), true)) { + throw new \UnexpectedValueException('Unknown facility value "'.$facility.'" given'); + } + + $this->facility = $facility; + } + + /** + * {@inheritdoc} + */ + protected function getDefaultFormatter() + { + return new LineFormatter('%channel%.%level_name%: %message% %context% %extra%'); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/AmqpHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/AmqpHandler.php new file mode 100644 index 0000000..e5a46bc --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/AmqpHandler.php @@ -0,0 +1,148 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\JsonFormatter; +use PhpAmqpLib\Message\AMQPMessage; +use PhpAmqpLib\Channel\AMQPChannel; +use AMQPExchange; + +class AmqpHandler extends AbstractProcessingHandler +{ + /** + * @var AMQPExchange|AMQPChannel $exchange + */ + protected $exchange; + + /** + * @var string + */ + protected $exchangeName; + + /** + * @param AMQPExchange|AMQPChannel $exchange AMQPExchange (php AMQP ext) or PHP AMQP lib channel, ready for use + * @param string $exchangeName + * @param int $level + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($exchange, $exchangeName = 'log', $level = Logger::DEBUG, $bubble = true) + { + if ($exchange instanceof AMQPExchange) { + $exchange->setName($exchangeName); + } elseif ($exchange instanceof AMQPChannel) { + $this->exchangeName = $exchangeName; + } else { + throw new \InvalidArgumentException('PhpAmqpLib\Channel\AMQPChannel or AMQPExchange instance required'); + } + $this->exchange = $exchange; + + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record) + { + $data = $record["formatted"]; + $routingKey = $this->getRoutingKey($record); + + if ($this->exchange instanceof AMQPExchange) { + $this->exchange->publish( + $data, + $routingKey, + 0, + array( + 'delivery_mode' => 2, + 'content_type' => 'application/json', + ) + ); + } else { + $this->exchange->basic_publish( + $this->createAmqpMessage($data), + $this->exchangeName, + $routingKey + ); + } + } + + /** + * {@inheritDoc} + */ + public function handleBatch(array $records) + { + if ($this->exchange instanceof AMQPExchange) { + parent::handleBatch($records); + + return; + } + + foreach ($records as $record) { + if (!$this->isHandling($record)) { + continue; + } + + $record = $this->processRecord($record); + $data = $this->getFormatter()->format($record); + + $this->exchange->batch_basic_publish( + $this->createAmqpMessage($data), + $this->exchangeName, + $this->getRoutingKey($record) + ); + } + + $this->exchange->publish_batch(); + } + + /** + * Gets the routing key for the AMQP exchange + * + * @param array $record + * @return string + */ + protected function getRoutingKey(array $record) + { + $routingKey = sprintf( + '%s.%s', + // TODO 2.0 remove substr call + substr($record['level_name'], 0, 4), + $record['channel'] + ); + + return strtolower($routingKey); + } + + /** + * @param string $data + * @return AMQPMessage + */ + private function createAmqpMessage($data) + { + return new AMQPMessage( + (string) $data, + array( + 'delivery_mode' => 2, + 'content_type' => 'application/json', + ) + ); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php new file mode 100644 index 0000000..68feb48 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php @@ -0,0 +1,241 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\LineFormatter; + +/** + * Handler sending logs to browser's javascript console with no browser extension required + * + * @author Olivier Poitrey + */ +class BrowserConsoleHandler extends AbstractProcessingHandler +{ + protected static $initialized = false; + protected static $records = array(); + + /** + * {@inheritDoc} + * + * Formatted output may contain some formatting markers to be transferred to `console.log` using the %c format. + * + * Example of formatted string: + * + * You can do [[blue text]]{color: blue} or [[green background]]{background-color: green; color: white} + */ + protected function getDefaultFormatter() + { + return new LineFormatter('[[%channel%]]{macro: autolabel} [[%level_name%]]{font-weight: bold} %message%'); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record) + { + // Accumulate records + static::$records[] = $record; + + // Register shutdown handler if not already done + if (!static::$initialized) { + static::$initialized = true; + $this->registerShutdownFunction(); + } + } + + /** + * Convert records to javascript console commands and send it to the browser. + * This method is automatically called on PHP shutdown if output is HTML or Javascript. + */ + public static function send() + { + $format = static::getResponseFormat(); + if ($format === 'unknown') { + return; + } + + if (count(static::$records)) { + if ($format === 'html') { + static::writeOutput(''); + } elseif ($format === 'js') { + static::writeOutput(static::generateScript()); + } + static::resetStatic(); + } + } + + public function close() + { + self::resetStatic(); + } + + public function reset() + { + self::resetStatic(); + } + + /** + * Forget all logged records + */ + public static function resetStatic() + { + static::$records = array(); + } + + /** + * Wrapper for register_shutdown_function to allow overriding + */ + protected function registerShutdownFunction() + { + if (PHP_SAPI !== 'cli') { + register_shutdown_function(array('Monolog\Handler\BrowserConsoleHandler', 'send')); + } + } + + /** + * Wrapper for echo to allow overriding + * + * @param string $str + */ + protected static function writeOutput($str) + { + echo $str; + } + + /** + * Checks the format of the response + * + * If Content-Type is set to application/javascript or text/javascript -> js + * If Content-Type is set to text/html, or is unset -> html + * If Content-Type is anything else -> unknown + * + * @return string One of 'js', 'html' or 'unknown' + */ + protected static function getResponseFormat() + { + // Check content type + foreach (headers_list() as $header) { + if (stripos($header, 'content-type:') === 0) { + // This handler only works with HTML and javascript outputs + // text/javascript is obsolete in favour of application/javascript, but still used + if (stripos($header, 'application/javascript') !== false || stripos($header, 'text/javascript') !== false) { + return 'js'; + } + if (stripos($header, 'text/html') === false) { + return 'unknown'; + } + break; + } + } + + return 'html'; + } + + private static function generateScript() + { + $script = array(); + foreach (static::$records as $record) { + $context = static::dump('Context', $record['context']); + $extra = static::dump('Extra', $record['extra']); + + if (empty($context) && empty($extra)) { + $script[] = static::call_array('log', static::handleStyles($record['formatted'])); + } else { + $script = array_merge($script, + array(static::call_array('groupCollapsed', static::handleStyles($record['formatted']))), + $context, + $extra, + array(static::call('groupEnd')) + ); + } + } + + return "(function (c) {if (c && c.groupCollapsed) {\n" . implode("\n", $script) . "\n}})(console);"; + } + + private static function handleStyles($formatted) + { + $args = array(); + $format = '%c' . $formatted; + preg_match_all('/\[\[(.*?)\]\]\{([^}]*)\}/s', $format, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER); + + foreach (array_reverse($matches) as $match) { + $args[] = '"font-weight: normal"'; + $args[] = static::quote(static::handleCustomStyles($match[2][0], $match[1][0])); + + $pos = $match[0][1]; + $format = substr($format, 0, $pos) . '%c' . $match[1][0] . '%c' . substr($format, $pos + strlen($match[0][0])); + } + + $args[] = static::quote('font-weight: normal'); + $args[] = static::quote($format); + + return array_reverse($args); + } + + private static function handleCustomStyles($style, $string) + { + static $colors = array('blue', 'green', 'red', 'magenta', 'orange', 'black', 'grey'); + static $labels = array(); + + return preg_replace_callback('/macro\s*:(.*?)(?:;|$)/', function ($m) use ($string, &$colors, &$labels) { + if (trim($m[1]) === 'autolabel') { + // Format the string as a label with consistent auto assigned background color + if (!isset($labels[$string])) { + $labels[$string] = $colors[count($labels) % count($colors)]; + } + $color = $labels[$string]; + + return "background-color: $color; color: white; border-radius: 3px; padding: 0 2px 0 2px"; + } + + return $m[1]; + }, $style); + } + + private static function dump($title, array $dict) + { + $script = array(); + $dict = array_filter($dict); + if (empty($dict)) { + return $script; + } + $script[] = static::call('log', static::quote('%c%s'), static::quote('font-weight: bold'), static::quote($title)); + foreach ($dict as $key => $value) { + $value = json_encode($value); + if (empty($value)) { + $value = static::quote(''); + } + $script[] = static::call('log', static::quote('%s: %o'), static::quote($key), $value); + } + + return $script; + } + + private static function quote($arg) + { + return '"' . addcslashes($arg, "\"\n\\") . '"'; + } + + private static function call() + { + $args = func_get_args(); + $method = array_shift($args); + + return static::call_array($method, $args); + } + + private static function call_array($method, array $args) + { + return 'c.' . $method . '(' . implode(', ', $args) . ');'; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php new file mode 100644 index 0000000..0957e55 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php @@ -0,0 +1,148 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\ResettableInterface; +use Monolog\Formatter\FormatterInterface; + +/** + * Buffers all records until closing the handler and then pass them as batch. + * + * This is useful for a MailHandler to send only one mail per request instead of + * sending one per log message. + * + * @author Christophe Coevoet + */ +class BufferHandler extends AbstractHandler +{ + protected $handler; + protected $bufferSize = 0; + protected $bufferLimit; + protected $flushOnOverflow; + protected $buffer = array(); + protected $initialized = false; + + /** + * @param HandlerInterface $handler Handler. + * @param int $bufferLimit How many entries should be buffered at most, beyond that the oldest items are removed from the buffer. + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param bool $flushOnOverflow If true, the buffer is flushed when the max size has been reached, by default oldest entries are discarded + */ + public function __construct(HandlerInterface $handler, $bufferLimit = 0, $level = Logger::DEBUG, $bubble = true, $flushOnOverflow = false) + { + parent::__construct($level, $bubble); + $this->handler = $handler; + $this->bufferLimit = (int) $bufferLimit; + $this->flushOnOverflow = $flushOnOverflow; + } + + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if ($record['level'] < $this->level) { + return false; + } + + if (!$this->initialized) { + // __destructor() doesn't get called on Fatal errors + register_shutdown_function(array($this, 'close')); + $this->initialized = true; + } + + if ($this->bufferLimit > 0 && $this->bufferSize === $this->bufferLimit) { + if ($this->flushOnOverflow) { + $this->flush(); + } else { + array_shift($this->buffer); + $this->bufferSize--; + } + } + + if ($this->processors) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + } + + $this->buffer[] = $record; + $this->bufferSize++; + + return false === $this->bubble; + } + + public function flush() + { + if ($this->bufferSize === 0) { + return; + } + + $this->handler->handleBatch($this->buffer); + $this->clear(); + } + + public function __destruct() + { + // suppress the parent behavior since we already have register_shutdown_function() + // to call close(), and the reference contained there will prevent this from being + // GC'd until the end of the request + } + + /** + * {@inheritdoc} + */ + public function close() + { + $this->flush(); + } + + /** + * Clears the buffer without flushing any messages down to the wrapped handler. + */ + public function clear() + { + $this->bufferSize = 0; + $this->buffer = array(); + } + + public function reset() + { + $this->flush(); + + parent::reset(); + + if ($this->handler instanceof ResettableInterface) { + $this->handler->reset(); + } + } + + /** + * {@inheritdoc} + */ + public function setFormatter(FormatterInterface $formatter) + { + $this->handler->setFormatter($formatter); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getFormatter() + { + return $this->handler->getFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php new file mode 100644 index 0000000..47120e5 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php @@ -0,0 +1,212 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\ChromePHPFormatter; +use Monolog\Logger; +use Monolog\Utils; + +/** + * Handler sending logs to the ChromePHP extension (http://www.chromephp.com/) + * + * This also works out of the box with Firefox 43+ + * + * @author Christophe Coevoet + */ +class ChromePHPHandler extends AbstractProcessingHandler +{ + /** + * Version of the extension + */ + const VERSION = '4.0'; + + /** + * Header name + */ + const HEADER_NAME = 'X-ChromeLogger-Data'; + + /** + * Regular expression to detect supported browsers (matches any Chrome, or Firefox 43+) + */ + const USER_AGENT_REGEX = '{\b(?:Chrome/\d+(?:\.\d+)*|HeadlessChrome|Firefox/(?:4[3-9]|[5-9]\d|\d{3,})(?:\.\d)*)\b}'; + + protected static $initialized = false; + + /** + * Tracks whether we sent too much data + * + * Chrome limits the headers to 4KB, so when we sent 3KB we stop sending + * + * @var bool + */ + protected static $overflowed = false; + + protected static $json = array( + 'version' => self::VERSION, + 'columns' => array('label', 'log', 'backtrace', 'type'), + 'rows' => array(), + ); + + protected static $sendHeaders = true; + + /** + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + if (!function_exists('json_encode')) { + throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s ChromePHPHandler'); + } + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + $messages = array(); + + foreach ($records as $record) { + if ($record['level'] < $this->level) { + continue; + } + $messages[] = $this->processRecord($record); + } + + if (!empty($messages)) { + $messages = $this->getFormatter()->formatBatch($messages); + self::$json['rows'] = array_merge(self::$json['rows'], $messages); + $this->send(); + } + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new ChromePHPFormatter(); + } + + /** + * Creates & sends header for a record + * + * @see sendHeader() + * @see send() + * @param array $record + */ + protected function write(array $record) + { + self::$json['rows'][] = $record['formatted']; + + $this->send(); + } + + /** + * Sends the log header + * + * @see sendHeader() + */ + protected function send() + { + if (self::$overflowed || !self::$sendHeaders) { + return; + } + + if (!self::$initialized) { + self::$initialized = true; + + self::$sendHeaders = $this->headersAccepted(); + if (!self::$sendHeaders) { + return; + } + + self::$json['request_uri'] = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''; + } + + $json = Utils::jsonEncode(self::$json, null, true); + $data = base64_encode(utf8_encode($json)); + if (strlen($data) > 3 * 1024) { + self::$overflowed = true; + + $record = array( + 'message' => 'Incomplete logs, chrome header size limit reached', + 'context' => array(), + 'level' => Logger::WARNING, + 'level_name' => Logger::getLevelName(Logger::WARNING), + 'channel' => 'monolog', + 'datetime' => new \DateTime(), + 'extra' => array(), + ); + self::$json['rows'][count(self::$json['rows']) - 1] = $this->getFormatter()->format($record); + $json = Utils::jsonEncode(self::$json, null, true); + $data = base64_encode(utf8_encode($json)); + } + + if (trim($data) !== '') { + $this->sendHeader(self::HEADER_NAME, $data); + } + } + + /** + * Send header string to the client + * + * @param string $header + * @param string $content + */ + protected function sendHeader($header, $content) + { + if (!headers_sent() && self::$sendHeaders) { + header(sprintf('%s: %s', $header, $content)); + } + } + + /** + * Verifies if the headers are accepted by the current user agent + * + * @return bool + */ + protected function headersAccepted() + { + if (empty($_SERVER['HTTP_USER_AGENT'])) { + return false; + } + + return preg_match(self::USER_AGENT_REGEX, $_SERVER['HTTP_USER_AGENT']); + } + + /** + * BC getter for the sendHeaders property that has been made static + */ + public function __get($property) + { + if ('sendHeaders' !== $property) { + throw new \InvalidArgumentException('Undefined property '.$property); + } + + return static::$sendHeaders; + } + + /** + * BC setter for the sendHeaders property that has been made static + */ + public function __set($property, $value) + { + if ('sendHeaders' !== $property) { + throw new \InvalidArgumentException('Undefined property '.$property); + } + + static::$sendHeaders = $value; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php new file mode 100644 index 0000000..cc98697 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\JsonFormatter; +use Monolog\Logger; + +/** + * CouchDB handler + * + * @author Markus Bachmann + */ +class CouchDBHandler extends AbstractProcessingHandler +{ + private $options; + + public function __construct(array $options = array(), $level = Logger::DEBUG, $bubble = true) + { + $this->options = array_merge(array( + 'host' => 'localhost', + 'port' => 5984, + 'dbname' => 'logger', + 'username' => null, + 'password' => null, + ), $options); + + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record) + { + $basicAuth = null; + if ($this->options['username']) { + $basicAuth = sprintf('%s:%s@', $this->options['username'], $this->options['password']); + } + + $url = 'http://'.$basicAuth.$this->options['host'].':'.$this->options['port'].'/'.$this->options['dbname']; + $context = stream_context_create(array( + 'http' => array( + 'method' => 'POST', + 'content' => $record['formatted'], + 'ignore_errors' => true, + 'max_redirects' => 0, + 'header' => 'Content-type: application/json', + ), + )); + + if (false === @file_get_contents($url, null, $context)) { + throw new \RuntimeException(sprintf('Could not connect to %s', $url)); + } + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php new file mode 100644 index 0000000..44928ef --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php @@ -0,0 +1,152 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Utils; + +/** + * Logs to Cube. + * + * @link http://square.github.com/cube/ + * @author Wan Chen + */ +class CubeHandler extends AbstractProcessingHandler +{ + private $udpConnection; + private $httpConnection; + private $scheme; + private $host; + private $port; + private $acceptedSchemes = array('http', 'udp'); + + /** + * Create a Cube handler + * + * @throws \UnexpectedValueException when given url is not a valid url. + * A valid url must consist of three parts : protocol://host:port + * Only valid protocols used by Cube are http and udp + */ + public function __construct($url, $level = Logger::DEBUG, $bubble = true) + { + $urlInfo = parse_url($url); + + if (!isset($urlInfo['scheme'], $urlInfo['host'], $urlInfo['port'])) { + throw new \UnexpectedValueException('URL "'.$url.'" is not valid'); + } + + if (!in_array($urlInfo['scheme'], $this->acceptedSchemes)) { + throw new \UnexpectedValueException( + 'Invalid protocol (' . $urlInfo['scheme'] . ').' + . ' Valid options are ' . implode(', ', $this->acceptedSchemes)); + } + + $this->scheme = $urlInfo['scheme']; + $this->host = $urlInfo['host']; + $this->port = $urlInfo['port']; + + parent::__construct($level, $bubble); + } + + /** + * Establish a connection to an UDP socket + * + * @throws \LogicException when unable to connect to the socket + * @throws MissingExtensionException when there is no socket extension + */ + protected function connectUdp() + { + if (!extension_loaded('sockets')) { + throw new MissingExtensionException('The sockets extension is required to use udp URLs with the CubeHandler'); + } + + $this->udpConnection = socket_create(AF_INET, SOCK_DGRAM, 0); + if (!$this->udpConnection) { + throw new \LogicException('Unable to create a socket'); + } + + if (!socket_connect($this->udpConnection, $this->host, $this->port)) { + throw new \LogicException('Unable to connect to the socket at ' . $this->host . ':' . $this->port); + } + } + + /** + * Establish a connection to a http server + * @throws \LogicException when no curl extension + */ + protected function connectHttp() + { + if (!extension_loaded('curl')) { + throw new \LogicException('The curl extension is needed to use http URLs with the CubeHandler'); + } + + $this->httpConnection = curl_init('http://'.$this->host.':'.$this->port.'/1.0/event/put'); + + if (!$this->httpConnection) { + throw new \LogicException('Unable to connect to ' . $this->host . ':' . $this->port); + } + + curl_setopt($this->httpConnection, CURLOPT_CUSTOMREQUEST, "POST"); + curl_setopt($this->httpConnection, CURLOPT_RETURNTRANSFER, true); + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $date = $record['datetime']; + + $data = array('time' => $date->format('Y-m-d\TH:i:s.uO')); + unset($record['datetime']); + + if (isset($record['context']['type'])) { + $data['type'] = $record['context']['type']; + unset($record['context']['type']); + } else { + $data['type'] = $record['channel']; + } + + $data['data'] = $record['context']; + $data['data']['level'] = $record['level']; + + if ($this->scheme === 'http') { + $this->writeHttp(Utils::jsonEncode($data)); + } else { + $this->writeUdp(Utils::jsonEncode($data)); + } + } + + private function writeUdp($data) + { + if (!$this->udpConnection) { + $this->connectUdp(); + } + + socket_send($this->udpConnection, $data, strlen($data), 0); + } + + private function writeHttp($data) + { + if (!$this->httpConnection) { + $this->connectHttp(); + } + + curl_setopt($this->httpConnection, CURLOPT_POSTFIELDS, '['.$data.']'); + curl_setopt($this->httpConnection, CURLOPT_HTTPHEADER, array( + 'Content-Type: application/json', + 'Content-Length: ' . strlen('['.$data.']'), + )); + + Curl\Util::execute($this->httpConnection, 5, false); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.php b/vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.php new file mode 100644 index 0000000..48d30b3 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\Curl; + +class Util +{ + private static $retriableErrorCodes = array( + CURLE_COULDNT_RESOLVE_HOST, + CURLE_COULDNT_CONNECT, + CURLE_HTTP_NOT_FOUND, + CURLE_READ_ERROR, + CURLE_OPERATION_TIMEOUTED, + CURLE_HTTP_POST_ERROR, + CURLE_SSL_CONNECT_ERROR, + ); + + /** + * Executes a CURL request with optional retries and exception on failure + * + * @param resource $ch curl handler + * @throws \RuntimeException + */ + public static function execute($ch, $retries = 5, $closeAfterDone = true) + { + while ($retries--) { + if (curl_exec($ch) === false) { + $curlErrno = curl_errno($ch); + + if (false === in_array($curlErrno, self::$retriableErrorCodes, true) || !$retries) { + $curlError = curl_error($ch); + + if ($closeAfterDone) { + curl_close($ch); + } + + throw new \RuntimeException(sprintf('Curl error (code %s): %s', $curlErrno, $curlError)); + } + + continue; + } + + if ($closeAfterDone) { + curl_close($ch); + } + break; + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php new file mode 100644 index 0000000..35b55cb --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php @@ -0,0 +1,169 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Simple handler wrapper that deduplicates log records across multiple requests + * + * It also includes the BufferHandler functionality and will buffer + * all messages until the end of the request or flush() is called. + * + * This works by storing all log records' messages above $deduplicationLevel + * to the file specified by $deduplicationStore. When further logs come in at the end of the + * request (or when flush() is called), all those above $deduplicationLevel are checked + * against the existing stored logs. If they match and the timestamps in the stored log is + * not older than $time seconds, the new log record is discarded. If no log record is new, the + * whole data set is discarded. + * + * This is mainly useful in combination with Mail handlers or things like Slack or HipChat handlers + * that send messages to people, to avoid spamming with the same message over and over in case of + * a major component failure like a database server being down which makes all requests fail in the + * same way. + * + * @author Jordi Boggiano + */ +class DeduplicationHandler extends BufferHandler +{ + /** + * @var string + */ + protected $deduplicationStore; + + /** + * @var int + */ + protected $deduplicationLevel; + + /** + * @var int + */ + protected $time; + + /** + * @var bool + */ + private $gc = false; + + /** + * @param HandlerInterface $handler Handler. + * @param string $deduplicationStore The file/path where the deduplication log should be kept + * @param int $deduplicationLevel The minimum logging level for log records to be looked at for deduplication purposes + * @param int $time The period (in seconds) during which duplicate entries should be suppressed after a given log is sent through + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(HandlerInterface $handler, $deduplicationStore = null, $deduplicationLevel = Logger::ERROR, $time = 60, $bubble = true) + { + parent::__construct($handler, 0, Logger::DEBUG, $bubble, false); + + $this->deduplicationStore = $deduplicationStore === null ? sys_get_temp_dir() . '/monolog-dedup-' . substr(md5(__FILE__), 0, 20) .'.log' : $deduplicationStore; + $this->deduplicationLevel = Logger::toMonologLevel($deduplicationLevel); + $this->time = $time; + } + + public function flush() + { + if ($this->bufferSize === 0) { + return; + } + + $passthru = null; + + foreach ($this->buffer as $record) { + if ($record['level'] >= $this->deduplicationLevel) { + + $passthru = $passthru || !$this->isDuplicate($record); + if ($passthru) { + $this->appendRecord($record); + } + } + } + + // default of null is valid as well as if no record matches duplicationLevel we just pass through + if ($passthru === true || $passthru === null) { + $this->handler->handleBatch($this->buffer); + } + + $this->clear(); + + if ($this->gc) { + $this->collectLogs(); + } + } + + private function isDuplicate(array $record) + { + if (!file_exists($this->deduplicationStore)) { + return false; + } + + $store = file($this->deduplicationStore, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + if (!is_array($store)) { + return false; + } + + $yesterday = time() - 86400; + $timestampValidity = $record['datetime']->getTimestamp() - $this->time; + $expectedMessage = preg_replace('{[\r\n].*}', '', $record['message']); + + for ($i = count($store) - 1; $i >= 0; $i--) { + list($timestamp, $level, $message) = explode(':', $store[$i], 3); + + if ($level === $record['level_name'] && $message === $expectedMessage && $timestamp > $timestampValidity) { + return true; + } + + if ($timestamp < $yesterday) { + $this->gc = true; + } + } + + return false; + } + + private function collectLogs() + { + if (!file_exists($this->deduplicationStore)) { + return false; + } + + $handle = fopen($this->deduplicationStore, 'rw+'); + flock($handle, LOCK_EX); + $validLogs = array(); + + $timestampValidity = time() - $this->time; + + while (!feof($handle)) { + $log = fgets($handle); + if (substr($log, 0, 10) >= $timestampValidity) { + $validLogs[] = $log; + } + } + + ftruncate($handle, 0); + rewind($handle); + foreach ($validLogs as $log) { + fwrite($handle, $log); + } + + flock($handle, LOCK_UN); + fclose($handle); + + $this->gc = false; + } + + private function appendRecord(array $record) + { + file_put_contents($this->deduplicationStore, $record['datetime']->getTimestamp() . ':' . $record['level_name'] . ':' . preg_replace('{[\r\n].*}', '', $record['message']) . "\n", FILE_APPEND); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php new file mode 100644 index 0000000..b91ffec --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\NormalizerFormatter; +use Doctrine\CouchDB\CouchDBClient; + +/** + * CouchDB handler for Doctrine CouchDB ODM + * + * @author Markus Bachmann + */ +class DoctrineCouchDBHandler extends AbstractProcessingHandler +{ + private $client; + + public function __construct(CouchDBClient $client, $level = Logger::DEBUG, $bubble = true) + { + $this->client = $client; + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record) + { + $this->client->postDocument($record['formatted']); + } + + protected function getDefaultFormatter() + { + return new NormalizerFormatter; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php new file mode 100644 index 0000000..8846e0a --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Aws\Sdk; +use Aws\DynamoDb\DynamoDbClient; +use Aws\DynamoDb\Marshaler; +use Monolog\Formatter\ScalarFormatter; +use Monolog\Logger; + +/** + * Amazon DynamoDB handler (http://aws.amazon.com/dynamodb/) + * + * @link https://github.com/aws/aws-sdk-php/ + * @author Andrew Lawson + */ +class DynamoDbHandler extends AbstractProcessingHandler +{ + const DATE_FORMAT = 'Y-m-d\TH:i:s.uO'; + + /** + * @var DynamoDbClient + */ + protected $client; + + /** + * @var string + */ + protected $table; + + /** + * @var int + */ + protected $version; + + /** + * @var Marshaler + */ + protected $marshaler; + + /** + * @param DynamoDbClient $client + * @param string $table + * @param int $level + * @param bool $bubble + */ + public function __construct(DynamoDbClient $client, $table, $level = Logger::DEBUG, $bubble = true) + { + if (defined('Aws\Sdk::VERSION') && version_compare(Sdk::VERSION, '3.0', '>=')) { + $this->version = 3; + $this->marshaler = new Marshaler; + } else { + $this->version = 2; + } + + $this->client = $client; + $this->table = $table; + + parent::__construct($level, $bubble); + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $filtered = $this->filterEmptyFields($record['formatted']); + if ($this->version === 3) { + $formatted = $this->marshaler->marshalItem($filtered); + } else { + /** @phpstan-ignore-next-line */ + $formatted = $this->client->formatAttributes($filtered); + } + + $this->client->putItem(array( + 'TableName' => $this->table, + 'Item' => $formatted, + )); + } + + /** + * @param array $record + * @return array + */ + protected function filterEmptyFields(array $record) + { + return array_filter($record, function ($value) { + return !empty($value) || false === $value || 0 === $value; + }); + } + + /** + * {@inheritdoc} + */ + protected function getDefaultFormatter() + { + return new ScalarFormatter(self::DATE_FORMAT); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ElasticSearchHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ElasticSearchHandler.php new file mode 100644 index 0000000..bb0f83e --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ElasticSearchHandler.php @@ -0,0 +1,128 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\ElasticaFormatter; +use Monolog\Logger; +use Elastica\Client; +use Elastica\Exception\ExceptionInterface; + +/** + * Elastic Search handler + * + * Usage example: + * + * $client = new \Elastica\Client(); + * $options = array( + * 'index' => 'elastic_index_name', + * 'type' => 'elastic_doc_type', + * ); + * $handler = new ElasticSearchHandler($client, $options); + * $log = new Logger('application'); + * $log->pushHandler($handler); + * + * @author Jelle Vink + */ +class ElasticSearchHandler extends AbstractProcessingHandler +{ + /** + * @var Client + */ + protected $client; + + /** + * @var array Handler config options + */ + protected $options = array(); + + /** + * @param Client $client Elastica Client object + * @param array $options Handler configuration + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(Client $client, array $options = array(), $level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + $this->client = $client; + $this->options = array_merge( + array( + 'index' => 'monolog', // Elastic index name + 'type' => 'record', // Elastic document type + 'ignore_error' => false, // Suppress Elastica exceptions + ), + $options + ); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record) + { + $this->bulkSend(array($record['formatted'])); + } + + /** + * {@inheritdoc} + */ + public function setFormatter(FormatterInterface $formatter) + { + if ($formatter instanceof ElasticaFormatter) { + return parent::setFormatter($formatter); + } + throw new \InvalidArgumentException('ElasticSearchHandler is only compatible with ElasticaFormatter'); + } + + /** + * Getter options + * @return array + */ + public function getOptions() + { + return $this->options; + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new ElasticaFormatter($this->options['index'], $this->options['type']); + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + $documents = $this->getFormatter()->formatBatch($records); + $this->bulkSend($documents); + } + + /** + * Use Elasticsearch bulk API to send list of documents + * @param array $documents + * @throws \RuntimeException + */ + protected function bulkSend(array $documents) + { + try { + $this->client->addDocuments($documents); + } catch (ExceptionInterface $e) { + if (!$this->options['ignore_error']) { + throw new \RuntimeException("Error sending messages to Elasticsearch", 0, $e); + } + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php new file mode 100644 index 0000000..b2986b0 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\LineFormatter; +use Monolog\Logger; + +/** + * Stores to PHP error_log() handler. + * + * @author Elan Ruusamäe + */ +class ErrorLogHandler extends AbstractProcessingHandler +{ + const OPERATING_SYSTEM = 0; + const SAPI = 4; + + protected $messageType; + protected $expandNewlines; + + /** + * @param int $messageType Says where the error should go. + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param bool $expandNewlines If set to true, newlines in the message will be expanded to be take multiple log entries + */ + public function __construct($messageType = self::OPERATING_SYSTEM, $level = Logger::DEBUG, $bubble = true, $expandNewlines = false) + { + parent::__construct($level, $bubble); + + if (false === in_array($messageType, self::getAvailableTypes())) { + $message = sprintf('The given message type "%s" is not supported', print_r($messageType, true)); + throw new \InvalidArgumentException($message); + } + + $this->messageType = $messageType; + $this->expandNewlines = $expandNewlines; + } + + /** + * @return array With all available types + */ + public static function getAvailableTypes() + { + return array( + self::OPERATING_SYSTEM, + self::SAPI, + ); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new LineFormatter('[%datetime%] %channel%.%level_name%: %message% %context% %extra%'); + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + if ($this->expandNewlines) { + $lines = preg_split('{[\r\n]+}', (string) $record['formatted']); + foreach ($lines as $line) { + error_log($line, $this->messageType); + } + } else { + error_log((string) $record['formatted'], $this->messageType); + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FilterHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FilterHandler.php new file mode 100644 index 0000000..949f227 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FilterHandler.php @@ -0,0 +1,172 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\FormatterInterface; + +/** + * Simple handler wrapper that filters records based on a list of levels + * + * It can be configured with an exact list of levels to allow, or a min/max level. + * + * @author Hennadiy Verkh + * @author Jordi Boggiano + */ +class FilterHandler extends AbstractHandler +{ + /** + * Handler or factory callable($record, $this) + * + * @var callable|\Monolog\Handler\HandlerInterface + */ + protected $handler; + + /** + * Minimum level for logs that are passed to handler + * + * @var int[] + */ + protected $acceptedLevels; + + /** + * Whether the messages that are handled can bubble up the stack or not + * + * @var bool + */ + protected $bubble; + + /** + * @param callable|HandlerInterface $handler Handler or factory callable($record|null, $filterHandler). + * @param int|array $minLevelOrList A list of levels to accept or a minimum level if maxLevel is provided + * @param int $maxLevel Maximum level to accept, only used if $minLevelOrList is not an array + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($handler, $minLevelOrList = Logger::DEBUG, $maxLevel = Logger::EMERGENCY, $bubble = true) + { + $this->handler = $handler; + $this->bubble = $bubble; + $this->setAcceptedLevels($minLevelOrList, $maxLevel); + + if (!$this->handler instanceof HandlerInterface && !is_callable($this->handler)) { + throw new \RuntimeException("The given handler (".json_encode($this->handler).") is not a callable nor a Monolog\Handler\HandlerInterface object"); + } + } + + /** + * @return array + */ + public function getAcceptedLevels() + { + return array_flip($this->acceptedLevels); + } + + /** + * @param int|string|array $minLevelOrList A list of levels to accept or a minimum level or level name if maxLevel is provided + * @param int|string $maxLevel Maximum level or level name to accept, only used if $minLevelOrList is not an array + */ + public function setAcceptedLevels($minLevelOrList = Logger::DEBUG, $maxLevel = Logger::EMERGENCY) + { + if (is_array($minLevelOrList)) { + $acceptedLevels = array_map('Monolog\Logger::toMonologLevel', $minLevelOrList); + } else { + $minLevelOrList = Logger::toMonologLevel($minLevelOrList); + $maxLevel = Logger::toMonologLevel($maxLevel); + $acceptedLevels = array_values(array_filter(Logger::getLevels(), function ($level) use ($minLevelOrList, $maxLevel) { + return $level >= $minLevelOrList && $level <= $maxLevel; + })); + } + $this->acceptedLevels = array_flip($acceptedLevels); + } + + /** + * {@inheritdoc} + */ + public function isHandling(array $record) + { + return isset($this->acceptedLevels[$record['level']]); + } + + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if (!$this->isHandling($record)) { + return false; + } + + if ($this->processors) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + } + + $this->getHandler($record)->handle($record); + + return false === $this->bubble; + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + $filtered = array(); + foreach ($records as $record) { + if ($this->isHandling($record)) { + $filtered[] = $record; + } + } + + if (count($filtered) > 0) { + $this->getHandler($filtered[count($filtered) - 1])->handleBatch($filtered); + } + } + + /** + * Return the nested handler + * + * If the handler was provided as a factory callable, this will trigger the handler's instantiation. + * + * @return HandlerInterface + */ + public function getHandler(array $record = null) + { + if (!$this->handler instanceof HandlerInterface) { + $this->handler = call_user_func($this->handler, $record, $this); + if (!$this->handler instanceof HandlerInterface) { + throw new \RuntimeException("The factory callable should return a HandlerInterface"); + } + } + + return $this->handler; + } + + /** + * {@inheritdoc} + */ + public function setFormatter(FormatterInterface $formatter) + { + $this->getHandler()->setFormatter($formatter); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getFormatter() + { + return $this->getHandler()->getFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php new file mode 100644 index 0000000..aaca12c --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\FingersCrossed; + +/** + * Interface for activation strategies for the FingersCrossedHandler. + * + * @author Johannes M. Schmitt + */ +interface ActivationStrategyInterface +{ + /** + * Returns whether the given record activates the handler. + * + * @param array $record + * @return bool + */ + public function isHandlerActivated(array $record); +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php new file mode 100644 index 0000000..2a2a64d --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\FingersCrossed; + +use Monolog\Logger; + +/** + * Channel and Error level based monolog activation strategy. Allows to trigger activation + * based on level per channel. e.g. trigger activation on level 'ERROR' by default, except + * for records of the 'sql' channel; those should trigger activation on level 'WARN'. + * + * Example: + * + * + * $activationStrategy = new ChannelLevelActivationStrategy( + * Logger::CRITICAL, + * array( + * 'request' => Logger::ALERT, + * 'sensitive' => Logger::ERROR, + * ) + * ); + * $handler = new FingersCrossedHandler(new StreamHandler('php://stderr'), $activationStrategy); + * + * + * @author Mike Meessen + */ +class ChannelLevelActivationStrategy implements ActivationStrategyInterface +{ + private $defaultActionLevel; + private $channelToActionLevel; + + /** + * @param int $defaultActionLevel The default action level to be used if the record's category doesn't match any + * @param array $channelToActionLevel An array that maps channel names to action levels. + */ + public function __construct($defaultActionLevel, $channelToActionLevel = array()) + { + $this->defaultActionLevel = Logger::toMonologLevel($defaultActionLevel); + $this->channelToActionLevel = array_map('Monolog\Logger::toMonologLevel', $channelToActionLevel); + } + + public function isHandlerActivated(array $record) + { + if (isset($this->channelToActionLevel[$record['channel']])) { + return $record['level'] >= $this->channelToActionLevel[$record['channel']]; + } + + return $record['level'] >= $this->defaultActionLevel; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php new file mode 100644 index 0000000..6e63085 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\FingersCrossed; + +use Monolog\Logger; + +/** + * Error level based activation strategy. + * + * @author Johannes M. Schmitt + */ +class ErrorLevelActivationStrategy implements ActivationStrategyInterface +{ + private $actionLevel; + + public function __construct($actionLevel) + { + $this->actionLevel = Logger::toMonologLevel($actionLevel); + } + + public function isHandlerActivated(array $record) + { + return $record['level'] >= $this->actionLevel; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php new file mode 100644 index 0000000..cdabc44 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php @@ -0,0 +1,207 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy; +use Monolog\Handler\FingersCrossed\ActivationStrategyInterface; +use Monolog\Logger; +use Monolog\ResettableInterface; +use Monolog\Formatter\FormatterInterface; + +/** + * Buffers all records until a certain level is reached + * + * The advantage of this approach is that you don't get any clutter in your log files. + * Only requests which actually trigger an error (or whatever your actionLevel is) will be + * in the logs, but they will contain all records, not only those above the level threshold. + * + * You can find the various activation strategies in the + * Monolog\Handler\FingersCrossed\ namespace. + * + * @author Jordi Boggiano + */ +class FingersCrossedHandler extends AbstractHandler +{ + protected $handler; + protected $activationStrategy; + protected $buffering = true; + protected $bufferSize; + protected $buffer = array(); + protected $stopBuffering; + protected $passthruLevel; + + /** + * @param callable|HandlerInterface $handler Handler or factory callable($record|null, $fingersCrossedHandler). + * @param int|ActivationStrategyInterface $activationStrategy Strategy which determines when this handler takes action + * @param int $bufferSize How many entries should be buffered at most, beyond that the oldest items are removed from the buffer. + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param bool $stopBuffering Whether the handler should stop buffering after being triggered (default true) + * @param int $passthruLevel Minimum level to always flush to handler on close, even if strategy not triggered + */ + public function __construct($handler, $activationStrategy = null, $bufferSize = 0, $bubble = true, $stopBuffering = true, $passthruLevel = null) + { + if (null === $activationStrategy) { + $activationStrategy = new ErrorLevelActivationStrategy(Logger::WARNING); + } + + // convert simple int activationStrategy to an object + if (!$activationStrategy instanceof ActivationStrategyInterface) { + $activationStrategy = new ErrorLevelActivationStrategy($activationStrategy); + } + + $this->handler = $handler; + $this->activationStrategy = $activationStrategy; + $this->bufferSize = $bufferSize; + $this->bubble = $bubble; + $this->stopBuffering = $stopBuffering; + + if ($passthruLevel !== null) { + $this->passthruLevel = Logger::toMonologLevel($passthruLevel); + } + + if (!$this->handler instanceof HandlerInterface && !is_callable($this->handler)) { + throw new \RuntimeException("The given handler (".json_encode($this->handler).") is not a callable nor a Monolog\Handler\HandlerInterface object"); + } + } + + /** + * {@inheritdoc} + */ + public function isHandling(array $record) + { + return true; + } + + /** + * Manually activate this logger regardless of the activation strategy + */ + public function activate() + { + if ($this->stopBuffering) { + $this->buffering = false; + } + $this->getHandler(end($this->buffer) ?: null)->handleBatch($this->buffer); + $this->buffer = array(); + } + + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if ($this->processors) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + } + + if ($this->buffering) { + $this->buffer[] = $record; + if ($this->bufferSize > 0 && count($this->buffer) > $this->bufferSize) { + array_shift($this->buffer); + } + if ($this->activationStrategy->isHandlerActivated($record)) { + $this->activate(); + } + } else { + $this->getHandler($record)->handle($record); + } + + return false === $this->bubble; + } + + /** + * {@inheritdoc} + */ + public function close() + { + $this->flushBuffer(); + } + + public function reset() + { + $this->flushBuffer(); + + parent::reset(); + + if ($this->getHandler() instanceof ResettableInterface) { + $this->getHandler()->reset(); + } + } + + /** + * Clears the buffer without flushing any messages down to the wrapped handler. + * + * It also resets the handler to its initial buffering state. + */ + public function clear() + { + $this->buffer = array(); + $this->reset(); + } + + /** + * Resets the state of the handler. Stops forwarding records to the wrapped handler. + */ + private function flushBuffer() + { + if (null !== $this->passthruLevel) { + $level = $this->passthruLevel; + $this->buffer = array_filter($this->buffer, function ($record) use ($level) { + return $record['level'] >= $level; + }); + if (count($this->buffer) > 0) { + $this->getHandler(end($this->buffer) ?: null)->handleBatch($this->buffer); + } + } + + $this->buffer = array(); + $this->buffering = true; + } + + /** + * Return the nested handler + * + * If the handler was provided as a factory callable, this will trigger the handler's instantiation. + * + * @return HandlerInterface + */ + public function getHandler(array $record = null) + { + if (!$this->handler instanceof HandlerInterface) { + $this->handler = call_user_func($this->handler, $record, $this); + if (!$this->handler instanceof HandlerInterface) { + throw new \RuntimeException("The factory callable should return a HandlerInterface"); + } + } + + return $this->handler; + } + + /** + * {@inheritdoc} + */ + public function setFormatter(FormatterInterface $formatter) + { + $this->getHandler()->setFormatter($formatter); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getFormatter() + { + return $this->getHandler()->getFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php new file mode 100644 index 0000000..2a171bd --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php @@ -0,0 +1,195 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\WildfireFormatter; + +/** + * Simple FirePHP Handler (http://www.firephp.org/), which uses the Wildfire protocol. + * + * @author Eric Clemmons (@ericclemmons) + */ +class FirePHPHandler extends AbstractProcessingHandler +{ + /** + * WildFire JSON header message format + */ + const PROTOCOL_URI = 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2'; + + /** + * FirePHP structure for parsing messages & their presentation + */ + const STRUCTURE_URI = 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1'; + + /** + * Must reference a "known" plugin, otherwise headers won't display in FirePHP + */ + const PLUGIN_URI = 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3'; + + /** + * Header prefix for Wildfire to recognize & parse headers + */ + const HEADER_PREFIX = 'X-Wf'; + + /** + * Whether or not Wildfire vendor-specific headers have been generated & sent yet + */ + protected static $initialized = false; + + /** + * Shared static message index between potentially multiple handlers + * @var int + */ + protected static $messageIndex = 1; + + protected static $sendHeaders = true; + + /** + * Base header creation function used by init headers & record headers + * + * @param array $meta Wildfire Plugin, Protocol & Structure Indexes + * @param string $message Log message + * @return array Complete header string ready for the client as key and message as value + */ + protected function createHeader(array $meta, $message) + { + $header = sprintf('%s-%s', self::HEADER_PREFIX, join('-', $meta)); + + return array($header => $message); + } + + /** + * Creates message header from record + * + * @see createHeader() + * @param array $record + * @return array + */ + protected function createRecordHeader(array $record) + { + // Wildfire is extensible to support multiple protocols & plugins in a single request, + // but we're not taking advantage of that (yet), so we're using "1" for simplicity's sake. + return $this->createHeader( + array(1, 1, 1, self::$messageIndex++), + $record['formatted'] + ); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new WildfireFormatter(); + } + + /** + * Wildfire initialization headers to enable message parsing + * + * @see createHeader() + * @see sendHeader() + * @return array + */ + protected function getInitHeaders() + { + // Initial payload consists of required headers for Wildfire + return array_merge( + $this->createHeader(array('Protocol', 1), self::PROTOCOL_URI), + $this->createHeader(array(1, 'Structure', 1), self::STRUCTURE_URI), + $this->createHeader(array(1, 'Plugin', 1), self::PLUGIN_URI) + ); + } + + /** + * Send header string to the client + * + * @param string $header + * @param string $content + */ + protected function sendHeader($header, $content) + { + if (!headers_sent() && self::$sendHeaders) { + header(sprintf('%s: %s', $header, $content)); + } + } + + /** + * Creates & sends header for a record, ensuring init headers have been sent prior + * + * @see sendHeader() + * @see sendInitHeaders() + * @param array $record + */ + protected function write(array $record) + { + if (!self::$sendHeaders) { + return; + } + + // WildFire-specific headers must be sent prior to any messages + if (!self::$initialized) { + self::$initialized = true; + + self::$sendHeaders = $this->headersAccepted(); + if (!self::$sendHeaders) { + return; + } + + foreach ($this->getInitHeaders() as $header => $content) { + $this->sendHeader($header, $content); + } + } + + $header = $this->createRecordHeader($record); + if (trim(current($header)) !== '') { + $this->sendHeader(key($header), current($header)); + } + } + + /** + * Verifies if the headers are accepted by the current user agent + * + * @return bool + */ + protected function headersAccepted() + { + if (!empty($_SERVER['HTTP_USER_AGENT']) && preg_match('{\bFirePHP/\d+\.\d+\b}', $_SERVER['HTTP_USER_AGENT'])) { + return true; + } + + return isset($_SERVER['HTTP_X_FIREPHP_VERSION']); + } + + /** + * BC getter for the sendHeaders property that has been made static + */ + public function __get($property) + { + if ('sendHeaders' !== $property) { + throw new \InvalidArgumentException('Undefined property '.$property); + } + + return static::$sendHeaders; + } + + /** + * BC setter for the sendHeaders property that has been made static + */ + public function __set($property, $value) + { + if ('sendHeaders' !== $property) { + throw new \InvalidArgumentException('Undefined property '.$property); + } + + static::$sendHeaders = $value; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php new file mode 100644 index 0000000..c43c013 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php @@ -0,0 +1,126 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\LineFormatter; +use Monolog\Logger; + +/** + * Sends logs to Fleep.io using Webhook integrations + * + * You'll need a Fleep.io account to use this handler. + * + * @see https://fleep.io/integrations/webhooks/ Fleep Webhooks Documentation + * @author Ando Roots + */ +class FleepHookHandler extends SocketHandler +{ + const FLEEP_HOST = 'fleep.io'; + + const FLEEP_HOOK_URI = '/hook/'; + + /** + * @var string Webhook token (specifies the conversation where logs are sent) + */ + protected $token; + + /** + * Construct a new Fleep.io Handler. + * + * For instructions on how to create a new web hook in your conversations + * see https://fleep.io/integrations/webhooks/ + * + * @param string $token Webhook token + * @param bool|int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @throws MissingExtensionException + */ + public function __construct($token, $level = Logger::DEBUG, $bubble = true) + { + if (!extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP extension is required to use the FleepHookHandler'); + } + + $this->token = $token; + + $connectionString = 'ssl://' . self::FLEEP_HOST . ':443'; + parent::__construct($connectionString, $level, $bubble); + } + + /** + * Returns the default formatter to use with this handler + * + * Overloaded to remove empty context and extra arrays from the end of the log message. + * + * @return LineFormatter + */ + protected function getDefaultFormatter() + { + return new LineFormatter(null, null, true, true); + } + + /** + * Handles a log record + * + * @param array $record + */ + public function write(array $record) + { + parent::write($record); + $this->closeSocket(); + } + + /** + * {@inheritdoc} + * + * @param array $record + * @return string + */ + protected function generateDataStream($record) + { + $content = $this->buildContent($record); + + return $this->buildHeader($content) . $content; + } + + /** + * Builds the header of the API Call + * + * @param string $content + * @return string + */ + private function buildHeader($content) + { + $header = "POST " . self::FLEEP_HOOK_URI . $this->token . " HTTP/1.1\r\n"; + $header .= "Host: " . self::FLEEP_HOST . "\r\n"; + $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; + $header .= "Content-Length: " . strlen($content) . "\r\n"; + $header .= "\r\n"; + + return $header; + } + + /** + * Builds the body of API call + * + * @param array $record + * @return string + */ + private function buildContent($record) + { + $dataArray = array( + 'message' => $record['formatted'], + ); + + return http_build_query($dataArray); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php new file mode 100644 index 0000000..f0f010c --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php @@ -0,0 +1,128 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Utils; +use Monolog\Formatter\FlowdockFormatter; +use Monolog\Formatter\FormatterInterface; + +/** + * Sends notifications through the Flowdock push API + * + * This must be configured with a FlowdockFormatter instance via setFormatter() + * + * Notes: + * API token - Flowdock API token + * + * @author Dominik Liebler + * @see https://www.flowdock.com/api/push + */ +class FlowdockHandler extends SocketHandler +{ + /** + * @var string + */ + protected $apiToken; + + /** + * @param string $apiToken + * @param bool|int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * + * @throws MissingExtensionException if OpenSSL is missing + */ + public function __construct($apiToken, $level = Logger::DEBUG, $bubble = true) + { + if (!extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP extension is required to use the FlowdockHandler'); + } + + parent::__construct('ssl://api.flowdock.com:443', $level, $bubble); + $this->apiToken = $apiToken; + } + + /** + * {@inheritdoc} + */ + public function setFormatter(FormatterInterface $formatter) + { + if (!$formatter instanceof FlowdockFormatter) { + throw new \InvalidArgumentException('The FlowdockHandler requires an instance of Monolog\Formatter\FlowdockFormatter to function correctly'); + } + + return parent::setFormatter($formatter); + } + + /** + * Gets the default formatter. + * + * @return FormatterInterface + */ + protected function getDefaultFormatter() + { + throw new \InvalidArgumentException('The FlowdockHandler must be configured (via setFormatter) with an instance of Monolog\Formatter\FlowdockFormatter to function correctly'); + } + + /** + * {@inheritdoc} + * + * @param array $record + */ + protected function write(array $record) + { + parent::write($record); + + $this->closeSocket(); + } + + /** + * {@inheritdoc} + * + * @param array $record + * @return string + */ + protected function generateDataStream($record) + { + $content = $this->buildContent($record); + + return $this->buildHeader($content) . $content; + } + + /** + * Builds the body of API call + * + * @param array $record + * @return string + */ + private function buildContent($record) + { + return Utils::jsonEncode($record['formatted']['flowdock']); + } + + /** + * Builds the header of the API Call + * + * @param string $content + * @return string + */ + private function buildHeader($content) + { + $header = "POST /v1/messages/team_inbox/" . $this->apiToken . " HTTP/1.1\r\n"; + $header .= "Host: api.flowdock.com\r\n"; + $header .= "Content-Type: application/json\r\n"; + $header .= "Content-Length: " . strlen($content) . "\r\n"; + $header .= "\r\n"; + + return $header; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerInterface.php b/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerInterface.php new file mode 100644 index 0000000..3e2f1b2 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerInterface.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; + +/** + * Interface to describe loggers that have a formatter + * + * This interface is present in monolog 1.x to ease forward compatibility. + * + * @author Jordi Boggiano + */ +interface FormattableHandlerInterface +{ + /** + * Sets the formatter. + * + * @param FormatterInterface $formatter + * @return HandlerInterface self + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface; + + /** + * Gets the formatter. + * + * @return FormatterInterface + */ + public function getFormatter(): FormatterInterface; +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerTrait.php b/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerTrait.php new file mode 100644 index 0000000..e9ec5e7 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerTrait.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\LineFormatter; + +/** + * Helper trait for implementing FormattableInterface + * + * This trait is present in monolog 1.x to ease forward compatibility. + * + * @author Jordi Boggiano + */ +trait FormattableHandlerTrait +{ + /** + * @var FormatterInterface + */ + protected $formatter; + + /** + * {@inheritdoc} + * @suppress PhanTypeMismatchReturn + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + $this->formatter = $formatter; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getFormatter(): FormatterInterface + { + if (!$this->formatter) { + $this->formatter = $this->getDefaultFormatter(); + } + + return $this->formatter; + } + + /** + * Gets the default formatter. + * + * Overwrite this if the LineFormatter is not a good default for your handler. + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new LineFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php new file mode 100644 index 0000000..b6cde7c --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Gelf\IMessagePublisher; +use Gelf\PublisherInterface; +use Gelf\Publisher; +use InvalidArgumentException; +use Monolog\Logger; +use Monolog\Formatter\GelfMessageFormatter; + +/** + * Handler to send messages to a Graylog2 (http://www.graylog2.org) server + * + * @author Matt Lehner + * @author Benjamin Zikarsky + */ +class GelfHandler extends AbstractProcessingHandler +{ + /** + * @var Publisher|PublisherInterface|IMessagePublisher the publisher object that sends the message to the server + */ + protected $publisher; + + /** + * @param PublisherInterface|IMessagePublisher|Publisher $publisher a publisher object + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($publisher, $level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + + if (!$publisher instanceof Publisher && !$publisher instanceof IMessagePublisher && !$publisher instanceof PublisherInterface) { + throw new InvalidArgumentException('Invalid publisher, expected a Gelf\Publisher, Gelf\IMessagePublisher or Gelf\PublisherInterface instance'); + } + + $this->publisher = $publisher; + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $this->publisher->publish($record['formatted']); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new GelfMessageFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php new file mode 100644 index 0000000..0d461f9 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php @@ -0,0 +1,117 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\ResettableInterface; + +/** + * Forwards records to multiple handlers + * + * @author Lenar Lõhmus + */ +class GroupHandler extends AbstractHandler +{ + protected $handlers; + + /** + * @param array $handlers Array of Handlers. + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(array $handlers, $bubble = true) + { + foreach ($handlers as $handler) { + if (!$handler instanceof HandlerInterface) { + throw new \InvalidArgumentException('The first argument of the GroupHandler must be an array of HandlerInterface instances.'); + } + } + + $this->handlers = $handlers; + $this->bubble = $bubble; + } + + /** + * {@inheritdoc} + */ + public function isHandling(array $record) + { + foreach ($this->handlers as $handler) { + if ($handler->isHandling($record)) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if ($this->processors) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + } + + foreach ($this->handlers as $handler) { + $handler->handle($record); + } + + return false === $this->bubble; + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + if ($this->processors) { + $processed = array(); + foreach ($records as $record) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + $processed[] = $record; + } + $records = $processed; + } + + foreach ($this->handlers as $handler) { + $handler->handleBatch($records); + } + } + + public function reset() + { + parent::reset(); + + foreach ($this->handlers as $handler) { + if ($handler instanceof ResettableInterface) { + $handler->reset(); + } + } + } + + /** + * {@inheritdoc} + */ + public function setFormatter(FormatterInterface $formatter) + { + foreach ($this->handlers as $handler) { + $handler->setFormatter($formatter); + } + + return $this; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php b/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php new file mode 100644 index 0000000..8d5a4a0 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; + +/** + * Interface that all Monolog Handlers must implement + * + * @author Jordi Boggiano + */ +interface HandlerInterface +{ + /** + * Checks whether the given record will be handled by this handler. + * + * This is mostly done for performance reasons, to avoid calling processors for nothing. + * + * Handlers should still check the record levels within handle(), returning false in isHandling() + * is no guarantee that handle() will not be called, and isHandling() might not be called + * for a given record. + * + * @param array $record Partial log record containing only a level key + * + * @return bool + */ + public function isHandling(array $record); + + /** + * Handles a record. + * + * All records may be passed to this method, and the handler should discard + * those that it does not want to handle. + * + * The return value of this function controls the bubbling process of the handler stack. + * Unless the bubbling is interrupted (by returning true), the Logger class will keep on + * calling further handlers in the stack with a given log record. + * + * @param array $record The record to handle + * @return bool true means that this handler handled the record, and that bubbling is not permitted. + * false means the record was either not processed or that this handler allows bubbling. + */ + public function handle(array $record); + + /** + * Handles a set of records at once. + * + * @param array $records The records to handle (an array of record arrays) + */ + public function handleBatch(array $records); + + /** + * Adds a processor in the stack. + * + * @param callable $callback + * @return self + */ + public function pushProcessor($callback); + + /** + * Removes the processor on top of the stack and returns it. + * + * @return callable + */ + public function popProcessor(); + + /** + * Sets the formatter. + * + * @param FormatterInterface $formatter + * @return self + */ + public function setFormatter(FormatterInterface $formatter); + + /** + * Gets the formatter. + * + * @return FormatterInterface + */ + public function getFormatter(); +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php b/vendor/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php new file mode 100644 index 0000000..55e6498 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\ResettableInterface; +use Monolog\Formatter\FormatterInterface; + +/** + * This simple wrapper class can be used to extend handlers functionality. + * + * Example: A custom filtering that can be applied to any handler. + * + * Inherit from this class and override handle() like this: + * + * public function handle(array $record) + * { + * if ($record meets certain conditions) { + * return false; + * } + * return $this->handler->handle($record); + * } + * + * @author Alexey Karapetov + */ +class HandlerWrapper implements HandlerInterface, ResettableInterface +{ + /** + * @var HandlerInterface + */ + protected $handler; + + /** + * HandlerWrapper constructor. + * @param HandlerInterface $handler + */ + public function __construct(HandlerInterface $handler) + { + $this->handler = $handler; + } + + /** + * {@inheritdoc} + */ + public function isHandling(array $record) + { + return $this->handler->isHandling($record); + } + + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + return $this->handler->handle($record); + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + return $this->handler->handleBatch($records); + } + + /** + * {@inheritdoc} + */ + public function pushProcessor($callback) + { + $this->handler->pushProcessor($callback); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function popProcessor() + { + return $this->handler->popProcessor(); + } + + /** + * {@inheritdoc} + */ + public function setFormatter(FormatterInterface $formatter) + { + $this->handler->setFormatter($formatter); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getFormatter() + { + return $this->handler->getFormatter(); + } + + public function reset() + { + if ($this->handler instanceof ResettableInterface) { + return $this->handler->reset(); + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/HipChatHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/HipChatHandler.php new file mode 100644 index 0000000..30258e3 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/HipChatHandler.php @@ -0,0 +1,367 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Sends notifications through the hipchat api to a hipchat room + * + * Notes: + * API token - HipChat API token + * Room - HipChat Room Id or name, where messages are sent + * Name - Name used to send the message (from) + * notify - Should the message trigger a notification in the clients + * version - The API version to use (HipChatHandler::API_V1 | HipChatHandler::API_V2) + * + * @author Rafael Dohms + * @see https://www.hipchat.com/docs/api + */ +class HipChatHandler extends SocketHandler +{ + /** + * Use API version 1 + */ + const API_V1 = 'v1'; + + /** + * Use API version v2 + */ + const API_V2 = 'v2'; + + /** + * The maximum allowed length for the name used in the "from" field. + */ + const MAXIMUM_NAME_LENGTH = 15; + + /** + * The maximum allowed length for the message. + */ + const MAXIMUM_MESSAGE_LENGTH = 9500; + + /** + * @var string + */ + private $token; + + /** + * @var string + */ + private $room; + + /** + * @var string + */ + private $name; + + /** + * @var bool + */ + private $notify; + + /** + * @var string + */ + private $format; + + /** + * @var string + */ + private $host; + + /** + * @var string + */ + private $version; + + /** + * @param string $token HipChat API Token + * @param string $room The room that should be alerted of the message (Id or Name) + * @param string $name Name used in the "from" field. + * @param bool $notify Trigger a notification in clients or not + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param bool $useSSL Whether to connect via SSL. + * @param string $format The format of the messages (default to text, can be set to html if you have html in the messages) + * @param string $host The HipChat server hostname. + * @param string $version The HipChat API version (default HipChatHandler::API_V1) + */ + public function __construct($token, $room, $name = 'Monolog', $notify = false, $level = Logger::CRITICAL, $bubble = true, $useSSL = true, $format = 'text', $host = 'api.hipchat.com', $version = self::API_V1) + { + @trigger_error('The Monolog\Handler\HipChatHandler class is deprecated. You should migrate to Slack and the SlackWebhookHandler / SlackbotHandler, see https://www.atlassian.com/partnerships/slack', E_USER_DEPRECATED); + + if ($version == self::API_V1 && !$this->validateStringLength($name, static::MAXIMUM_NAME_LENGTH)) { + throw new \InvalidArgumentException('The supplied name is too long. HipChat\'s v1 API supports names up to 15 UTF-8 characters.'); + } + + $connectionString = $useSSL ? 'ssl://'.$host.':443' : $host.':80'; + parent::__construct($connectionString, $level, $bubble); + + $this->token = $token; + $this->name = $name; + $this->notify = $notify; + $this->room = $room; + $this->format = $format; + $this->host = $host; + $this->version = $version; + } + + /** + * {@inheritdoc} + * + * @param array $record + * @return string + */ + protected function generateDataStream($record) + { + $content = $this->buildContent($record); + + return $this->buildHeader($content) . $content; + } + + /** + * Builds the body of API call + * + * @param array $record + * @return string + */ + private function buildContent($record) + { + $dataArray = array( + 'notify' => $this->version == self::API_V1 ? + ($this->notify ? 1 : 0) : + ($this->notify ? 'true' : 'false'), + 'message' => $record['formatted'], + 'message_format' => $this->format, + 'color' => $this->getAlertColor($record['level']), + ); + + if (!$this->validateStringLength($dataArray['message'], static::MAXIMUM_MESSAGE_LENGTH)) { + if (function_exists('mb_substr')) { + $dataArray['message'] = mb_substr($dataArray['message'], 0, static::MAXIMUM_MESSAGE_LENGTH).' [truncated]'; + } else { + $dataArray['message'] = substr($dataArray['message'], 0, static::MAXIMUM_MESSAGE_LENGTH).' [truncated]'; + } + } + + // if we are using the legacy API then we need to send some additional information + if ($this->version == self::API_V1) { + $dataArray['room_id'] = $this->room; + } + + // append the sender name if it is set + // always append it if we use the v1 api (it is required in v1) + if ($this->version == self::API_V1 || $this->name !== null) { + $dataArray['from'] = (string) $this->name; + } + + return http_build_query($dataArray); + } + + /** + * Builds the header of the API Call + * + * @param string $content + * @return string + */ + private function buildHeader($content) + { + if ($this->version == self::API_V1) { + $header = "POST /v1/rooms/message?format=json&auth_token={$this->token} HTTP/1.1\r\n"; + } else { + // needed for rooms with special (spaces, etc) characters in the name + $room = rawurlencode($this->room); + $header = "POST /v2/room/{$room}/notification?auth_token={$this->token} HTTP/1.1\r\n"; + } + + $header .= "Host: {$this->host}\r\n"; + $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; + $header .= "Content-Length: " . strlen($content) . "\r\n"; + $header .= "\r\n"; + + return $header; + } + + /** + * Assigns a color to each level of log records. + * + * @param int $level + * @return string + */ + protected function getAlertColor($level) + { + switch (true) { + case $level >= Logger::ERROR: + return 'red'; + case $level >= Logger::WARNING: + return 'yellow'; + case $level >= Logger::INFO: + return 'green'; + case $level == Logger::DEBUG: + return 'gray'; + default: + return 'yellow'; + } + } + + /** + * {@inheritdoc} + * + * @param array $record + */ + protected function write(array $record) + { + parent::write($record); + $this->finalizeWrite(); + } + + /** + * Finalizes the request by reading some bytes and then closing the socket + * + * If we do not read some but close the socket too early, hipchat sometimes + * drops the request entirely. + */ + protected function finalizeWrite() + { + $res = $this->getResource(); + if (is_resource($res)) { + @fread($res, 2048); + } + $this->closeSocket(); + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + if (count($records) == 0) { + return true; + } + + $batchRecords = $this->combineRecords($records); + + $handled = false; + foreach ($batchRecords as $batchRecord) { + if ($this->isHandling($batchRecord)) { + $this->write($batchRecord); + $handled = true; + } + } + + if (!$handled) { + return false; + } + + return false === $this->bubble; + } + + /** + * Combines multiple records into one. Error level of the combined record + * will be the highest level from the given records. Datetime will be taken + * from the first record. + * + * @param array $records + * @return array + */ + private function combineRecords(array $records) + { + $batchRecord = null; + $batchRecords = array(); + $messages = array(); + $formattedMessages = array(); + $level = 0; + $levelName = null; + $datetime = null; + + foreach ($records as $record) { + $record = $this->processRecord($record); + + if ($record['level'] > $level) { + $level = $record['level']; + $levelName = $record['level_name']; + } + + if (null === $datetime) { + $datetime = $record['datetime']; + } + + $messages[] = $record['message']; + $messageStr = implode(PHP_EOL, $messages); + $formattedMessages[] = $this->getFormatter()->format($record); + $formattedMessageStr = implode('', $formattedMessages); + + $batchRecord = array( + 'message' => $messageStr, + 'formatted' => $formattedMessageStr, + 'context' => array(), + 'extra' => array(), + ); + + if (!$this->validateStringLength($batchRecord['formatted'], static::MAXIMUM_MESSAGE_LENGTH)) { + // Pop the last message and implode the remaining messages + $lastMessage = array_pop($messages); + $lastFormattedMessage = array_pop($formattedMessages); + $batchRecord['message'] = implode(PHP_EOL, $messages); + $batchRecord['formatted'] = implode('', $formattedMessages); + + $batchRecords[] = $batchRecord; + $messages = array($lastMessage); + $formattedMessages = array($lastFormattedMessage); + + $batchRecord = null; + } + } + + if (null !== $batchRecord) { + $batchRecords[] = $batchRecord; + } + + // Set the max level and datetime for all records + foreach ($batchRecords as &$batchRecord) { + $batchRecord = array_merge( + $batchRecord, + array( + 'level' => $level, + 'level_name' => $levelName, + 'datetime' => $datetime, + ) + ); + } + + return $batchRecords; + } + + /** + * Validates the length of a string. + * + * If the `mb_strlen()` function is available, it will use that, as HipChat + * allows UTF-8 characters. Otherwise, it will fall back to `strlen()`. + * + * Note that this might cause false failures in the specific case of using + * a valid name with less than 16 characters, but 16 or more bytes, on a + * system where `mb_strlen()` is unavailable. + * + * @param string $str + * @param int $length + * + * @return bool + */ + private function validateStringLength($str, $length) + { + if (function_exists('mb_strlen')) { + return (mb_strlen($str) <= $length); + } + + return (strlen($str) <= $length); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php new file mode 100644 index 0000000..f4d3b97 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Utils; + +/** + * IFTTTHandler uses cURL to trigger IFTTT Maker actions + * + * Register a secret key and trigger/event name at https://ifttt.com/maker + * + * value1 will be the channel from monolog's Logger constructor, + * value2 will be the level name (ERROR, WARNING, ..) + * value3 will be the log record's message + * + * @author Nehal Patel + */ +class IFTTTHandler extends AbstractProcessingHandler +{ + private $eventName; + private $secretKey; + + /** + * @param string $eventName The name of the IFTTT Maker event that should be triggered + * @param string $secretKey A valid IFTTT secret key + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($eventName, $secretKey, $level = Logger::ERROR, $bubble = true) + { + $this->eventName = $eventName; + $this->secretKey = $secretKey; + + parent::__construct($level, $bubble); + } + + /** + * {@inheritdoc} + */ + public function write(array $record) + { + $postData = array( + "value1" => $record["channel"], + "value2" => $record["level_name"], + "value3" => $record["message"], + ); + $postString = Utils::jsonEncode($postData); + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, "https://maker.ifttt.com/trigger/" . $this->eventName . "/with/key/" . $this->secretKey); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $postString); + curl_setopt($ch, CURLOPT_HTTPHEADER, array( + "Content-Type: application/json", + )); + + Curl\Util::execute($ch); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/InsightOpsHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/InsightOpsHandler.php new file mode 100644 index 0000000..8f683dc --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/InsightOpsHandler.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + + namespace Monolog\Handler; + + use Monolog\Logger; + +/** + * Inspired on LogEntriesHandler. + * + * @author Robert Kaufmann III + * @author Gabriel Machado + */ +class InsightOpsHandler extends SocketHandler +{ + /** + * @var string + */ + protected $logToken; + + /** + * @param string $token Log token supplied by InsightOps + * @param string $region Region where InsightOps account is hosted. Could be 'us' or 'eu'. + * @param bool $useSSL Whether or not SSL encryption should be used + * @param int $level The minimum logging level to trigger this handler + * @param bool $bubble Whether or not messages that are handled should bubble up the stack. + * + * @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing + */ + public function __construct($token, $region = 'us', $useSSL = true, $level = Logger::DEBUG, $bubble = true) + { + if ($useSSL && !extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP plugin is required to use SSL encrypted connection for InsightOpsHandler'); + } + + $endpoint = $useSSL + ? 'ssl://' . $region . '.data.logs.insight.rapid7.com:443' + : $region . '.data.logs.insight.rapid7.com:80'; + + parent::__construct($endpoint, $level, $bubble); + $this->logToken = $token; + } + + /** + * {@inheritdoc} + * + * @param array $record + * @return string + */ + protected function generateDataStream($record) + { + return $this->logToken . ' ' . $record['formatted']; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php new file mode 100644 index 0000000..ea89fb3 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * @author Robert Kaufmann III + */ +class LogEntriesHandler extends SocketHandler +{ + /** + * @var string + */ + protected $logToken; + + /** + * @param string $token Log token supplied by LogEntries + * @param bool $useSSL Whether or not SSL encryption should be used. + * @param int $level The minimum logging level to trigger this handler + * @param bool $bubble Whether or not messages that are handled should bubble up the stack. + * + * @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing + */ + public function __construct($token, $useSSL = true, $level = Logger::DEBUG, $bubble = true, $host = 'data.logentries.com') + { + if ($useSSL && !extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP plugin is required to use SSL encrypted connection for LogEntriesHandler'); + } + + $endpoint = $useSSL ? 'ssl://' . $host . ':443' : $host . ':80'; + parent::__construct($endpoint, $level, $bubble); + $this->logToken = $token; + } + + /** + * {@inheritdoc} + * + * @param array $record + * @return string + */ + protected function generateDataStream($record) + { + return $this->logToken . ' ' . $record['formatted']; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.php new file mode 100644 index 0000000..bcd62e1 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\LogglyFormatter; + +/** + * Sends errors to Loggly. + * + * @author Przemek Sobstel + * @author Adam Pancutt + * @author Gregory Barchard + */ +class LogglyHandler extends AbstractProcessingHandler +{ + const HOST = 'logs-01.loggly.com'; + const ENDPOINT_SINGLE = 'inputs'; + const ENDPOINT_BATCH = 'bulk'; + + protected $token; + + protected $tag = array(); + + public function __construct($token, $level = Logger::DEBUG, $bubble = true) + { + if (!extension_loaded('curl')) { + throw new \LogicException('The curl extension is needed to use the LogglyHandler'); + } + + $this->token = $token; + + parent::__construct($level, $bubble); + } + + public function setTag($tag) + { + $tag = !empty($tag) ? $tag : array(); + $this->tag = is_array($tag) ? $tag : array($tag); + } + + public function addTag($tag) + { + if (!empty($tag)) { + $tag = is_array($tag) ? $tag : array($tag); + $this->tag = array_unique(array_merge($this->tag, $tag)); + } + } + + protected function write(array $record) + { + $this->send($record["formatted"], self::ENDPOINT_SINGLE); + } + + public function handleBatch(array $records) + { + $level = $this->level; + + $records = array_filter($records, function ($record) use ($level) { + return ($record['level'] >= $level); + }); + + if ($records) { + $this->send($this->getFormatter()->formatBatch($records), self::ENDPOINT_BATCH); + } + } + + protected function send($data, $endpoint) + { + $url = sprintf("https://%s/%s/%s/", self::HOST, $endpoint, $this->token); + + $headers = array('Content-Type: application/json'); + + if (!empty($this->tag)) { + $headers[] = 'X-LOGGLY-TAG: '.implode(',', $this->tag); + } + + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $data); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + + Curl\Util::execute($ch); + } + + protected function getDefaultFormatter() + { + return new LogglyFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php new file mode 100644 index 0000000..9e23283 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * Base class for all mail handlers + * + * @author Gyula Sallai + */ +abstract class MailHandler extends AbstractProcessingHandler +{ + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + $messages = array(); + + foreach ($records as $record) { + if ($record['level'] < $this->level) { + continue; + } + $messages[] = $this->processRecord($record); + } + + if (!empty($messages)) { + $this->send((string) $this->getFormatter()->formatBatch($messages), $messages); + } + } + + /** + * Send a mail with the given content + * + * @param string $content formatted email body to be sent + * @param array $records the array of log records that formed this content + */ + abstract protected function send($content, array $records); + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $this->send((string) $record['formatted'], array($record)); + } + + protected function getHighestRecord(array $records) + { + $highestRecord = null; + foreach ($records as $record) { + if ($highestRecord === null || $highestRecord['level'] < $record['level']) { + $highestRecord = $record; + } + } + + return $highestRecord; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.php new file mode 100644 index 0000000..3f0956a --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * MandrillHandler uses cURL to send the emails to the Mandrill API + * + * @author Adam Nicholson + */ +class MandrillHandler extends MailHandler +{ + protected $message; + protected $apiKey; + + /** + * @param string $apiKey A valid Mandrill API key + * @param callable|\Swift_Message $message An example message for real messages, only the body will be replaced + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($apiKey, $message, $level = Logger::ERROR, $bubble = true) + { + parent::__construct($level, $bubble); + + if (!$message instanceof \Swift_Message && is_callable($message)) { + $message = call_user_func($message); + } + if (!$message instanceof \Swift_Message) { + throw new \InvalidArgumentException('You must provide either a Swift_Message instance or a callable returning it'); + } + $this->message = $message; + $this->apiKey = $apiKey; + } + + /** + * {@inheritdoc} + */ + protected function send($content, array $records) + { + $message = clone $this->message; + $message->setBody($content); + $message->setDate(time()); + + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, 'https://mandrillapp.com/api/1.0/messages/send-raw.json'); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query(array( + 'key' => $this->apiKey, + 'raw_message' => (string) $message, + 'async' => false, + ))); + + Curl\Util::execute($ch); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php b/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php new file mode 100644 index 0000000..4724a7e --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * Exception can be thrown if an extension for an handler is missing + * + * @author Christian Bergau + */ +class MissingExtensionException extends \Exception +{ +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php new file mode 100644 index 0000000..56fe755 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\NormalizerFormatter; + +/** + * Logs to a MongoDB database. + * + * usage example: + * + * $log = new Logger('application'); + * $mongodb = new MongoDBHandler(new \Mongo("mongodb://localhost:27017"), "logs", "prod"); + * $log->pushHandler($mongodb); + * + * @author Thomas Tourlourat + */ +class MongoDBHandler extends AbstractProcessingHandler +{ + protected $mongoCollection; + + public function __construct($mongo, $database, $collection, $level = Logger::DEBUG, $bubble = true) + { + if (!($mongo instanceof \MongoClient || $mongo instanceof \Mongo || $mongo instanceof \MongoDB\Client)) { + throw new \InvalidArgumentException('MongoClient, Mongo or MongoDB\Client instance required'); + } + + $this->mongoCollection = $mongo->selectCollection($database, $collection); + + parent::__construct($level, $bubble); + } + + protected function write(array $record) + { + if ($this->mongoCollection instanceof \MongoDB\Collection) { + $this->mongoCollection->insertOne($record["formatted"]); + } else { + $this->mongoCollection->save($record["formatted"]); + } + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new NormalizerFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php new file mode 100644 index 0000000..d7807fd --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php @@ -0,0 +1,185 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; + +/** + * NativeMailerHandler uses the mail() function to send the emails + * + * @author Christophe Coevoet + * @author Mark Garrett + */ +class NativeMailerHandler extends MailHandler +{ + /** + * The email addresses to which the message will be sent + * @var array + */ + protected $to; + + /** + * The subject of the email + * @var string + */ + protected $subject; + + /** + * Optional headers for the message + * @var array + */ + protected $headers = array(); + + /** + * Optional parameters for the message + * @var array + */ + protected $parameters = array(); + + /** + * The wordwrap length for the message + * @var int + */ + protected $maxColumnWidth; + + /** + * The Content-type for the message + * @var string + */ + protected $contentType = 'text/plain'; + + /** + * The encoding for the message + * @var string + */ + protected $encoding = 'utf-8'; + + /** + * @param string|array $to The receiver of the mail + * @param string $subject The subject of the mail + * @param string $from The sender of the mail + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param int $maxColumnWidth The maximum column width that the message lines will have + */ + public function __construct($to, $subject, $from, $level = Logger::ERROR, $bubble = true, $maxColumnWidth = 70) + { + parent::__construct($level, $bubble); + $this->to = is_array($to) ? $to : array($to); + $this->subject = $subject; + $this->addHeader(sprintf('From: %s', $from)); + $this->maxColumnWidth = $maxColumnWidth; + } + + /** + * Add headers to the message + * + * @param string|array $headers Custom added headers + * @return self + */ + public function addHeader($headers) + { + foreach ((array) $headers as $header) { + if (strpos($header, "\n") !== false || strpos($header, "\r") !== false) { + throw new \InvalidArgumentException('Headers can not contain newline characters for security reasons'); + } + $this->headers[] = $header; + } + + return $this; + } + + /** + * Add parameters to the message + * + * @param string|array $parameters Custom added parameters + * @return self + */ + public function addParameter($parameters) + { + $this->parameters = array_merge($this->parameters, (array) $parameters); + + return $this; + } + + /** + * {@inheritdoc} + */ + protected function send($content, array $records) + { + $content = wordwrap($content, $this->maxColumnWidth); + $headers = ltrim(implode("\r\n", $this->headers) . "\r\n", "\r\n"); + $headers .= 'Content-type: ' . $this->getContentType() . '; charset=' . $this->getEncoding() . "\r\n"; + if ($this->getContentType() == 'text/html' && false === strpos($headers, 'MIME-Version:')) { + $headers .= 'MIME-Version: 1.0' . "\r\n"; + } + + $subject = $this->subject; + if ($records) { + $subjectFormatter = new LineFormatter($this->subject); + $subject = $subjectFormatter->format($this->getHighestRecord($records)); + } + + $parameters = implode(' ', $this->parameters); + foreach ($this->to as $to) { + mail($to, $subject, $content, $headers, $parameters); + } + } + + /** + * @return string $contentType + */ + public function getContentType() + { + return $this->contentType; + } + + /** + * @return string $encoding + */ + public function getEncoding() + { + return $this->encoding; + } + + /** + * @param string $contentType The content type of the email - Defaults to text/plain. Use text/html for HTML + * messages. + * @return self + */ + public function setContentType($contentType) + { + if (strpos($contentType, "\n") !== false || strpos($contentType, "\r") !== false) { + throw new \InvalidArgumentException('The content type can not contain newline characters to prevent email header injection'); + } + + $this->contentType = $contentType; + + return $this; + } + + /** + * @param string $encoding + * @return self + */ + public function setEncoding($encoding) + { + if (strpos($encoding, "\n") !== false || strpos($encoding, "\r") !== false) { + throw new \InvalidArgumentException('The encoding can not contain newline characters to prevent email header injection'); + } + + $this->encoding = $encoding; + + return $this; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php new file mode 100644 index 0000000..64dc138 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php @@ -0,0 +1,205 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Utils; +use Monolog\Formatter\NormalizerFormatter; + +/** + * Class to record a log on a NewRelic application. + * Enabling New Relic High Security mode may prevent capture of useful information. + * + * This handler requires a NormalizerFormatter to function and expects an array in $record['formatted'] + * + * @see https://docs.newrelic.com/docs/agents/php-agent + * @see https://docs.newrelic.com/docs/accounts-partnerships/accounts/security/high-security + */ +class NewRelicHandler extends AbstractProcessingHandler +{ + /** + * Name of the New Relic application that will receive logs from this handler. + * + * @var string + */ + protected $appName; + + /** + * Name of the current transaction + * + * @var string + */ + protected $transactionName; + + /** + * Some context and extra data is passed into the handler as arrays of values. Do we send them as is + * (useful if we are using the API), or explode them for display on the NewRelic RPM website? + * + * @var bool + */ + protected $explodeArrays; + + /** + * {@inheritDoc} + * + * @param string $appName + * @param bool $explodeArrays + * @param string $transactionName + */ + public function __construct( + $level = Logger::ERROR, + $bubble = true, + $appName = null, + $explodeArrays = false, + $transactionName = null + ) { + parent::__construct($level, $bubble); + + $this->appName = $appName; + $this->explodeArrays = $explodeArrays; + $this->transactionName = $transactionName; + } + + /** + * {@inheritDoc} + */ + protected function write(array $record) + { + if (!$this->isNewRelicEnabled()) { + throw new MissingExtensionException('The newrelic PHP extension is required to use the NewRelicHandler'); + } + + if ($appName = $this->getAppName($record['context'])) { + $this->setNewRelicAppName($appName); + } + + if ($transactionName = $this->getTransactionName($record['context'])) { + $this->setNewRelicTransactionName($transactionName); + unset($record['formatted']['context']['transaction_name']); + } + + if (isset($record['context']['exception']) && ($record['context']['exception'] instanceof \Exception || (PHP_VERSION_ID >= 70000 && $record['context']['exception'] instanceof \Throwable))) { + newrelic_notice_error($record['message'], $record['context']['exception']); + unset($record['formatted']['context']['exception']); + } else { + newrelic_notice_error($record['message']); + } + + if (isset($record['formatted']['context']) && is_array($record['formatted']['context'])) { + foreach ($record['formatted']['context'] as $key => $parameter) { + if (is_array($parameter) && $this->explodeArrays) { + foreach ($parameter as $paramKey => $paramValue) { + $this->setNewRelicParameter('context_' . $key . '_' . $paramKey, $paramValue); + } + } else { + $this->setNewRelicParameter('context_' . $key, $parameter); + } + } + } + + if (isset($record['formatted']['extra']) && is_array($record['formatted']['extra'])) { + foreach ($record['formatted']['extra'] as $key => $parameter) { + if (is_array($parameter) && $this->explodeArrays) { + foreach ($parameter as $paramKey => $paramValue) { + $this->setNewRelicParameter('extra_' . $key . '_' . $paramKey, $paramValue); + } + } else { + $this->setNewRelicParameter('extra_' . $key, $parameter); + } + } + } + } + + /** + * Checks whether the NewRelic extension is enabled in the system. + * + * @return bool + */ + protected function isNewRelicEnabled() + { + return extension_loaded('newrelic'); + } + + /** + * Returns the appname where this log should be sent. Each log can override the default appname, set in this + * handler's constructor, by providing the appname in it's context. + * + * @param array $context + * @return null|string + */ + protected function getAppName(array $context) + { + if (isset($context['appname'])) { + return $context['appname']; + } + + return $this->appName; + } + + /** + * Returns the name of the current transaction. Each log can override the default transaction name, set in this + * handler's constructor, by providing the transaction_name in it's context + * + * @param array $context + * + * @return null|string + */ + protected function getTransactionName(array $context) + { + if (isset($context['transaction_name'])) { + return $context['transaction_name']; + } + + return $this->transactionName; + } + + /** + * Sets the NewRelic application that should receive this log. + * + * @param string $appName + */ + protected function setNewRelicAppName($appName) + { + newrelic_set_appname($appName); + } + + /** + * Overwrites the name of the current transaction + * + * @param string $transactionName + */ + protected function setNewRelicTransactionName($transactionName) + { + newrelic_name_transaction($transactionName); + } + + /** + * @param string $key + * @param mixed $value + */ + protected function setNewRelicParameter($key, $value) + { + if (null === $value || is_scalar($value)) { + newrelic_add_custom_parameter($key, $value); + } else { + newrelic_add_custom_parameter($key, Utils::jsonEncode($value, null, true)); + } + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new NormalizerFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php new file mode 100644 index 0000000..4b84588 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Blackhole + * + * Any record it can handle will be thrown away. This can be used + * to put on top of an existing stack to override it temporarily. + * + * @author Jordi Boggiano + */ +class NullHandler extends AbstractHandler +{ + /** + * @param int $level The minimum logging level at which this handler will be triggered + */ + public function __construct($level = Logger::DEBUG) + { + parent::__construct($level, false); + } + + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if ($record['level'] < $this->level) { + return false; + } + + return true; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php new file mode 100644 index 0000000..d0a8b43 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php @@ -0,0 +1,243 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Exception; +use Monolog\Formatter\LineFormatter; +use Monolog\Logger; +use Monolog\Utils; +use PhpConsole\Connector; +use PhpConsole\Handler; +use PhpConsole\Helper; + +/** + * Monolog handler for Google Chrome extension "PHP Console" + * + * Display PHP error/debug log messages in Google Chrome console and notification popups, executes PHP code remotely + * + * Usage: + * 1. Install Google Chrome extension https://chrome.google.com/webstore/detail/php-console/nfhmhhlpfleoednkpnnnkolmclajemef + * 2. See overview https://github.com/barbushin/php-console#overview + * 3. Install PHP Console library https://github.com/barbushin/php-console#installation + * 4. Example (result will looks like http://i.hizliresim.com/vg3Pz4.png) + * + * $logger = new \Monolog\Logger('all', array(new \Monolog\Handler\PHPConsoleHandler())); + * \Monolog\ErrorHandler::register($logger); + * echo $undefinedVar; + * $logger->addDebug('SELECT * FROM users', array('db', 'time' => 0.012)); + * PC::debug($_SERVER); // PHP Console debugger for any type of vars + * + * @author Sergey Barbushin https://www.linkedin.com/in/barbushin + */ +class PHPConsoleHandler extends AbstractProcessingHandler +{ + private $options = array( + 'enabled' => true, // bool Is PHP Console server enabled + 'classesPartialsTraceIgnore' => array('Monolog\\'), // array Hide calls of classes started with... + 'debugTagsKeysInContext' => array(0, 'tag'), // bool Is PHP Console server enabled + 'useOwnErrorsHandler' => false, // bool Enable errors handling + 'useOwnExceptionsHandler' => false, // bool Enable exceptions handling + 'sourcesBasePath' => null, // string Base path of all project sources to strip in errors source paths + 'registerHelper' => true, // bool Register PhpConsole\Helper that allows short debug calls like PC::debug($var, 'ta.g.s') + 'serverEncoding' => null, // string|null Server internal encoding + 'headersLimit' => null, // int|null Set headers size limit for your web-server + 'password' => null, // string|null Protect PHP Console connection by password + 'enableSslOnlyMode' => false, // bool Force connection by SSL for clients with PHP Console installed + 'ipMasks' => array(), // array Set IP masks of clients that will be allowed to connect to PHP Console: array('192.168.*.*', '127.0.0.1') + 'enableEvalListener' => false, // bool Enable eval request to be handled by eval dispatcher(if enabled, 'password' option is also required) + 'dumperDetectCallbacks' => false, // bool Convert callback items in dumper vars to (callback SomeClass::someMethod) strings + 'dumperLevelLimit' => 5, // int Maximum dumped vars array or object nested dump level + 'dumperItemsCountLimit' => 100, // int Maximum dumped var same level array items or object properties number + 'dumperItemSizeLimit' => 5000, // int Maximum length of any string or dumped array item + 'dumperDumpSizeLimit' => 500000, // int Maximum approximate size of dumped vars result formatted in JSON + 'detectDumpTraceAndSource' => false, // bool Autodetect and append trace data to debug + 'dataStorage' => null, // PhpConsole\Storage|null Fixes problem with custom $_SESSION handler(see http://goo.gl/Ne8juJ) + ); + + /** @var Connector */ + private $connector; + + /** + * @param array $options See \Monolog\Handler\PHPConsoleHandler::$options for more details + * @param Connector|null $connector Instance of \PhpConsole\Connector class (optional) + * @param int $level + * @param bool $bubble + * @throws Exception + */ + public function __construct(array $options = array(), Connector $connector = null, $level = Logger::DEBUG, $bubble = true) + { + if (!class_exists('PhpConsole\Connector')) { + throw new Exception('PHP Console library not found. See https://github.com/barbushin/php-console#installation'); + } + parent::__construct($level, $bubble); + $this->options = $this->initOptions($options); + $this->connector = $this->initConnector($connector); + } + + private function initOptions(array $options) + { + $wrongOptions = array_diff(array_keys($options), array_keys($this->options)); + if ($wrongOptions) { + throw new Exception('Unknown options: ' . implode(', ', $wrongOptions)); + } + + return array_replace($this->options, $options); + } + + private function initConnector(Connector $connector = null) + { + if (!$connector) { + if ($this->options['dataStorage']) { + Connector::setPostponeStorage($this->options['dataStorage']); + } + $connector = Connector::getInstance(); + } + + if ($this->options['registerHelper'] && !Helper::isRegistered()) { + Helper::register(); + } + + if ($this->options['enabled'] && $connector->isActiveClient()) { + if ($this->options['useOwnErrorsHandler'] || $this->options['useOwnExceptionsHandler']) { + $handler = Handler::getInstance(); + $handler->setHandleErrors($this->options['useOwnErrorsHandler']); + $handler->setHandleExceptions($this->options['useOwnExceptionsHandler']); + $handler->start(); + } + if ($this->options['sourcesBasePath']) { + $connector->setSourcesBasePath($this->options['sourcesBasePath']); + } + if ($this->options['serverEncoding']) { + $connector->setServerEncoding($this->options['serverEncoding']); + } + if ($this->options['password']) { + $connector->setPassword($this->options['password']); + } + if ($this->options['enableSslOnlyMode']) { + $connector->enableSslOnlyMode(); + } + if ($this->options['ipMasks']) { + $connector->setAllowedIpMasks($this->options['ipMasks']); + } + if ($this->options['headersLimit']) { + $connector->setHeadersLimit($this->options['headersLimit']); + } + if ($this->options['detectDumpTraceAndSource']) { + $connector->getDebugDispatcher()->detectTraceAndSource = true; + } + $dumper = $connector->getDumper(); + $dumper->levelLimit = $this->options['dumperLevelLimit']; + $dumper->itemsCountLimit = $this->options['dumperItemsCountLimit']; + $dumper->itemSizeLimit = $this->options['dumperItemSizeLimit']; + $dumper->dumpSizeLimit = $this->options['dumperDumpSizeLimit']; + $dumper->detectCallbacks = $this->options['dumperDetectCallbacks']; + if ($this->options['enableEvalListener']) { + $connector->startEvalRequestsListener(); + } + } + + return $connector; + } + + public function getConnector() + { + return $this->connector; + } + + public function getOptions() + { + return $this->options; + } + + public function handle(array $record) + { + if ($this->options['enabled'] && $this->connector->isActiveClient()) { + return parent::handle($record); + } + + return !$this->bubble; + } + + /** + * Writes the record down to the log of the implementing handler + * + * @param array $record + * @return void + */ + protected function write(array $record) + { + if ($record['level'] < Logger::NOTICE) { + $this->handleDebugRecord($record); + } elseif (isset($record['context']['exception']) && $record['context']['exception'] instanceof Exception) { + $this->handleExceptionRecord($record); + } else { + $this->handleErrorRecord($record); + } + } + + private function handleDebugRecord(array $record) + { + $tags = $this->getRecordTags($record); + $message = $record['message']; + if ($record['context']) { + $message .= ' ' . Utils::jsonEncode($this->connector->getDumper()->dump(array_filter($record['context'])), null, true); + } + $this->connector->getDebugDispatcher()->dispatchDebug($message, $tags, $this->options['classesPartialsTraceIgnore']); + } + + private function handleExceptionRecord(array $record) + { + $this->connector->getErrorsDispatcher()->dispatchException($record['context']['exception']); + } + + private function handleErrorRecord(array $record) + { + $context = $record['context']; + + $this->connector->getErrorsDispatcher()->dispatchError( + isset($context['code']) ? $context['code'] : null, + isset($context['message']) ? $context['message'] : $record['message'], + isset($context['file']) ? $context['file'] : null, + isset($context['line']) ? $context['line'] : null, + $this->options['classesPartialsTraceIgnore'] + ); + } + + private function getRecordTags(array &$record) + { + $tags = null; + if (!empty($record['context'])) { + $context = & $record['context']; + foreach ($this->options['debugTagsKeysInContext'] as $key) { + if (!empty($context[$key])) { + $tags = $context[$key]; + if ($key === 0) { + array_shift($context); + } else { + unset($context[$key]); + } + break; + } + } + } + + return $tags ?: strtolower($record['level_name']); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new LineFormatter('%message%'); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerInterface.php b/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerInterface.php new file mode 100644 index 0000000..66a3d83 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerInterface.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Processor\ProcessorInterface; + +/** + * Interface to describe loggers that have processors + * + * This interface is present in monolog 1.x to ease forward compatibility. + * + * @author Jordi Boggiano + */ +interface ProcessableHandlerInterface +{ + /** + * Adds a processor in the stack. + * + * @param ProcessorInterface|callable $callback + * @return HandlerInterface self + */ + public function pushProcessor($callback): HandlerInterface; + + /** + * Removes the processor on top of the stack and returns it. + * + * @throws \LogicException In case the processor stack is empty + * @return callable + */ + public function popProcessor(): callable; +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerTrait.php b/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerTrait.php new file mode 100644 index 0000000..09f32a1 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerTrait.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\ResettableInterface; + +/** + * Helper trait for implementing ProcessableInterface + * + * This trait is present in monolog 1.x to ease forward compatibility. + * + * @author Jordi Boggiano + */ +trait ProcessableHandlerTrait +{ + /** + * @var callable[] + */ + protected $processors = []; + + /** + * {@inheritdoc} + * @suppress PhanTypeMismatchReturn + */ + public function pushProcessor($callback): HandlerInterface + { + array_unshift($this->processors, $callback); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function popProcessor(): callable + { + if (!$this->processors) { + throw new \LogicException('You tried to pop from an empty processor stack.'); + } + + return array_shift($this->processors); + } + + /** + * Processes a record. + */ + protected function processRecord(array $record): array + { + foreach ($this->processors as $processor) { + $record = $processor($record); + } + + return $record; + } + + protected function resetProcessors(): void + { + foreach ($this->processors as $processor) { + if ($processor instanceof ResettableInterface) { + $processor->reset(); + } + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.php new file mode 100644 index 0000000..a99e6ab --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Psr\Log\LoggerInterface; + +/** + * Proxies log messages to an existing PSR-3 compliant logger. + * + * @author Michael Moussa + */ +class PsrHandler extends AbstractHandler +{ + /** + * PSR-3 compliant logger + * + * @var LoggerInterface + */ + protected $logger; + + /** + * @param LoggerInterface $logger The underlying PSR-3 compliant logger to which messages will be proxied + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(LoggerInterface $logger, $level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + + $this->logger = $logger; + } + + /** + * {@inheritDoc} + */ + public function handle(array $record) + { + if (!$this->isHandling($record)) { + return false; + } + + $this->logger->log(strtolower($record['level_name']), $record['message'], $record['context']); + + return false === $this->bubble; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php new file mode 100644 index 0000000..f27bb3d --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php @@ -0,0 +1,185 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Sends notifications through the pushover api to mobile phones + * + * @author Sebastian Göttschkes + * @see https://www.pushover.net/api + */ +class PushoverHandler extends SocketHandler +{ + private $token; + private $users; + private $title; + private $user; + private $retry; + private $expire; + + private $highPriorityLevel; + private $emergencyLevel; + private $useFormattedMessage = false; + + /** + * All parameters that can be sent to Pushover + * @see https://pushover.net/api + * @var array + */ + private $parameterNames = array( + 'token' => true, + 'user' => true, + 'message' => true, + 'device' => true, + 'title' => true, + 'url' => true, + 'url_title' => true, + 'priority' => true, + 'timestamp' => true, + 'sound' => true, + 'retry' => true, + 'expire' => true, + 'callback' => true, + ); + + /** + * Sounds the api supports by default + * @see https://pushover.net/api#sounds + * @var array + */ + private $sounds = array( + 'pushover', 'bike', 'bugle', 'cashregister', 'classical', 'cosmic', 'falling', 'gamelan', 'incoming', + 'intermission', 'magic', 'mechanical', 'pianobar', 'siren', 'spacealarm', 'tugboat', 'alien', 'climb', + 'persistent', 'echo', 'updown', 'none', + ); + + /** + * @param string $token Pushover api token + * @param string|array $users Pushover user id or array of ids the message will be sent to + * @param string $title Title sent to the Pushover API + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param bool $useSSL Whether to connect via SSL. Required when pushing messages to users that are not + * the pushover.net app owner. OpenSSL is required for this option. + * @param int $highPriorityLevel The minimum logging level at which this handler will start + * sending "high priority" requests to the Pushover API + * @param int $emergencyLevel The minimum logging level at which this handler will start + * sending "emergency" requests to the Pushover API + * @param int $retry The retry parameter specifies how often (in seconds) the Pushover servers will send the same notification to the user. + * @param int $expire The expire parameter specifies how many seconds your notification will continue to be retried for (every retry seconds). + */ + public function __construct($token, $users, $title = null, $level = Logger::CRITICAL, $bubble = true, $useSSL = true, $highPriorityLevel = Logger::CRITICAL, $emergencyLevel = Logger::EMERGENCY, $retry = 30, $expire = 25200) + { + $connectionString = $useSSL ? 'ssl://api.pushover.net:443' : 'api.pushover.net:80'; + parent::__construct($connectionString, $level, $bubble); + + $this->token = $token; + $this->users = (array) $users; + $this->title = $title ?: gethostname(); + $this->highPriorityLevel = Logger::toMonologLevel($highPriorityLevel); + $this->emergencyLevel = Logger::toMonologLevel($emergencyLevel); + $this->retry = $retry; + $this->expire = $expire; + } + + protected function generateDataStream($record) + { + $content = $this->buildContent($record); + + return $this->buildHeader($content) . $content; + } + + private function buildContent($record) + { + // Pushover has a limit of 512 characters on title and message combined. + $maxMessageLength = 512 - strlen($this->title); + + $message = ($this->useFormattedMessage) ? $record['formatted'] : $record['message']; + $message = substr($message, 0, $maxMessageLength); + + $timestamp = $record['datetime']->getTimestamp(); + + $dataArray = array( + 'token' => $this->token, + 'user' => $this->user, + 'message' => $message, + 'title' => $this->title, + 'timestamp' => $timestamp, + ); + + if (isset($record['level']) && $record['level'] >= $this->emergencyLevel) { + $dataArray['priority'] = 2; + $dataArray['retry'] = $this->retry; + $dataArray['expire'] = $this->expire; + } elseif (isset($record['level']) && $record['level'] >= $this->highPriorityLevel) { + $dataArray['priority'] = 1; + } + + // First determine the available parameters + $context = array_intersect_key($record['context'], $this->parameterNames); + $extra = array_intersect_key($record['extra'], $this->parameterNames); + + // Least important info should be merged with subsequent info + $dataArray = array_merge($extra, $context, $dataArray); + + // Only pass sounds that are supported by the API + if (isset($dataArray['sound']) && !in_array($dataArray['sound'], $this->sounds)) { + unset($dataArray['sound']); + } + + return http_build_query($dataArray); + } + + private function buildHeader($content) + { + $header = "POST /1/messages.json HTTP/1.1\r\n"; + $header .= "Host: api.pushover.net\r\n"; + $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; + $header .= "Content-Length: " . strlen($content) . "\r\n"; + $header .= "\r\n"; + + return $header; + } + + protected function write(array $record) + { + foreach ($this->users as $user) { + $this->user = $user; + + parent::write($record); + $this->closeSocket(); + } + + $this->user = null; + } + + public function setHighPriorityLevel($value) + { + $this->highPriorityLevel = $value; + } + + public function setEmergencyLevel($value) + { + $this->emergencyLevel = $value; + } + + /** + * Use the formatted message? + * @param bool $value + */ + public function useFormattedMessage($value) + { + $this->useFormattedMessage = (bool) $value; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/RavenHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/RavenHandler.php new file mode 100644 index 0000000..b0298fa --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/RavenHandler.php @@ -0,0 +1,234 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\LineFormatter; +use Monolog\Formatter\FormatterInterface; +use Monolog\Logger; +use Raven_Client; + +/** + * Handler to send messages to a Sentry (https://github.com/getsentry/sentry) server + * using sentry-php (https://github.com/getsentry/sentry-php) + * + * @author Marc Abramowitz + */ +class RavenHandler extends AbstractProcessingHandler +{ + /** + * Translates Monolog log levels to Raven log levels. + */ + protected $logLevels = array( + Logger::DEBUG => Raven_Client::DEBUG, + Logger::INFO => Raven_Client::INFO, + Logger::NOTICE => Raven_Client::INFO, + Logger::WARNING => Raven_Client::WARNING, + Logger::ERROR => Raven_Client::ERROR, + Logger::CRITICAL => Raven_Client::FATAL, + Logger::ALERT => Raven_Client::FATAL, + Logger::EMERGENCY => Raven_Client::FATAL, + ); + + /** + * @var string should represent the current version of the calling + * software. Can be any string (git commit, version number) + */ + protected $release; + + /** + * @var Raven_Client the client object that sends the message to the server + */ + protected $ravenClient; + + /** + * @var FormatterInterface The formatter to use for the logs generated via handleBatch() + */ + protected $batchFormatter; + + /** + * @param Raven_Client $ravenClient + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(Raven_Client $ravenClient, $level = Logger::DEBUG, $bubble = true) + { + @trigger_error('The Monolog\Handler\RavenHandler class is deprecated. You should rather upgrade to the sentry/sentry 2.x and use Sentry\Monolog\Handler, see https://github.com/getsentry/sentry-php/blob/master/src/Monolog/Handler.php', E_USER_DEPRECATED); + + parent::__construct($level, $bubble); + + $this->ravenClient = $ravenClient; + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + $level = $this->level; + + // filter records based on their level + $records = array_filter($records, function ($record) use ($level) { + return $record['level'] >= $level; + }); + + if (!$records) { + return; + } + + // the record with the highest severity is the "main" one + $record = array_reduce($records, function ($highest, $record) { + if (null === $highest || $record['level'] > $highest['level']) { + return $record; + } + + return $highest; + }); + + // the other ones are added as a context item + $logs = array(); + foreach ($records as $r) { + $logs[] = $this->processRecord($r); + } + + if ($logs) { + $record['context']['logs'] = (string) $this->getBatchFormatter()->formatBatch($logs); + } + + $this->handle($record); + } + + /** + * Sets the formatter for the logs generated by handleBatch(). + * + * @param FormatterInterface $formatter + */ + public function setBatchFormatter(FormatterInterface $formatter) + { + $this->batchFormatter = $formatter; + } + + /** + * Gets the formatter for the logs generated by handleBatch(). + * + * @return FormatterInterface + */ + public function getBatchFormatter() + { + if (!$this->batchFormatter) { + $this->batchFormatter = $this->getDefaultBatchFormatter(); + } + + return $this->batchFormatter; + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $previousUserContext = false; + $options = array(); + $options['level'] = $this->logLevels[$record['level']]; + $options['tags'] = array(); + if (!empty($record['extra']['tags'])) { + $options['tags'] = array_merge($options['tags'], $record['extra']['tags']); + unset($record['extra']['tags']); + } + if (!empty($record['context']['tags'])) { + $options['tags'] = array_merge($options['tags'], $record['context']['tags']); + unset($record['context']['tags']); + } + if (!empty($record['context']['fingerprint'])) { + $options['fingerprint'] = $record['context']['fingerprint']; + unset($record['context']['fingerprint']); + } + if (!empty($record['context']['logger'])) { + $options['logger'] = $record['context']['logger']; + unset($record['context']['logger']); + } else { + $options['logger'] = $record['channel']; + } + foreach ($this->getExtraParameters() as $key) { + foreach (array('extra', 'context') as $source) { + if (!empty($record[$source][$key])) { + $options[$key] = $record[$source][$key]; + unset($record[$source][$key]); + } + } + } + if (!empty($record['context'])) { + $options['extra']['context'] = $record['context']; + if (!empty($record['context']['user'])) { + $previousUserContext = $this->ravenClient->context->user; + $this->ravenClient->user_context($record['context']['user']); + unset($options['extra']['context']['user']); + } + } + if (!empty($record['extra'])) { + $options['extra']['extra'] = $record['extra']; + } + + if (!empty($this->release) && !isset($options['release'])) { + $options['release'] = $this->release; + } + + if (isset($record['context']['exception']) && ($record['context']['exception'] instanceof \Exception || (PHP_VERSION_ID >= 70000 && $record['context']['exception'] instanceof \Throwable))) { + $options['message'] = $record['formatted']; + $this->ravenClient->captureException($record['context']['exception'], $options); + } else { + $this->ravenClient->captureMessage($record['formatted'], array(), $options); + } + + if ($previousUserContext !== false) { + $this->ravenClient->user_context($previousUserContext); + } + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new LineFormatter('[%channel%] %message%'); + } + + /** + * Gets the default formatter for the logs generated by handleBatch(). + * + * @return FormatterInterface + */ + protected function getDefaultBatchFormatter() + { + return new LineFormatter(); + } + + /** + * Gets extra parameters supported by Raven that can be found in "extra" and "context" + * + * @return array + */ + protected function getExtraParameters() + { + return array('contexts', 'checksum', 'release', 'event_id'); + } + + /** + * @param string $value + * @return self + */ + public function setRelease($value) + { + $this->release = $value; + + return $this; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php new file mode 100644 index 0000000..3725db2 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\LineFormatter; +use Monolog\Logger; + +/** + * Logs to a Redis key using rpush + * + * usage example: + * + * $log = new Logger('application'); + * $redis = new RedisHandler(new Predis\Client("tcp://localhost:6379"), "logs", "prod"); + * $log->pushHandler($redis); + * + * @author Thomas Tourlourat + */ +class RedisHandler extends AbstractProcessingHandler +{ + private $redisClient; + private $redisKey; + protected $capSize; + + /** + * @param \Predis\Client|\Redis $redis The redis instance + * @param string $key The key name to push records to + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param int|false $capSize Number of entries to limit list size to + */ + public function __construct($redis, $key, $level = Logger::DEBUG, $bubble = true, $capSize = false) + { + if (!(($redis instanceof \Predis\Client) || ($redis instanceof \Redis))) { + throw new \InvalidArgumentException('Predis\Client or Redis instance required'); + } + + $this->redisClient = $redis; + $this->redisKey = $key; + $this->capSize = $capSize; + + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record) + { + if ($this->capSize) { + $this->writeCapped($record); + } else { + $this->redisClient->rpush($this->redisKey, $record["formatted"]); + } + } + + /** + * Write and cap the collection + * Writes the record to the redis list and caps its + * + * @param array $record associative record array + * @return void + */ + protected function writeCapped(array $record) + { + if ($this->redisClient instanceof \Redis) { + $mode = defined('\Redis::MULTI') ? \Redis::MULTI : 1; + $this->redisClient->multi($mode) + ->rpush($this->redisKey, $record["formatted"]) + ->ltrim($this->redisKey, -$this->capSize, -1) + ->exec(); + } else { + $redisKey = $this->redisKey; + $capSize = $this->capSize; + $this->redisClient->transaction(function ($tx) use ($record, $redisKey, $capSize) { + $tx->rpush($redisKey, $record["formatted"]); + $tx->ltrim($redisKey, -$capSize, -1); + }); + } + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new LineFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/RollbarHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/RollbarHandler.php new file mode 100644 index 0000000..65073ff --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/RollbarHandler.php @@ -0,0 +1,144 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use RollbarNotifier; +use Exception; +use Monolog\Logger; + +/** + * Sends errors to Rollbar + * + * If the context data contains a `payload` key, that is used as an array + * of payload options to RollbarNotifier's report_message/report_exception methods. + * + * Rollbar's context info will contain the context + extra keys from the log record + * merged, and then on top of that a few keys: + * + * - level (rollbar level name) + * - monolog_level (monolog level name, raw level, as rollbar only has 5 but monolog 8) + * - channel + * - datetime (unix timestamp) + * + * @author Paul Statezny + */ +class RollbarHandler extends AbstractProcessingHandler +{ + /** + * Rollbar notifier + * + * @var RollbarNotifier + */ + protected $rollbarNotifier; + + protected $levelMap = array( + Logger::DEBUG => 'debug', + Logger::INFO => 'info', + Logger::NOTICE => 'info', + Logger::WARNING => 'warning', + Logger::ERROR => 'error', + Logger::CRITICAL => 'critical', + Logger::ALERT => 'critical', + Logger::EMERGENCY => 'critical', + ); + + /** + * Records whether any log records have been added since the last flush of the rollbar notifier + * + * @var bool + */ + private $hasRecords = false; + + protected $initialized = false; + + /** + * @param RollbarNotifier $rollbarNotifier RollbarNotifier object constructed with valid token + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(RollbarNotifier $rollbarNotifier, $level = Logger::ERROR, $bubble = true) + { + $this->rollbarNotifier = $rollbarNotifier; + + parent::__construct($level, $bubble); + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + if (!$this->initialized) { + // __destructor() doesn't get called on Fatal errors + register_shutdown_function(array($this, 'close')); + $this->initialized = true; + } + + $context = $record['context']; + $payload = array(); + if (isset($context['payload'])) { + $payload = $context['payload']; + unset($context['payload']); + } + $context = array_merge($context, $record['extra'], array( + 'level' => $this->levelMap[$record['level']], + 'monolog_level' => $record['level_name'], + 'channel' => $record['channel'], + 'datetime' => $record['datetime']->format('U'), + )); + + if (isset($context['exception']) && $context['exception'] instanceof Exception) { + $payload['level'] = $context['level']; + $exception = $context['exception']; + unset($context['exception']); + + $this->rollbarNotifier->report_exception($exception, $context, $payload); + } else { + $this->rollbarNotifier->report_message( + $record['message'], + $context['level'], + $context, + $payload + ); + } + + $this->hasRecords = true; + } + + public function flush() + { + if ($this->hasRecords) { + $this->rollbarNotifier->flush(); + $this->hasRecords = false; + } + } + + /** + * {@inheritdoc} + */ + public function close() + { + $this->flush(); + } + + /** + * {@inheritdoc} + */ + public function reset() + { + $this->flush(); + + parent::reset(); + } + + +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php new file mode 100644 index 0000000..b8253ba --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php @@ -0,0 +1,191 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Utils; + +/** + * Stores logs to files that are rotated every day and a limited number of files are kept. + * + * This rotation is only intended to be used as a workaround. Using logrotate to + * handle the rotation is strongly encouraged when you can use it. + * + * @author Christophe Coevoet + * @author Jordi Boggiano + */ +class RotatingFileHandler extends StreamHandler +{ + const FILE_PER_DAY = 'Y-m-d'; + const FILE_PER_MONTH = 'Y-m'; + const FILE_PER_YEAR = 'Y'; + + protected $filename; + protected $maxFiles; + protected $mustRotate; + protected $nextRotation; + protected $filenameFormat; + protected $dateFormat; + + /** + * @param string $filename + * @param int $maxFiles The maximal amount of files to keep (0 means unlimited) + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param int|null $filePermission Optional file permissions (default (0644) are only for owner read/write) + * @param bool $useLocking Try to lock log file before doing any writes + */ + public function __construct($filename, $maxFiles = 0, $level = Logger::DEBUG, $bubble = true, $filePermission = null, $useLocking = false) + { + $this->filename = Utils::canonicalizePath($filename); + $this->maxFiles = (int) $maxFiles; + $this->nextRotation = new \DateTime('tomorrow'); + $this->filenameFormat = '{filename}-{date}'; + $this->dateFormat = 'Y-m-d'; + + parent::__construct($this->getTimedFilename(), $level, $bubble, $filePermission, $useLocking); + } + + /** + * {@inheritdoc} + */ + public function close() + { + parent::close(); + + if (true === $this->mustRotate) { + $this->rotate(); + } + } + + /** + * {@inheritdoc} + */ + public function reset() + { + parent::reset(); + + if (true === $this->mustRotate) { + $this->rotate(); + } + } + + public function setFilenameFormat($filenameFormat, $dateFormat) + { + if (!preg_match('{^Y(([/_.-]?m)([/_.-]?d)?)?$}', $dateFormat)) { + trigger_error( + 'Invalid date format - format must be one of '. + 'RotatingFileHandler::FILE_PER_DAY ("Y-m-d"), RotatingFileHandler::FILE_PER_MONTH ("Y-m") '. + 'or RotatingFileHandler::FILE_PER_YEAR ("Y"), or you can set one of the '. + 'date formats using slashes, underscores and/or dots instead of dashes.', + E_USER_DEPRECATED + ); + } + if (substr_count($filenameFormat, '{date}') === 0) { + trigger_error( + 'Invalid filename format - format should contain at least `{date}`, because otherwise rotating is impossible.', + E_USER_DEPRECATED + ); + } + $this->filenameFormat = $filenameFormat; + $this->dateFormat = $dateFormat; + $this->url = $this->getTimedFilename(); + $this->close(); + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + // on the first record written, if the log is new, we should rotate (once per day) + if (null === $this->mustRotate) { + $this->mustRotate = !file_exists($this->url); + } + + if ($this->nextRotation < $record['datetime']) { + $this->mustRotate = true; + $this->close(); + } + + parent::write($record); + } + + /** + * Rotates the files. + */ + protected function rotate() + { + // update filename + $this->url = $this->getTimedFilename(); + $this->nextRotation = new \DateTime('tomorrow'); + + // skip GC of old logs if files are unlimited + if (0 === $this->maxFiles) { + return; + } + + $logFiles = glob($this->getGlobPattern()); + if ($this->maxFiles >= count($logFiles)) { + // no files to remove + return; + } + + // Sorting the files by name to remove the older ones + usort($logFiles, function ($a, $b) { + return strcmp($b, $a); + }); + + foreach (array_slice($logFiles, $this->maxFiles) as $file) { + if (is_writable($file)) { + // suppress errors here as unlink() might fail if two processes + // are cleaning up/rotating at the same time + set_error_handler(function ($errno, $errstr, $errfile, $errline) {}); + unlink($file); + restore_error_handler(); + } + } + + $this->mustRotate = false; + } + + protected function getTimedFilename() + { + $fileInfo = pathinfo($this->filename); + $timedFilename = str_replace( + array('{filename}', '{date}'), + array($fileInfo['filename'], date($this->dateFormat)), + $fileInfo['dirname'] . '/' . $this->filenameFormat + ); + + if (!empty($fileInfo['extension'])) { + $timedFilename .= '.'.$fileInfo['extension']; + } + + return $timedFilename; + } + + protected function getGlobPattern() + { + $fileInfo = pathinfo($this->filename); + $glob = str_replace( + array('{filename}', '{date}'), + array($fileInfo['filename'], '[0-9][0-9][0-9][0-9]*'), + $fileInfo['dirname'] . '/' . $this->filenameFormat + ); + if (!empty($fileInfo['extension'])) { + $glob .= '.'.$fileInfo['extension']; + } + + return $glob; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SamplingHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SamplingHandler.php new file mode 100644 index 0000000..b547ed7 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SamplingHandler.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; + +/** + * Sampling handler + * + * A sampled event stream can be useful for logging high frequency events in + * a production environment where you only need an idea of what is happening + * and are not concerned with capturing every occurrence. Since the decision to + * handle or not handle a particular event is determined randomly, the + * resulting sampled log is not guaranteed to contain 1/N of the events that + * occurred in the application, but based on the Law of large numbers, it will + * tend to be close to this ratio with a large number of attempts. + * + * @author Bryan Davis + * @author Kunal Mehta + */ +class SamplingHandler extends AbstractHandler +{ + /** + * @var callable|HandlerInterface $handler + */ + protected $handler; + + /** + * @var int $factor + */ + protected $factor; + + /** + * @param callable|HandlerInterface $handler Handler or factory callable($record|null, $samplingHandler). + * @param int $factor Sample factor + */ + public function __construct($handler, $factor) + { + parent::__construct(); + $this->handler = $handler; + $this->factor = $factor; + + if (!$this->handler instanceof HandlerInterface && !is_callable($this->handler)) { + throw new \RuntimeException("The given handler (".json_encode($this->handler).") is not a callable nor a Monolog\Handler\HandlerInterface object"); + } + } + + public function isHandling(array $record) + { + return $this->getHandler($record)->isHandling($record); + } + + public function handle(array $record) + { + if ($this->isHandling($record) && mt_rand(1, $this->factor) === 1) { + if ($this->processors) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + } + + $this->getHandler($record)->handle($record); + } + + return false === $this->bubble; + } + + /** + * Return the nested handler + * + * If the handler was provided as a factory callable, this will trigger the handler's instantiation. + * + * @return HandlerInterface + */ + public function getHandler(array $record = null) + { + if (!$this->handler instanceof HandlerInterface) { + $this->handler = call_user_func($this->handler, $record, $this); + if (!$this->handler instanceof HandlerInterface) { + throw new \RuntimeException("The factory callable should return a HandlerInterface"); + } + } + + return $this->handler; + } + + /** + * {@inheritdoc} + */ + public function setFormatter(FormatterInterface $formatter) + { + $this->getHandler()->setFormatter($formatter); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getFormatter() + { + return $this->getHandler()->getFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php b/vendor/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php new file mode 100644 index 0000000..3945550 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php @@ -0,0 +1,299 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\Slack; + +use Monolog\Logger; +use Monolog\Utils; +use Monolog\Formatter\NormalizerFormatter; +use Monolog\Formatter\FormatterInterface; + +/** + * Slack record utility helping to log to Slack webhooks or API. + * + * @author Greg Kedzierski + * @author Haralan Dobrev + * @see https://api.slack.com/incoming-webhooks + * @see https://api.slack.com/docs/message-attachments + */ +class SlackRecord +{ + const COLOR_DANGER = 'danger'; + + const COLOR_WARNING = 'warning'; + + const COLOR_GOOD = 'good'; + + const COLOR_DEFAULT = '#e3e4e6'; + + /** + * Slack channel (encoded ID or name) + * @var string|null + */ + private $channel; + + /** + * Name of a bot + * @var string|null + */ + private $username; + + /** + * User icon e.g. 'ghost', 'http://example.com/user.png' + * @var string + */ + private $userIcon; + + /** + * Whether the message should be added to Slack as attachment (plain text otherwise) + * @var bool + */ + private $useAttachment; + + /** + * Whether the the context/extra messages added to Slack as attachments are in a short style + * @var bool + */ + private $useShortAttachment; + + /** + * Whether the attachment should include context and extra data + * @var bool + */ + private $includeContextAndExtra; + + /** + * Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2'] + * @var array + */ + private $excludeFields; + + /** + * @var FormatterInterface + */ + private $formatter; + + /** + * @var NormalizerFormatter + */ + private $normalizerFormatter; + + public function __construct($channel = null, $username = null, $useAttachment = true, $userIcon = null, $useShortAttachment = false, $includeContextAndExtra = false, array $excludeFields = array(), FormatterInterface $formatter = null) + { + $this->channel = $channel; + $this->username = $username; + $this->userIcon = trim($userIcon, ':'); + $this->useAttachment = $useAttachment; + $this->useShortAttachment = $useShortAttachment; + $this->includeContextAndExtra = $includeContextAndExtra; + $this->excludeFields = $excludeFields; + $this->formatter = $formatter; + + if ($this->includeContextAndExtra) { + $this->normalizerFormatter = new NormalizerFormatter(); + } + } + + public function getSlackData(array $record) + { + $dataArray = array(); + $record = $this->excludeFields($record); + + if ($this->username) { + $dataArray['username'] = $this->username; + } + + if ($this->channel) { + $dataArray['channel'] = $this->channel; + } + + if ($this->formatter && !$this->useAttachment) { + $message = $this->formatter->format($record); + } else { + $message = $record['message']; + } + + if ($this->useAttachment) { + $attachment = array( + 'fallback' => $message, + 'text' => $message, + 'color' => $this->getAttachmentColor($record['level']), + 'fields' => array(), + 'mrkdwn_in' => array('fields'), + 'ts' => $record['datetime']->getTimestamp() + ); + + if ($this->useShortAttachment) { + $attachment['title'] = $record['level_name']; + } else { + $attachment['title'] = 'Message'; + $attachment['fields'][] = $this->generateAttachmentField('Level', $record['level_name']); + } + + + if ($this->includeContextAndExtra) { + foreach (array('extra', 'context') as $key) { + if (empty($record[$key])) { + continue; + } + + if ($this->useShortAttachment) { + $attachment['fields'][] = $this->generateAttachmentField( + $key, + $record[$key] + ); + } else { + // Add all extra fields as individual fields in attachment + $attachment['fields'] = array_merge( + $attachment['fields'], + $this->generateAttachmentFields($record[$key]) + ); + } + } + } + + $dataArray['attachments'] = array($attachment); + } else { + $dataArray['text'] = $message; + } + + if ($this->userIcon) { + if (filter_var($this->userIcon, FILTER_VALIDATE_URL)) { + $dataArray['icon_url'] = $this->userIcon; + } else { + $dataArray['icon_emoji'] = ":{$this->userIcon}:"; + } + } + + return $dataArray; + } + + /** + * Returned a Slack message attachment color associated with + * provided level. + * + * @param int $level + * @return string + */ + public function getAttachmentColor($level) + { + switch (true) { + case $level >= Logger::ERROR: + return self::COLOR_DANGER; + case $level >= Logger::WARNING: + return self::COLOR_WARNING; + case $level >= Logger::INFO: + return self::COLOR_GOOD; + default: + return self::COLOR_DEFAULT; + } + } + + /** + * Stringifies an array of key/value pairs to be used in attachment fields + * + * @param array $fields + * + * @return string + */ + public function stringify($fields) + { + $normalized = $this->normalizerFormatter->format($fields); + $prettyPrintFlag = defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 128; + $flags = 0; + if (PHP_VERSION_ID >= 50400) { + $flags = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE; + } + + $hasSecondDimension = count(array_filter($normalized, 'is_array')); + $hasNonNumericKeys = !count(array_filter(array_keys($normalized), 'is_numeric')); + + return $hasSecondDimension || $hasNonNumericKeys + ? Utils::jsonEncode($normalized, $prettyPrintFlag | $flags) + : Utils::jsonEncode($normalized, $flags); + } + + /** + * Sets the formatter + * + * @param FormatterInterface $formatter + */ + public function setFormatter(FormatterInterface $formatter) + { + $this->formatter = $formatter; + } + + /** + * Generates attachment field + * + * @param string $title + * @param string|array $value + * + * @return array + */ + private function generateAttachmentField($title, $value) + { + $value = is_array($value) + ? sprintf('```%s```', $this->stringify($value)) + : $value; + + return array( + 'title' => ucfirst($title), + 'value' => $value, + 'short' => false + ); + } + + /** + * Generates a collection of attachment fields from array + * + * @param array $data + * + * @return array + */ + private function generateAttachmentFields(array $data) + { + $fields = array(); + foreach ($this->normalizerFormatter->format($data) as $key => $value) { + $fields[] = $this->generateAttachmentField($key, $value); + } + + return $fields; + } + + /** + * Get a copy of record with fields excluded according to $this->excludeFields + * + * @param array $record + * + * @return array + */ + private function excludeFields(array $record) + { + foreach ($this->excludeFields as $field) { + $keys = explode('.', $field); + $node = &$record; + $lastKey = end($keys); + foreach ($keys as $key) { + if (!isset($node[$key])) { + break; + } + if ($lastKey === $key) { + unset($node[$key]); + break; + } + $node = &$node[$key]; + } + } + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.php new file mode 100644 index 0000000..88c4c4d --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.php @@ -0,0 +1,221 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\Logger; +use Monolog\Utils; +use Monolog\Handler\Slack\SlackRecord; + +/** + * Sends notifications through Slack API + * + * @author Greg Kedzierski + * @see https://api.slack.com/ + */ +class SlackHandler extends SocketHandler +{ + /** + * Slack API token + * @var string + */ + private $token; + + /** + * Instance of the SlackRecord util class preparing data for Slack API. + * @var SlackRecord + */ + private $slackRecord; + + /** + * @param string $token Slack API token + * @param string $channel Slack channel (encoded ID or name) + * @param string|null $username Name of a bot + * @param bool $useAttachment Whether the message should be added to Slack as attachment (plain text otherwise) + * @param string|null $iconEmoji The emoji name to use (or null) + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param bool $useShortAttachment Whether the the context/extra messages added to Slack as attachments are in a short style + * @param bool $includeContextAndExtra Whether the attachment should include context and extra data + * @param array $excludeFields Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2'] + * @throws MissingExtensionException If no OpenSSL PHP extension configured + */ + public function __construct($token, $channel, $username = null, $useAttachment = true, $iconEmoji = null, $level = Logger::CRITICAL, $bubble = true, $useShortAttachment = false, $includeContextAndExtra = false, array $excludeFields = array()) + { + if (!extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP extension is required to use the SlackHandler'); + } + + parent::__construct('ssl://slack.com:443', $level, $bubble); + + $this->slackRecord = new SlackRecord( + $channel, + $username, + $useAttachment, + $iconEmoji, + $useShortAttachment, + $includeContextAndExtra, + $excludeFields, + $this->formatter + ); + + $this->token = $token; + } + + public function getSlackRecord() + { + return $this->slackRecord; + } + + public function getToken() + { + return $this->token; + } + + /** + * {@inheritdoc} + * + * @param array $record + * @return string + */ + protected function generateDataStream($record) + { + $content = $this->buildContent($record); + + return $this->buildHeader($content) . $content; + } + + /** + * Builds the body of API call + * + * @param array $record + * @return string + */ + private function buildContent($record) + { + $dataArray = $this->prepareContentData($record); + + return http_build_query($dataArray); + } + + /** + * Prepares content data + * + * @param array $record + * @return array + */ + protected function prepareContentData($record) + { + $dataArray = $this->slackRecord->getSlackData($record); + $dataArray['token'] = $this->token; + + if (!empty($dataArray['attachments'])) { + $dataArray['attachments'] = Utils::jsonEncode($dataArray['attachments']); + } + + return $dataArray; + } + + /** + * Builds the header of the API Call + * + * @param string $content + * @return string + */ + private function buildHeader($content) + { + $header = "POST /api/chat.postMessage HTTP/1.1\r\n"; + $header .= "Host: slack.com\r\n"; + $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; + $header .= "Content-Length: " . strlen($content) . "\r\n"; + $header .= "\r\n"; + + return $header; + } + + /** + * {@inheritdoc} + * + * @param array $record + */ + protected function write(array $record) + { + parent::write($record); + $this->finalizeWrite(); + } + + /** + * Finalizes the request by reading some bytes and then closing the socket + * + * If we do not read some but close the socket too early, slack sometimes + * drops the request entirely. + */ + protected function finalizeWrite() + { + $res = $this->getResource(); + if (is_resource($res)) { + @fread($res, 2048); + } + $this->closeSocket(); + } + + /** + * Returned a Slack message attachment color associated with + * provided level. + * + * @param int $level + * @return string + * @deprecated Use underlying SlackRecord instead + */ + protected function getAttachmentColor($level) + { + trigger_error( + 'SlackHandler::getAttachmentColor() is deprecated. Use underlying SlackRecord instead.', + E_USER_DEPRECATED + ); + + return $this->slackRecord->getAttachmentColor($level); + } + + /** + * Stringifies an array of key/value pairs to be used in attachment fields + * + * @param array $fields + * @return string + * @deprecated Use underlying SlackRecord instead + */ + protected function stringify($fields) + { + trigger_error( + 'SlackHandler::stringify() is deprecated. Use underlying SlackRecord instead.', + E_USER_DEPRECATED + ); + + return $this->slackRecord->stringify($fields); + } + + public function setFormatter(FormatterInterface $formatter) + { + parent::setFormatter($formatter); + $this->slackRecord->setFormatter($formatter); + + return $this; + } + + public function getFormatter() + { + $formatter = parent::getFormatter(); + $this->slackRecord->setFormatter($formatter); + + return $formatter; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php new file mode 100644 index 0000000..b87be99 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php @@ -0,0 +1,121 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\Logger; +use Monolog\Utils; +use Monolog\Handler\Slack\SlackRecord; + +/** + * Sends notifications through Slack Webhooks + * + * @author Haralan Dobrev + * @see https://api.slack.com/incoming-webhooks + */ +class SlackWebhookHandler extends AbstractProcessingHandler +{ + /** + * Slack Webhook token + * @var string + */ + private $webhookUrl; + + /** + * Instance of the SlackRecord util class preparing data for Slack API. + * @var SlackRecord + */ + private $slackRecord; + + /** + * @param string $webhookUrl Slack Webhook URL + * @param string|null $channel Slack channel (encoded ID or name) + * @param string|null $username Name of a bot + * @param bool $useAttachment Whether the message should be added to Slack as attachment (plain text otherwise) + * @param string|null $iconEmoji The emoji name to use (or null) + * @param bool $useShortAttachment Whether the the context/extra messages added to Slack as attachments are in a short style + * @param bool $includeContextAndExtra Whether the attachment should include context and extra data + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param array $excludeFields Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2'] + */ + public function __construct($webhookUrl, $channel = null, $username = null, $useAttachment = true, $iconEmoji = null, $useShortAttachment = false, $includeContextAndExtra = false, $level = Logger::CRITICAL, $bubble = true, array $excludeFields = array()) + { + parent::__construct($level, $bubble); + + $this->webhookUrl = $webhookUrl; + + $this->slackRecord = new SlackRecord( + $channel, + $username, + $useAttachment, + $iconEmoji, + $useShortAttachment, + $includeContextAndExtra, + $excludeFields, + $this->formatter + ); + } + + public function getSlackRecord() + { + return $this->slackRecord; + } + + public function getWebhookUrl() + { + return $this->webhookUrl; + } + + /** + * {@inheritdoc} + * + * @param array $record + */ + protected function write(array $record) + { + $postData = $this->slackRecord->getSlackData($record); + $postString = Utils::jsonEncode($postData); + + $ch = curl_init(); + $options = array( + CURLOPT_URL => $this->webhookUrl, + CURLOPT_POST => true, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTPHEADER => array('Content-type: application/json'), + CURLOPT_POSTFIELDS => $postString + ); + if (defined('CURLOPT_SAFE_UPLOAD')) { + $options[CURLOPT_SAFE_UPLOAD] = true; + } + + curl_setopt_array($ch, $options); + + Curl\Util::execute($ch); + } + + public function setFormatter(FormatterInterface $formatter) + { + parent::setFormatter($formatter); + $this->slackRecord->setFormatter($formatter); + + return $this; + } + + public function getFormatter() + { + $formatter = parent::getFormatter(); + $this->slackRecord->setFormatter($formatter); + + return $formatter; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SlackbotHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SlackbotHandler.php new file mode 100644 index 0000000..d3352ea --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SlackbotHandler.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Sends notifications through Slack's Slackbot + * + * @author Haralan Dobrev + * @see https://slack.com/apps/A0F81R8ET-slackbot + * @deprecated According to Slack the API used on this handler it is deprecated. + * Therefore this handler will be removed on 2.x + * Slack suggests to use webhooks instead. Please contact slack for more information. + */ +class SlackbotHandler extends AbstractProcessingHandler +{ + /** + * The slug of the Slack team + * @var string + */ + private $slackTeam; + + /** + * Slackbot token + * @var string + */ + private $token; + + /** + * Slack channel name + * @var string + */ + private $channel; + + /** + * @param string $slackTeam Slack team slug + * @param string $token Slackbot token + * @param string $channel Slack channel (encoded ID or name) + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($slackTeam, $token, $channel, $level = Logger::CRITICAL, $bubble = true) + { + @trigger_error('SlackbotHandler is deprecated and will be removed on 2.x', E_USER_DEPRECATED); + parent::__construct($level, $bubble); + + $this->slackTeam = $slackTeam; + $this->token = $token; + $this->channel = $channel; + } + + /** + * {@inheritdoc} + * + * @param array $record + */ + protected function write(array $record) + { + $slackbotUrl = sprintf( + 'https://%s.slack.com/services/hooks/slackbot?token=%s&channel=%s', + $this->slackTeam, + $this->token, + $this->channel + ); + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $slackbotUrl); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $record['message']); + + Curl\Util::execute($ch); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php new file mode 100644 index 0000000..db50d97 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php @@ -0,0 +1,385 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Stores to any socket - uses fsockopen() or pfsockopen(). + * + * @author Pablo de Leon Belloc + * @see http://php.net/manual/en/function.fsockopen.php + */ +class SocketHandler extends AbstractProcessingHandler +{ + private $connectionString; + private $connectionTimeout; + private $resource; + private $timeout = 0; + private $writingTimeout = 10; + private $lastSentBytes = null; + private $chunkSize = null; + private $persistent = false; + private $errno; + private $errstr; + private $lastWritingAt; + + /** + * @param string $connectionString Socket connection string + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($connectionString, $level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + $this->connectionString = $connectionString; + $this->connectionTimeout = (float) ini_get('default_socket_timeout'); + } + + /** + * Connect (if necessary) and write to the socket + * + * @param array $record + * + * @throws \UnexpectedValueException + * @throws \RuntimeException + */ + protected function write(array $record) + { + $this->connectIfNotConnected(); + $data = $this->generateDataStream($record); + $this->writeToSocket($data); + } + + /** + * We will not close a PersistentSocket instance so it can be reused in other requests. + */ + public function close() + { + if (!$this->isPersistent()) { + $this->closeSocket(); + } + } + + /** + * Close socket, if open + */ + public function closeSocket() + { + if (is_resource($this->resource)) { + fclose($this->resource); + $this->resource = null; + } + } + + /** + * Set socket connection to nbe persistent. It only has effect before the connection is initiated. + * + * @param bool $persistent + */ + public function setPersistent($persistent) + { + $this->persistent = (bool) $persistent; + } + + /** + * Set connection timeout. Only has effect before we connect. + * + * @param float $seconds + * + * @see http://php.net/manual/en/function.fsockopen.php + */ + public function setConnectionTimeout($seconds) + { + $this->validateTimeout($seconds); + $this->connectionTimeout = (float) $seconds; + } + + /** + * Set write timeout. Only has effect before we connect. + * + * @param float $seconds + * + * @see http://php.net/manual/en/function.stream-set-timeout.php + */ + public function setTimeout($seconds) + { + $this->validateTimeout($seconds); + $this->timeout = (float) $seconds; + } + + /** + * Set writing timeout. Only has effect during connection in the writing cycle. + * + * @param float $seconds 0 for no timeout + */ + public function setWritingTimeout($seconds) + { + $this->validateTimeout($seconds); + $this->writingTimeout = (float) $seconds; + } + + /** + * Set chunk size. Only has effect during connection in the writing cycle. + * + * @param float $bytes + */ + public function setChunkSize($bytes) + { + $this->chunkSize = $bytes; + } + + /** + * Get current connection string + * + * @return string + */ + public function getConnectionString() + { + return $this->connectionString; + } + + /** + * Get persistent setting + * + * @return bool + */ + public function isPersistent() + { + return $this->persistent; + } + + /** + * Get current connection timeout setting + * + * @return float + */ + public function getConnectionTimeout() + { + return $this->connectionTimeout; + } + + /** + * Get current in-transfer timeout + * + * @return float + */ + public function getTimeout() + { + return $this->timeout; + } + + /** + * Get current local writing timeout + * + * @return float + */ + public function getWritingTimeout() + { + return $this->writingTimeout; + } + + /** + * Get current chunk size + * + * @return float + */ + public function getChunkSize() + { + return $this->chunkSize; + } + + /** + * Check to see if the socket is currently available. + * + * UDP might appear to be connected but might fail when writing. See http://php.net/fsockopen for details. + * + * @return bool + */ + public function isConnected() + { + return is_resource($this->resource) + && !feof($this->resource); // on TCP - other party can close connection. + } + + /** + * Wrapper to allow mocking + */ + protected function pfsockopen() + { + return @pfsockopen($this->connectionString, -1, $this->errno, $this->errstr, $this->connectionTimeout); + } + + /** + * Wrapper to allow mocking + */ + protected function fsockopen() + { + return @fsockopen($this->connectionString, -1, $this->errno, $this->errstr, $this->connectionTimeout); + } + + /** + * Wrapper to allow mocking + * + * @see http://php.net/manual/en/function.stream-set-timeout.php + */ + protected function streamSetTimeout() + { + $seconds = floor($this->timeout); + $microseconds = round(($this->timeout - $seconds) * 1e6); + + return stream_set_timeout($this->resource, $seconds, $microseconds); + } + + /** + * Wrapper to allow mocking + * + * @see http://php.net/manual/en/function.stream-set-chunk-size.php + */ + protected function streamSetChunkSize() + { + return stream_set_chunk_size($this->resource, $this->chunkSize); + } + + /** + * Wrapper to allow mocking + */ + protected function fwrite($data) + { + return @fwrite($this->resource, $data); + } + + /** + * Wrapper to allow mocking + */ + protected function streamGetMetadata() + { + return stream_get_meta_data($this->resource); + } + + private function validateTimeout($value) + { + $ok = filter_var($value, FILTER_VALIDATE_FLOAT); + if ($ok === false || $value < 0) { + throw new \InvalidArgumentException("Timeout must be 0 or a positive float (got $value)"); + } + } + + private function connectIfNotConnected() + { + if ($this->isConnected()) { + return; + } + $this->connect(); + } + + protected function generateDataStream($record) + { + return (string) $record['formatted']; + } + + /** + * @return resource|null + */ + protected function getResource() + { + return $this->resource; + } + + private function connect() + { + $this->createSocketResource(); + $this->setSocketTimeout(); + $this->setStreamChunkSize(); + } + + private function createSocketResource() + { + if ($this->isPersistent()) { + $resource = $this->pfsockopen(); + } else { + $resource = $this->fsockopen(); + } + if (!$resource) { + throw new \UnexpectedValueException("Failed connecting to $this->connectionString ($this->errno: $this->errstr)"); + } + $this->resource = $resource; + } + + private function setSocketTimeout() + { + if (!$this->streamSetTimeout()) { + throw new \UnexpectedValueException("Failed setting timeout with stream_set_timeout()"); + } + } + + private function setStreamChunkSize() + { + if ($this->chunkSize && !$this->streamSetChunkSize()) { + throw new \UnexpectedValueException("Failed setting chunk size with stream_set_chunk_size()"); + } + } + + private function writeToSocket($data) + { + $length = strlen($data); + $sent = 0; + $this->lastSentBytes = $sent; + while ($this->isConnected() && $sent < $length) { + if (0 == $sent) { + $chunk = $this->fwrite($data); + } else { + $chunk = $this->fwrite(substr($data, $sent)); + } + if ($chunk === false) { + throw new \RuntimeException("Could not write to socket"); + } + $sent += $chunk; + $socketInfo = $this->streamGetMetadata(); + if ($socketInfo['timed_out']) { + throw new \RuntimeException("Write timed-out"); + } + + if ($this->writingIsTimedOut($sent)) { + throw new \RuntimeException("Write timed-out, no data sent for `{$this->writingTimeout}` seconds, probably we got disconnected (sent $sent of $length)"); + } + } + if (!$this->isConnected() && $sent < $length) { + throw new \RuntimeException("End-of-file reached, probably we got disconnected (sent $sent of $length)"); + } + } + + private function writingIsTimedOut($sent) + { + $writingTimeout = (int) floor($this->writingTimeout); + if (0 === $writingTimeout) { + return false; + } + + if ($sent !== $this->lastSentBytes) { + $this->lastWritingAt = time(); + $this->lastSentBytes = $sent; + + return false; + } else { + usleep(100); + } + + if ((time() - $this->lastWritingAt) >= $writingTimeout) { + $this->closeSocket(); + + return true; + } + + return false; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php new file mode 100644 index 0000000..ad6960c --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php @@ -0,0 +1,179 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Utils; + +/** + * Stores to any stream resource + * + * Can be used to store into php://stderr, remote and local files, etc. + * + * @author Jordi Boggiano + */ +class StreamHandler extends AbstractProcessingHandler +{ + protected $stream; + protected $url; + private $errorMessage; + protected $filePermission; + protected $useLocking; + private $dirCreated; + + /** + * @param resource|string $stream + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param int|null $filePermission Optional file permissions (default (0644) are only for owner read/write) + * @param bool $useLocking Try to lock log file before doing any writes + * + * @throws \Exception If a missing directory is not buildable + * @throws \InvalidArgumentException If stream is not a resource or string + */ + public function __construct($stream, $level = Logger::DEBUG, $bubble = true, $filePermission = null, $useLocking = false) + { + parent::__construct($level, $bubble); + if (is_resource($stream)) { + $this->stream = $stream; + } elseif (is_string($stream)) { + $this->url = Utils::canonicalizePath($stream); + } else { + throw new \InvalidArgumentException('A stream must either be a resource or a string.'); + } + + $this->filePermission = $filePermission; + $this->useLocking = $useLocking; + } + + /** + * {@inheritdoc} + */ + public function close() + { + if ($this->url && is_resource($this->stream)) { + fclose($this->stream); + } + $this->stream = null; + $this->dirCreated = null; + } + + /** + * Return the currently active stream if it is open + * + * @return resource|null + */ + public function getStream() + { + return $this->stream; + } + + /** + * Return the stream URL if it was configured with a URL and not an active resource + * + * @return string|null + */ + public function getUrl() + { + return $this->url; + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + if (!is_resource($this->stream)) { + if (null === $this->url || '' === $this->url) { + throw new \LogicException('Missing stream url, the stream can not be opened. This may be caused by a premature call to close().'); + } + $this->createDir(); + $this->errorMessage = null; + set_error_handler(array($this, 'customErrorHandler')); + $this->stream = fopen($this->url, 'a'); + if ($this->filePermission !== null) { + @chmod($this->url, $this->filePermission); + } + restore_error_handler(); + if (!is_resource($this->stream)) { + $this->stream = null; + + throw new \UnexpectedValueException(sprintf('The stream or file "%s" could not be opened in append mode: '.$this->errorMessage, $this->url)); + } + } + + if ($this->useLocking) { + // ignoring errors here, there's not much we can do about them + flock($this->stream, LOCK_EX); + } + + $this->streamWrite($this->stream, $record); + + if ($this->useLocking) { + flock($this->stream, LOCK_UN); + } + } + + /** + * Write to stream + * @param resource $stream + * @param array $record + */ + protected function streamWrite($stream, array $record) + { + fwrite($stream, (string) $record['formatted']); + } + + private function customErrorHandler($code, $msg) + { + $this->errorMessage = preg_replace('{^(fopen|mkdir)\(.*?\): }', '', $msg); + } + + /** + * @param string $stream + * + * @return null|string + */ + private function getDirFromStream($stream) + { + $pos = strpos($stream, '://'); + if ($pos === false) { + return dirname($stream); + } + + if ('file://' === substr($stream, 0, 7)) { + return dirname(substr($stream, 7)); + } + + return null; + } + + private function createDir() + { + // Do not try to create dir if it has already been tried. + if ($this->dirCreated) { + return; + } + + $dir = $this->getDirFromStream($this->url); + if (null !== $dir && !is_dir($dir)) { + $this->errorMessage = null; + set_error_handler(array($this, 'customErrorHandler')); + $status = mkdir($dir, 0777, true); + restore_error_handler(); + if (false === $status && !is_dir($dir)) { + throw new \UnexpectedValueException(sprintf('There is no existing directory at "%s" and its not buildable: '.$this->errorMessage, $dir)); + } + } + $this->dirCreated = true; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php new file mode 100644 index 0000000..ac7b16f --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\LineFormatter; +use Swift; + +/** + * SwiftMailerHandler uses Swift_Mailer to send the emails + * + * @author Gyula Sallai + */ +class SwiftMailerHandler extends MailHandler +{ + protected $mailer; + private $messageTemplate; + + /** + * @param \Swift_Mailer $mailer The mailer to use + * @param callable|\Swift_Message $message An example message for real messages, only the body will be replaced + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(\Swift_Mailer $mailer, $message, $level = Logger::ERROR, $bubble = true) + { + parent::__construct($level, $bubble); + + $this->mailer = $mailer; + $this->messageTemplate = $message; + } + + /** + * {@inheritdoc} + */ + protected function send($content, array $records) + { + $this->mailer->send($this->buildMessage($content, $records)); + } + + /** + * Gets the formatter for the Swift_Message subject. + * + * @param string $format The format of the subject + * @return FormatterInterface + */ + protected function getSubjectFormatter($format) + { + return new LineFormatter($format); + } + + /** + * Creates instance of Swift_Message to be sent + * + * @param string $content formatted email body to be sent + * @param array $records Log records that formed the content + * @return \Swift_Message + */ + protected function buildMessage($content, array $records) + { + $message = null; + if ($this->messageTemplate instanceof \Swift_Message) { + $message = clone $this->messageTemplate; + $message->generateId(); + } elseif (is_callable($this->messageTemplate)) { + $message = call_user_func($this->messageTemplate, $content, $records); + } + + if (!$message instanceof \Swift_Message) { + throw new \InvalidArgumentException('Could not resolve message as instance of Swift_Message or a callable returning it'); + } + + if ($records) { + $subjectFormatter = $this->getSubjectFormatter($message->getSubject()); + $message->setSubject($subjectFormatter->format($this->getHighestRecord($records))); + } + + $message->setBody($content); + if (version_compare(Swift::VERSION, '6.0.0', '>=')) { + $message->setDate(new \DateTimeImmutable()); + } else { + $message->setDate(time()); + } + + return $message; + } + + /** + * BC getter, to be removed in 2.0 + */ + public function __get($name) + { + if ($name === 'message') { + trigger_error('SwiftMailerHandler->message is deprecated, use ->buildMessage() instead to retrieve the message', E_USER_DEPRECATED); + + return $this->buildMessage(null, array()); + } + + throw new \InvalidArgumentException('Invalid property '.$name); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php new file mode 100644 index 0000000..f770c80 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Logs to syslog service. + * + * usage example: + * + * $log = new Logger('application'); + * $syslog = new SyslogHandler('myfacility', 'local6'); + * $formatter = new LineFormatter("%channel%.%level_name%: %message% %extra%"); + * $syslog->setFormatter($formatter); + * $log->pushHandler($syslog); + * + * @author Sven Paulus + */ +class SyslogHandler extends AbstractSyslogHandler +{ + protected $ident; + protected $logopts; + + /** + * @param string $ident + * @param mixed $facility + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param int $logopts Option flags for the openlog() call, defaults to LOG_PID + */ + public function __construct($ident, $facility = LOG_USER, $level = Logger::DEBUG, $bubble = true, $logopts = LOG_PID) + { + parent::__construct($facility, $level, $bubble); + + $this->ident = $ident; + $this->logopts = $logopts; + } + + /** + * {@inheritdoc} + */ + public function close() + { + closelog(); + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + if (!openlog($this->ident, $this->logopts, $this->facility)) { + throw new \LogicException('Can\'t open syslog for ident "'.$this->ident.'" and facility "'.$this->facility.'"'); + } + syslog($this->logLevels[$record['level']], (string) $record['formatted']); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php b/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php new file mode 100644 index 0000000..3bff085 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\SyslogUdp; + +class UdpSocket +{ + const DATAGRAM_MAX_LENGTH = 65023; + + protected $ip; + protected $port; + protected $socket; + + public function __construct($ip, $port = 514) + { + $this->ip = $ip; + $this->port = $port; + $this->socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); + } + + public function write($line, $header = "") + { + $this->send($this->assembleMessage($line, $header)); + } + + public function close() + { + if (is_resource($this->socket)) { + socket_close($this->socket); + $this->socket = null; + } + } + + protected function send($chunk) + { + if (!is_resource($this->socket)) { + throw new \LogicException('The UdpSocket to '.$this->ip.':'.$this->port.' has been closed and can not be written to anymore'); + } + socket_sendto($this->socket, $chunk, strlen($chunk), $flags = 0, $this->ip, $this->port); + } + + protected function assembleMessage($line, $header) + { + $chunkSize = self::DATAGRAM_MAX_LENGTH - strlen($header); + + return $header . substr($line, 0, $chunkSize); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php new file mode 100644 index 0000000..4dfd5f5 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php @@ -0,0 +1,124 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Handler\SyslogUdp\UdpSocket; + +/** + * A Handler for logging to a remote syslogd server. + * + * @author Jesper Skovgaard Nielsen + * @author Dominik Kukacka + */ +class SyslogUdpHandler extends AbstractSyslogHandler +{ + const RFC3164 = 0; + const RFC5424 = 1; + + private $dateFormats = array( + self::RFC3164 => 'M d H:i:s', + self::RFC5424 => \DateTime::RFC3339, + ); + + protected $socket; + protected $ident; + protected $rfc; + + /** + * @param string $host + * @param int $port + * @param mixed $facility + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param string $ident Program name or tag for each log message. + * @param int $rfc RFC to format the message for. + */ + public function __construct($host, $port = 514, $facility = LOG_USER, $level = Logger::DEBUG, $bubble = true, $ident = 'php', $rfc = self::RFC5424) + { + parent::__construct($facility, $level, $bubble); + + $this->ident = $ident; + $this->rfc = $rfc; + + $this->socket = new UdpSocket($host, $port ?: 514); + } + + protected function write(array $record) + { + $lines = $this->splitMessageIntoLines($record['formatted']); + + $header = $this->makeCommonSyslogHeader($this->logLevels[$record['level']]); + + foreach ($lines as $line) { + $this->socket->write($line, $header); + } + } + + public function close() + { + $this->socket->close(); + } + + private function splitMessageIntoLines($message) + { + if (is_array($message)) { + $message = implode("\n", $message); + } + + return preg_split('/$\R?^/m', $message, -1, PREG_SPLIT_NO_EMPTY); + } + + /** + * Make common syslog header (see rfc5424 or rfc3164) + */ + protected function makeCommonSyslogHeader($severity) + { + $priority = $severity + $this->facility; + + if (!$pid = getmypid()) { + $pid = '-'; + } + + if (!$hostname = gethostname()) { + $hostname = '-'; + } + + $date = $this->getDateTime(); + + if ($this->rfc === self::RFC3164) { + return "<$priority>" . + $date . " " . + $hostname . " " . + $this->ident . "[" . $pid . "]: "; + } else { + return "<$priority>1 " . + $date . " " . + $hostname . " " . + $this->ident . " " . + $pid . " - - "; + } + } + + protected function getDateTime() + { + return date($this->dateFormats[$this->rfc]); + } + + /** + * Inject your own socket, mainly used for testing + */ + public function setSocket($socket) + { + $this->socket = $socket; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php new file mode 100644 index 0000000..478db0a --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php @@ -0,0 +1,177 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * Used for testing purposes. + * + * It records all records and gives you access to them for verification. + * + * @author Jordi Boggiano + * + * @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 TestHandler extends AbstractProcessingHandler +{ + protected $records = array(); + protected $recordsByLevel = array(); + private $skipReset = false; + + public function getRecords() + { + return $this->records; + } + + public function clear() + { + $this->records = array(); + $this->recordsByLevel = array(); + } + + public function reset() + { + if (!$this->skipReset) { + $this->clear(); + } + } + + public function setSkipReset($skipReset) + { + $this->skipReset = $skipReset; + } + + public function hasRecords($level) + { + return isset($this->recordsByLevel[$level]); + } + + /** + * @param string|array $record Either a message string or an array containing message and optionally context keys that will be checked against all records + * @param int $level Logger::LEVEL constant value + */ + public function hasRecord($record, $level) + { + if (is_string($record)) { + $record = array('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($predicate, $level) + { + if (!is_callable($predicate)) { + throw new \InvalidArgumentException("Expected a callable for hasRecordThatSucceeds"); + } + + 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; + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $this->recordsByLevel[$record['level']][] = $record; + $this->records[] = $record; + } + + 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 = constant('Monolog\Logger::' . strtoupper($matches[2])); + if (method_exists($this, $genericMethod)) { + $args[] = $level; + + return call_user_func_array(array($this, $genericMethod), $args); + } + } + + throw new \BadMethodCallException('Call to undefined method ' . get_class($this) . '::' . $method . '()'); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php new file mode 100644 index 0000000..7d7622a --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * Forwards records to multiple handlers suppressing failures of each handler + * and continuing through to give every handler a chance to succeed. + * + * @author Craig D'Amelio + */ +class WhatFailureGroupHandler extends GroupHandler +{ + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if ($this->processors) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + } + + foreach ($this->handlers as $handler) { + try { + $handler->handle($record); + } catch (\Exception $e) { + // What failure? + } catch (\Throwable $e) { + // What failure? + } + } + + return false === $this->bubble; + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + if ($this->processors) { + $processed = array(); + foreach ($records as $record) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + $processed[] = $record; + } + $records = $processed; + } + + foreach ($this->handlers as $handler) { + try { + $handler->handleBatch($records); + } catch (\Exception $e) { + // What failure? + } catch (\Throwable $e) { + // What failure? + } + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php new file mode 100644 index 0000000..a20aeae --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\NormalizerFormatter; +use Monolog\Logger; + +/** + * Handler sending logs to Zend Monitor + * + * @author Christian Bergau + * @author Jason Davis + */ +class ZendMonitorHandler extends AbstractProcessingHandler +{ + /** + * Monolog level / ZendMonitor Custom Event priority map + * + * @var array + */ + protected $levelMap = array(); + + /** + * Construct + * + * @param int $level + * @param bool $bubble + * @throws MissingExtensionException + */ + public function __construct($level = Logger::DEBUG, $bubble = true) + { + if (!function_exists('zend_monitor_custom_event')) { + throw new MissingExtensionException( + 'You must have Zend Server installed with Zend Monitor enabled in order to use this handler' + ); + } + //zend monitor constants are not defined if zend monitor is not enabled. + $this->levelMap = array( + Logger::DEBUG => \ZEND_MONITOR_EVENT_SEVERITY_INFO, + Logger::INFO => \ZEND_MONITOR_EVENT_SEVERITY_INFO, + Logger::NOTICE => \ZEND_MONITOR_EVENT_SEVERITY_INFO, + Logger::WARNING => \ZEND_MONITOR_EVENT_SEVERITY_WARNING, + Logger::ERROR => \ZEND_MONITOR_EVENT_SEVERITY_ERROR, + Logger::CRITICAL => \ZEND_MONITOR_EVENT_SEVERITY_ERROR, + Logger::ALERT => \ZEND_MONITOR_EVENT_SEVERITY_ERROR, + Logger::EMERGENCY => \ZEND_MONITOR_EVENT_SEVERITY_ERROR, + ); + parent::__construct($level, $bubble); + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $this->writeZendMonitorCustomEvent( + Logger::getLevelName($record['level']), + $record['message'], + $record['formatted'], + $this->levelMap[$record['level']] + ); + } + + /** + * Write to Zend Monitor Events + * @param string $type Text displayed in "Class Name (custom)" field + * @param string $message Text displayed in "Error String" + * @param mixed $formatted Displayed in Custom Variables tab + * @param int $severity Set the event severity level (-1,0,1) + */ + protected function writeZendMonitorCustomEvent($type, $message, $formatted, $severity) + { + zend_monitor_custom_event($type, $message, $formatted, $severity); + } + + /** + * {@inheritdoc} + */ + public function getDefaultFormatter() + { + return new NormalizerFormatter(); + } + + /** + * Get the level map + * + * @return array + */ + public function getLevelMap() + { + return $this->levelMap; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Logger.php b/vendor/monolog/monolog/src/Monolog/Logger.php new file mode 100644 index 0000000..7d26b29 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Logger.php @@ -0,0 +1,796 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use Monolog\Handler\HandlerInterface; +use Monolog\Handler\StreamHandler; +use Psr\Log\LoggerInterface; +use Psr\Log\InvalidArgumentException; +use Exception; + +/** + * Monolog log channel + * + * It contains a stack of Handlers and a stack of Processors, + * and uses them to store records that are added to it. + * + * @author Jordi Boggiano + */ +class Logger implements LoggerInterface, ResettableInterface +{ + /** + * Detailed debug information + */ + const DEBUG = 100; + + /** + * Interesting events + * + * Examples: User logs in, SQL logs. + */ + const INFO = 200; + + /** + * Uncommon events + */ + const NOTICE = 250; + + /** + * Exceptional occurrences that are not errors + * + * Examples: Use of deprecated APIs, poor use of an API, + * undesirable things that are not necessarily wrong. + */ + const WARNING = 300; + + /** + * Runtime errors + */ + const ERROR = 400; + + /** + * Critical conditions + * + * Example: Application component unavailable, unexpected exception. + */ + const CRITICAL = 500; + + /** + * Action must be taken immediately + * + * Example: Entire website down, database unavailable, etc. + * This should trigger the SMS alerts and wake you up. + */ + const ALERT = 550; + + /** + * Urgent alert. + */ + const EMERGENCY = 600; + + /** + * Monolog API version + * + * This is only bumped when API breaks are done and should + * follow the major version of the library + * + * @var int + */ + const API = 1; + + /** + * Logging levels from syslog protocol defined in RFC 5424 + * + * @var array $levels Logging levels + */ + protected static $levels = array( + self::DEBUG => 'DEBUG', + self::INFO => 'INFO', + self::NOTICE => 'NOTICE', + self::WARNING => 'WARNING', + self::ERROR => 'ERROR', + self::CRITICAL => 'CRITICAL', + self::ALERT => 'ALERT', + self::EMERGENCY => 'EMERGENCY', + ); + + /** + * @var \DateTimeZone + */ + protected static $timezone; + + /** + * @var string + */ + protected $name; + + /** + * The handler stack + * + * @var HandlerInterface[] + */ + protected $handlers; + + /** + * Processors that will process all log records + * + * To process records of a single handler instead, add the processor on that specific handler + * + * @var callable[] + */ + protected $processors; + + /** + * @var bool + */ + protected $microsecondTimestamps = true; + + /** + * @var callable + */ + protected $exceptionHandler; + + /** + * @param string $name The logging channel + * @param HandlerInterface[] $handlers Optional stack of handlers, the first one in the array is called first, etc. + * @param callable[] $processors Optional array of processors + */ + public function __construct($name, array $handlers = array(), array $processors = array()) + { + $this->name = $name; + $this->setHandlers($handlers); + $this->processors = $processors; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Return a new cloned instance with the name changed + * + * @return static + */ + public function withName($name) + { + $new = clone $this; + $new->name = $name; + + return $new; + } + + /** + * Pushes a handler on to the stack. + * + * @param HandlerInterface $handler + * @return $this + */ + public function pushHandler(HandlerInterface $handler) + { + array_unshift($this->handlers, $handler); + + return $this; + } + + /** + * Pops a handler from the stack + * + * @return HandlerInterface + */ + public function popHandler() + { + if (!$this->handlers) { + throw new \LogicException('You tried to pop from an empty handler stack.'); + } + + return array_shift($this->handlers); + } + + /** + * Set handlers, replacing all existing ones. + * + * If a map is passed, keys will be ignored. + * + * @param HandlerInterface[] $handlers + * @return $this + */ + public function setHandlers(array $handlers) + { + $this->handlers = array(); + foreach (array_reverse($handlers) as $handler) { + $this->pushHandler($handler); + } + + return $this; + } + + /** + * @return HandlerInterface[] + */ + public function getHandlers() + { + return $this->handlers; + } + + /** + * Adds a processor on to the stack. + * + * @param callable $callback + * @return $this + */ + public function pushProcessor($callback) + { + if (!is_callable($callback)) { + throw new \InvalidArgumentException('Processors must be valid callables (callback or object with an __invoke method), '.var_export($callback, true).' given'); + } + array_unshift($this->processors, $callback); + + return $this; + } + + /** + * Removes the processor on top of the stack and returns it. + * + * @return callable + */ + public function popProcessor() + { + if (!$this->processors) { + throw new \LogicException('You tried to pop from an empty processor stack.'); + } + + return array_shift($this->processors); + } + + /** + * @return callable[] + */ + public function getProcessors() + { + return $this->processors; + } + + /** + * Control the use of microsecond resolution timestamps in the 'datetime' + * member of new records. + * + * Generating microsecond resolution timestamps by calling + * microtime(true), formatting the result via sprintf() and then parsing + * the resulting string via \DateTime::createFromFormat() can incur + * a measurable runtime overhead vs simple usage of DateTime to capture + * a second resolution timestamp in systems which generate a large number + * of log events. + * + * @param bool $micro True to use microtime() to create timestamps + */ + public function useMicrosecondTimestamps($micro) + { + $this->microsecondTimestamps = (bool) $micro; + } + + /** + * Adds a log record. + * + * @param int $level The logging level + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function addRecord($level, $message, array $context = array()) + { + if (!$this->handlers) { + $this->pushHandler(new StreamHandler('php://stderr', static::DEBUG)); + } + + $levelName = static::getLevelName($level); + + // check if any handler will handle this message so we can return early and save cycles + $handlerKey = null; + reset($this->handlers); + while ($handler = current($this->handlers)) { + if ($handler->isHandling(array('level' => $level))) { + $handlerKey = key($this->handlers); + break; + } + + next($this->handlers); + } + + if (null === $handlerKey) { + return false; + } + + if (!static::$timezone) { + static::$timezone = new \DateTimeZone(date_default_timezone_get() ?: 'UTC'); + } + + // php7.1+ always has microseconds enabled, so we do not need this hack + if ($this->microsecondTimestamps && PHP_VERSION_ID < 70100) { + $ts = \DateTime::createFromFormat('U.u', sprintf('%.6F', microtime(true)), static::$timezone); + } else { + $ts = new \DateTime('now', static::$timezone); + } + $ts->setTimezone(static::$timezone); + + $record = array( + 'message' => (string) $message, + 'context' => $context, + 'level' => $level, + 'level_name' => $levelName, + 'channel' => $this->name, + 'datetime' => $ts, + 'extra' => array(), + ); + + try { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + + while ($handler = current($this->handlers)) { + if (true === $handler->handle($record)) { + break; + } + + next($this->handlers); + } + } catch (Exception $e) { + $this->handleException($e, $record); + } + + return true; + } + + /** + * Ends a log cycle and frees all resources used by handlers. + * + * Closing a Handler means flushing all buffers and freeing any open resources/handles. + * Handlers that have been closed should be able to accept log records again and re-open + * themselves on demand, but this may not always be possible depending on implementation. + * + * This is useful at the end of a request and will be called automatically on every handler + * when they get destructed. + */ + public function close() + { + foreach ($this->handlers as $handler) { + if (method_exists($handler, 'close')) { + $handler->close(); + } + } + } + + /** + * Ends a log cycle and resets all handlers and processors to their initial state. + * + * Resetting a Handler or a Processor means flushing/cleaning all buffers, resetting internal + * state, and getting it back to a state in which it can receive log records again. + * + * This is useful in case you want to avoid logs leaking between two requests or jobs when you + * have a long running process like a worker or an application server serving multiple requests + * in one process. + */ + public function reset() + { + foreach ($this->handlers as $handler) { + if ($handler instanceof ResettableInterface) { + $handler->reset(); + } + } + + foreach ($this->processors as $processor) { + if ($processor instanceof ResettableInterface) { + $processor->reset(); + } + } + } + + /** + * Adds a log record at the DEBUG level. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function addDebug($message, array $context = array()) + { + return $this->addRecord(static::DEBUG, $message, $context); + } + + /** + * Adds a log record at the INFO level. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function addInfo($message, array $context = array()) + { + return $this->addRecord(static::INFO, $message, $context); + } + + /** + * Adds a log record at the NOTICE level. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function addNotice($message, array $context = array()) + { + return $this->addRecord(static::NOTICE, $message, $context); + } + + /** + * Adds a log record at the WARNING level. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function addWarning($message, array $context = array()) + { + return $this->addRecord(static::WARNING, $message, $context); + } + + /** + * Adds a log record at the ERROR level. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function addError($message, array $context = array()) + { + return $this->addRecord(static::ERROR, $message, $context); + } + + /** + * Adds a log record at the CRITICAL level. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function addCritical($message, array $context = array()) + { + return $this->addRecord(static::CRITICAL, $message, $context); + } + + /** + * Adds a log record at the ALERT level. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function addAlert($message, array $context = array()) + { + return $this->addRecord(static::ALERT, $message, $context); + } + + /** + * Adds a log record at the EMERGENCY level. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function addEmergency($message, array $context = array()) + { + return $this->addRecord(static::EMERGENCY, $message, $context); + } + + /** + * Gets all supported logging levels. + * + * @return array Assoc array with human-readable level names => level codes. + */ + public static function getLevels() + { + return array_flip(static::$levels); + } + + /** + * Gets the name of the logging level. + * + * @param int $level + * @return string + */ + public static function getLevelName($level) + { + if (!isset(static::$levels[$level])) { + throw new InvalidArgumentException('Level "'.$level.'" is not defined, use one of: '.implode(', ', array_keys(static::$levels))); + } + + return static::$levels[$level]; + } + + /** + * Converts PSR-3 levels to Monolog ones if necessary + * + * @param string|int $level Level number (monolog) or name (PSR-3) + * @return int + */ + public static function toMonologLevel($level) + { + if (is_string($level)) { + // Contains chars of all log levels and avoids using strtoupper() which may have + // strange results depending on locale (for example, "i" will become "İ") + $upper = strtr($level, 'abcdefgilmnortuwy', 'ABCDEFGILMNORTUWY'); + if (defined(__CLASS__.'::'.$upper)) { + return constant(__CLASS__ . '::' . $upper); + } + } + + return $level; + } + + /** + * Checks whether the Logger has a handler that listens on the given level + * + * @param int $level + * @return bool + */ + public function isHandling($level) + { + $record = array( + 'level' => $level, + ); + + foreach ($this->handlers as $handler) { + if ($handler->isHandling($record)) { + return true; + } + } + + return false; + } + + /** + * Set a custom exception handler + * + * @param callable $callback + * @return $this + */ + public function setExceptionHandler($callback) + { + if (!is_callable($callback)) { + throw new \InvalidArgumentException('Exception handler must be valid callable (callback or object with an __invoke method), '.var_export($callback, true).' given'); + } + $this->exceptionHandler = $callback; + + return $this; + } + + /** + * @return callable + */ + public function getExceptionHandler() + { + return $this->exceptionHandler; + } + + /** + * Delegates exception management to the custom exception handler, + * or throws the exception if no custom handler is set. + */ + protected function handleException(Exception $e, array $record) + { + if (!$this->exceptionHandler) { + throw $e; + } + + call_user_func($this->exceptionHandler, $e, $record); + } + + /** + * Adds a log record at an arbitrary level. + * + * This method allows for compatibility with common interfaces. + * + * @param mixed $level The log level + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function log($level, $message, array $context = array()) + { + $level = static::toMonologLevel($level); + + return $this->addRecord($level, $message, $context); + } + + /** + * Adds a log record at the DEBUG level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function debug($message, array $context = array()) + { + return $this->addRecord(static::DEBUG, $message, $context); + } + + /** + * Adds a log record at the INFO level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function info($message, array $context = array()) + { + return $this->addRecord(static::INFO, $message, $context); + } + + /** + * Adds a log record at the NOTICE level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function notice($message, array $context = array()) + { + return $this->addRecord(static::NOTICE, $message, $context); + } + + /** + * Adds a log record at the WARNING level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function warn($message, array $context = array()) + { + return $this->addRecord(static::WARNING, $message, $context); + } + + /** + * Adds a log record at the WARNING level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function warning($message, array $context = array()) + { + return $this->addRecord(static::WARNING, $message, $context); + } + + /** + * Adds a log record at the ERROR level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function err($message, array $context = array()) + { + return $this->addRecord(static::ERROR, $message, $context); + } + + /** + * Adds a log record at the ERROR level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function error($message, array $context = array()) + { + return $this->addRecord(static::ERROR, $message, $context); + } + + /** + * Adds a log record at the CRITICAL level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function crit($message, array $context = array()) + { + return $this->addRecord(static::CRITICAL, $message, $context); + } + + /** + * Adds a log record at the CRITICAL level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function critical($message, array $context = array()) + { + return $this->addRecord(static::CRITICAL, $message, $context); + } + + /** + * Adds a log record at the ALERT level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function alert($message, array $context = array()) + { + return $this->addRecord(static::ALERT, $message, $context); + } + + /** + * Adds a log record at the EMERGENCY level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function emerg($message, array $context = array()) + { + return $this->addRecord(static::EMERGENCY, $message, $context); + } + + /** + * Adds a log record at the EMERGENCY level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function emergency($message, array $context = array()) + { + return $this->addRecord(static::EMERGENCY, $message, $context); + } + + /** + * Set the timezone to be used for the timestamp of log records. + * + * This is stored globally for all Logger instances + * + * @param \DateTimeZone $tz Timezone object + */ + public static function setTimezone(\DateTimeZone $tz) + { + self::$timezone = $tz; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/GitProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/GitProcessor.php new file mode 100644 index 0000000..cdf5ec7 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/GitProcessor.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\Logger; + +/** + * Injects Git branch and Git commit SHA in all records + * + * @author Nick Otter + * @author Jordi Boggiano + */ +class GitProcessor implements ProcessorInterface +{ + private $level; + private static $cache; + + public function __construct($level = Logger::DEBUG) + { + $this->level = Logger::toMonologLevel($level); + } + + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + // return if the level is not high enough + if ($record['level'] < $this->level) { + return $record; + } + + $record['extra']['git'] = self::getGitInfo(); + + return $record; + } + + private static function getGitInfo() + { + if (self::$cache) { + return self::$cache; + } + + $branches = `git branch -v --no-abbrev`; + if ($branches && preg_match('{^\* (.+?)\s+([a-f0-9]{40})(?:\s|$)}m', $branches, $matches)) { + return self::$cache = array( + 'branch' => $matches[1], + 'commit' => $matches[2], + ); + } + + return self::$cache = array(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php new file mode 100644 index 0000000..6ae192a --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\Logger; + +/** + * Injects line/file:class/function where the log message came from + * + * Warning: This only works if the handler processes the logs directly. + * If you put the processor on a handler that is behind a FingersCrossedHandler + * for example, the processor will only be called once the trigger level is reached, + * and all the log records will have the same file/line/.. data from the call that + * triggered the FingersCrossedHandler. + * + * @author Jordi Boggiano + */ +class IntrospectionProcessor implements ProcessorInterface +{ + private $level; + + private $skipClassesPartials; + + private $skipStackFramesCount; + + private $skipFunctions = array( + 'call_user_func', + 'call_user_func_array', + ); + + public function __construct($level = Logger::DEBUG, array $skipClassesPartials = array(), $skipStackFramesCount = 0) + { + $this->level = Logger::toMonologLevel($level); + $this->skipClassesPartials = array_merge(array('Monolog\\'), $skipClassesPartials); + $this->skipStackFramesCount = $skipStackFramesCount; + } + + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + // return if the level is not high enough + if ($record['level'] < $this->level) { + return $record; + } + + /* + * http://php.net/manual/en/function.debug-backtrace.php + * As of 5.3.6, DEBUG_BACKTRACE_IGNORE_ARGS option was added. + * Any version less than 5.3.6 must use the DEBUG_BACKTRACE_IGNORE_ARGS constant value '2'. + */ + $trace = debug_backtrace((PHP_VERSION_ID < 50306) ? 2 : DEBUG_BACKTRACE_IGNORE_ARGS); + + // skip first since it's always the current method + array_shift($trace); + // the call_user_func call is also skipped + array_shift($trace); + + $i = 0; + + while ($this->isTraceClassOrSkippedFunction($trace, $i)) { + if (isset($trace[$i]['class'])) { + foreach ($this->skipClassesPartials as $part) { + if (strpos($trace[$i]['class'], $part) !== false) { + $i++; + continue 2; + } + } + } elseif (in_array($trace[$i]['function'], $this->skipFunctions)) { + $i++; + continue; + } + + break; + } + + $i += $this->skipStackFramesCount; + + // we should have the call source now + $record['extra'] = array_merge( + $record['extra'], + array( + 'file' => isset($trace[$i - 1]['file']) ? $trace[$i - 1]['file'] : null, + 'line' => isset($trace[$i - 1]['line']) ? $trace[$i - 1]['line'] : null, + 'class' => isset($trace[$i]['class']) ? $trace[$i]['class'] : null, + 'function' => isset($trace[$i]['function']) ? $trace[$i]['function'] : null, + ) + ); + + return $record; + } + + private function isTraceClassOrSkippedFunction(array $trace, $index) + { + if (!isset($trace[$index])) { + return false; + } + + return isset($trace[$index]['class']) || in_array($trace[$index]['function'], $this->skipFunctions); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php new file mode 100644 index 0000000..0543e92 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Injects memory_get_peak_usage in all records + * + * @see Monolog\Processor\MemoryProcessor::__construct() for options + * @author Rob Jensen + */ +class MemoryPeakUsageProcessor extends MemoryProcessor +{ + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + $bytes = memory_get_peak_usage($this->realUsage); + $formatted = $this->formatBytes($bytes); + + $record['extra']['memory_peak_usage'] = $formatted; + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php new file mode 100644 index 0000000..2a379a3 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Some methods that are common for all memory processors + * + * @author Rob Jensen + */ +abstract class MemoryProcessor implements ProcessorInterface +{ + /** + * @var bool If true, get the real size of memory allocated from system. Else, only the memory used by emalloc() is reported. + */ + protected $realUsage; + + /** + * @var bool If true, then format memory size to human readable string (MB, KB, B depending on size) + */ + protected $useFormatting; + + /** + * @param bool $realUsage Set this to true to get the real size of memory allocated from system. + * @param bool $useFormatting If true, then format memory size to human readable string (MB, KB, B depending on size) + */ + public function __construct($realUsage = true, $useFormatting = true) + { + $this->realUsage = (bool) $realUsage; + $this->useFormatting = (bool) $useFormatting; + } + + /** + * Formats bytes into a human readable string if $this->useFormatting is true, otherwise return $bytes as is + * + * @param int $bytes + * @return string|int Formatted string if $this->useFormatting is true, otherwise return $bytes as is + */ + protected function formatBytes($bytes) + { + $bytes = (int) $bytes; + + if (!$this->useFormatting) { + return $bytes; + } + + if ($bytes > 1024 * 1024) { + return round($bytes / 1024 / 1024, 2).' MB'; + } elseif ($bytes > 1024) { + return round($bytes / 1024, 2).' KB'; + } + + return $bytes . ' B'; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php new file mode 100644 index 0000000..2783d65 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Injects memory_get_usage in all records + * + * @see Monolog\Processor\MemoryProcessor::__construct() for options + * @author Rob Jensen + */ +class MemoryUsageProcessor extends MemoryProcessor +{ + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + $bytes = memory_get_usage($this->realUsage); + $formatted = $this->formatBytes($bytes); + + $record['extra']['memory_usage'] = $formatted; + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php new file mode 100644 index 0000000..2f5b326 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\Logger; + +/** + * Injects Hg branch and Hg revision number in all records + * + * @author Jonathan A. Schweder + */ +class MercurialProcessor implements ProcessorInterface +{ + private $level; + private static $cache; + + public function __construct($level = Logger::DEBUG) + { + $this->level = Logger::toMonologLevel($level); + } + + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + // return if the level is not high enough + if ($record['level'] < $this->level) { + return $record; + } + + $record['extra']['hg'] = self::getMercurialInfo(); + + return $record; + } + + private static function getMercurialInfo() + { + if (self::$cache) { + return self::$cache; + } + + $result = explode(' ', trim(`hg id -nb`)); + if (count($result) >= 3) { + return self::$cache = array( + 'branch' => $result[1], + 'revision' => $result[2], + ); + } + + return self::$cache = array(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php new file mode 100644 index 0000000..66b80fb --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Adds value of getmypid into records + * + * @author Andreas Hörnicke + */ +class ProcessIdProcessor implements ProcessorInterface +{ + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + $record['extra']['process_id'] = getmypid(); + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/ProcessorInterface.php b/vendor/monolog/monolog/src/Monolog/Processor/ProcessorInterface.php new file mode 100644 index 0000000..7e64d4d --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/ProcessorInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * An optional interface to allow labelling Monolog processors. + * + * @author Nicolas Grekas + */ +interface ProcessorInterface +{ + /** + * @return array The processed records + */ + public function __invoke(array $records); +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php new file mode 100644 index 0000000..a318af7 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\Utils; + +/** + * Processes a record's message according to PSR-3 rules + * + * It replaces {foo} with the value from $context['foo'] + * + * @author Jordi Boggiano + */ +class PsrLogMessageProcessor implements ProcessorInterface +{ + const SIMPLE_DATE = "Y-m-d\TH:i:s.uP"; + + /** @var string|null */ + private $dateFormat; + + /** @var bool */ + private $removeUsedContextFields; + + /** + * @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format + * @param bool $removeUsedContextFields If set to true the fields interpolated into message gets unset + */ + public function __construct($dateFormat = null, $removeUsedContextFields = false) + { + $this->dateFormat = $dateFormat; + $this->removeUsedContextFields = $removeUsedContextFields; + } + + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + if (false === strpos($record['message'], '{')) { + return $record; + } + + $replacements = array(); + foreach ($record['context'] as $key => $val) { + $placeholder = '{' . $key . '}'; + if (strpos($record['message'], $placeholder) === false) { + continue; + } + + if (is_null($val) || is_scalar($val) || (is_object($val) && method_exists($val, "__toString"))) { + $replacements[$placeholder] = $val; + } elseif ($val instanceof \DateTime) { + $replacements[$placeholder] = $val->format($this->dateFormat ?: static::SIMPLE_DATE); + } elseif (is_object($val)) { + $replacements[$placeholder] = '[object '.Utils::getClass($val).']'; + } elseif (is_array($val)) { + $replacements[$placeholder] = 'array'.Utils::jsonEncode($val, null, true); + } else { + $replacements[$placeholder] = '['.gettype($val).']'; + } + + if ($this->removeUsedContextFields) { + unset($record['context'][$key]); + } + } + + $record['message'] = strtr($record['message'], $replacements); + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php new file mode 100644 index 0000000..615a4d9 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Adds a tags array into record + * + * @author Martijn Riemers + */ +class TagProcessor implements ProcessorInterface +{ + private $tags; + + public function __construct(array $tags = array()) + { + $this->setTags($tags); + } + + public function addTags(array $tags = array()) + { + $this->tags = array_merge($this->tags, $tags); + } + + public function setTags(array $tags = array()) + { + $this->tags = $tags; + } + + public function __invoke(array $record) + { + $record['extra']['tags'] = $this->tags; + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php new file mode 100644 index 0000000..d1f708c --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\ResettableInterface; + +/** + * Adds a unique identifier into records + * + * @author Simon Mönch + */ +class UidProcessor implements ProcessorInterface, ResettableInterface +{ + private $uid; + + public function __construct($length = 7) + { + if (!is_int($length) || $length > 32 || $length < 1) { + throw new \InvalidArgumentException('The uid length must be an integer between 1 and 32'); + } + + + $this->uid = $this->generateUid($length); + } + + public function __invoke(array $record) + { + $record['extra']['uid'] = $this->uid; + + return $record; + } + + /** + * @return string + */ + public function getUid() + { + return $this->uid; + } + + public function reset() + { + $this->uid = $this->generateUid(strlen($this->uid)); + } + + private function generateUid($length) + { + return substr(hash('md5', uniqid('', true)), 0, $length); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php new file mode 100644 index 0000000..2e8dfae --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Injects url/method and remote IP of the current web request in all records + * + * @author Jordi Boggiano + */ +class WebProcessor implements ProcessorInterface +{ + /** + * @var array|\ArrayAccess + */ + protected $serverData; + + /** + * Default fields + * + * Array is structured as [key in record.extra => key in $serverData] + * + * @var array + */ + protected $extraFields = array( + 'url' => 'REQUEST_URI', + 'ip' => 'REMOTE_ADDR', + 'http_method' => 'REQUEST_METHOD', + 'server' => 'SERVER_NAME', + 'referrer' => 'HTTP_REFERER', + ); + + /** + * @param array|\ArrayAccess $serverData Array or object w/ ArrayAccess that provides access to the $_SERVER data + * @param array|null $extraFields Field names and the related key inside $serverData to be added. If not provided it defaults to: url, ip, http_method, server, referrer + */ + public function __construct($serverData = null, array $extraFields = null) + { + if (null === $serverData) { + $this->serverData = &$_SERVER; + } elseif (is_array($serverData) || $serverData instanceof \ArrayAccess) { + $this->serverData = $serverData; + } else { + throw new \UnexpectedValueException('$serverData must be an array or object implementing ArrayAccess.'); + } + + if (isset($this->serverData['UNIQUE_ID'])) { + $this->extraFields['unique_id'] = 'UNIQUE_ID'; + } + + if (null !== $extraFields) { + if (isset($extraFields[0])) { + foreach (array_keys($this->extraFields) as $fieldName) { + if (!in_array($fieldName, $extraFields)) { + unset($this->extraFields[$fieldName]); + } + } + } else { + $this->extraFields = $extraFields; + } + } + } + + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + // skip processing if for some reason request data + // is not present (CLI or wonky SAPIs) + if (!isset($this->serverData['REQUEST_URI'])) { + return $record; + } + + $record['extra'] = $this->appendExtraFields($record['extra']); + + return $record; + } + + /** + * @param string $extraName + * @param string $serverName + * @return $this + */ + public function addExtraField($extraName, $serverName) + { + $this->extraFields[$extraName] = $serverName; + + return $this; + } + + /** + * @param array $extra + * @return array + */ + private function appendExtraFields(array $extra) + { + foreach ($this->extraFields as $extraName => $serverName) { + $extra[$extraName] = isset($this->serverData[$serverName]) ? $this->serverData[$serverName] : null; + } + + return $extra; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Registry.php b/vendor/monolog/monolog/src/Monolog/Registry.php new file mode 100644 index 0000000..159b751 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Registry.php @@ -0,0 +1,134 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use InvalidArgumentException; + +/** + * Monolog log registry + * + * Allows to get `Logger` instances in the global scope + * via static method calls on this class. + * + * + * $application = new Monolog\Logger('application'); + * $api = new Monolog\Logger('api'); + * + * Monolog\Registry::addLogger($application); + * Monolog\Registry::addLogger($api); + * + * function testLogger() + * { + * Monolog\Registry::api()->addError('Sent to $api Logger instance'); + * Monolog\Registry::application()->addError('Sent to $application Logger instance'); + * } + * + * + * @author Tomas Tatarko + */ +class Registry +{ + /** + * List of all loggers in the registry (by named indexes) + * + * @var Logger[] + */ + private static $loggers = array(); + + /** + * Adds new logging channel to the registry + * + * @param Logger $logger Instance of the logging channel + * @param string|null $name Name of the logging channel ($logger->getName() by default) + * @param bool $overwrite Overwrite instance in the registry if the given name already exists? + * @throws \InvalidArgumentException If $overwrite set to false and named Logger instance already exists + */ + public static function addLogger(Logger $logger, $name = null, $overwrite = false) + { + $name = $name ?: $logger->getName(); + + if (isset(self::$loggers[$name]) && !$overwrite) { + throw new InvalidArgumentException('Logger with the given name already exists'); + } + + self::$loggers[$name] = $logger; + } + + /** + * Checks if such logging channel exists by name or instance + * + * @param string|Logger $logger Name or logger instance + */ + public static function hasLogger($logger) + { + if ($logger instanceof Logger) { + $index = array_search($logger, self::$loggers, true); + + return false !== $index; + } else { + return isset(self::$loggers[$logger]); + } + } + + /** + * Removes instance from registry by name or instance + * + * @param string|Logger $logger Name or logger instance + */ + public static function removeLogger($logger) + { + if ($logger instanceof Logger) { + if (false !== ($idx = array_search($logger, self::$loggers, true))) { + unset(self::$loggers[$idx]); + } + } else { + unset(self::$loggers[$logger]); + } + } + + /** + * Clears the registry + */ + public static function clear() + { + self::$loggers = array(); + } + + /** + * Gets Logger instance from the registry + * + * @param string $name Name of the requested Logger instance + * @throws \InvalidArgumentException If named Logger instance is not in the registry + * @return Logger Requested instance of Logger + */ + public static function getInstance($name) + { + if (!isset(self::$loggers[$name])) { + throw new InvalidArgumentException(sprintf('Requested "%s" logger instance is not in the registry', $name)); + } + + return self::$loggers[$name]; + } + + /** + * Gets Logger instance from the registry via static method call + * + * @param string $name Name of the requested Logger instance + * @param array $arguments Arguments passed to static method call + * @throws \InvalidArgumentException If named Logger instance is not in the registry + * @return Logger Requested instance of Logger + */ + public static function __callStatic($name, $arguments) + { + return self::getInstance($name); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/ResettableInterface.php b/vendor/monolog/monolog/src/Monolog/ResettableInterface.php new file mode 100644 index 0000000..635bc77 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/ResettableInterface.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +/** + * Handler or Processor implementing this interface will be reset when Logger::reset() is called. + * + * Resetting ends a log cycle gets them back to their initial state. + * + * Resetting a Handler or a Processor means flushing/cleaning all buffers, resetting internal + * state, and getting it back to a state in which it can receive log records again. + * + * This is useful in case you want to avoid logs leaking between two requests or jobs when you + * have a long running process like a worker or an application server serving multiple requests + * in one process. + * + * @author Grégoire Pineau + */ +interface ResettableInterface +{ + public function reset(); +} diff --git a/vendor/monolog/monolog/src/Monolog/SignalHandler.php b/vendor/monolog/monolog/src/Monolog/SignalHandler.php new file mode 100644 index 0000000..d87018f --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/SignalHandler.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use Psr\Log\LoggerInterface; +use Psr\Log\LogLevel; +use ReflectionExtension; + +/** + * Monolog POSIX signal handler + * + * @author Robert Gust-Bardon + */ +class SignalHandler +{ + private $logger; + + private $previousSignalHandler = array(); + private $signalLevelMap = array(); + private $signalRestartSyscalls = array(); + + public function __construct(LoggerInterface $logger) + { + $this->logger = $logger; + } + + public function registerSignalHandler($signo, $level = LogLevel::CRITICAL, $callPrevious = true, $restartSyscalls = true, $async = true) + { + if (!extension_loaded('pcntl') || !function_exists('pcntl_signal')) { + return $this; + } + + if ($callPrevious) { + if (function_exists('pcntl_signal_get_handler')) { + $handler = pcntl_signal_get_handler($signo); + if ($handler === false) { + return $this; + } + $this->previousSignalHandler[$signo] = $handler; + } else { + $this->previousSignalHandler[$signo] = true; + } + } else { + unset($this->previousSignalHandler[$signo]); + } + $this->signalLevelMap[$signo] = $level; + $this->signalRestartSyscalls[$signo] = $restartSyscalls; + + if (function_exists('pcntl_async_signals') && $async !== null) { + pcntl_async_signals($async); + } + + pcntl_signal($signo, array($this, 'handleSignal'), $restartSyscalls); + + return $this; + } + + public function handleSignal($signo, array $siginfo = null) + { + static $signals = array(); + + if (!$signals && extension_loaded('pcntl')) { + $pcntl = new ReflectionExtension('pcntl'); + $constants = $pcntl->getConstants(); + if (!$constants) { + // HHVM 3.24.2 returns an empty array. + $constants = get_defined_constants(true); + $constants = $constants['Core']; + } + foreach ($constants as $name => $value) { + if (substr($name, 0, 3) === 'SIG' && $name[3] !== '_' && is_int($value)) { + $signals[$value] = $name; + } + } + unset($constants); + } + + $level = isset($this->signalLevelMap[$signo]) ? $this->signalLevelMap[$signo] : LogLevel::CRITICAL; + $signal = isset($signals[$signo]) ? $signals[$signo] : $signo; + $context = isset($siginfo) ? $siginfo : array(); + $this->logger->log($level, sprintf('Program received signal %s', $signal), $context); + + if (!isset($this->previousSignalHandler[$signo])) { + return; + } + + if ($this->previousSignalHandler[$signo] === true || $this->previousSignalHandler[$signo] === SIG_DFL) { + if (extension_loaded('pcntl') && function_exists('pcntl_signal') && function_exists('pcntl_sigprocmask') && function_exists('pcntl_signal_dispatch') + && extension_loaded('posix') && function_exists('posix_getpid') && function_exists('posix_kill')) { + $restartSyscalls = isset($this->signalRestartSyscalls[$signo]) ? $this->signalRestartSyscalls[$signo] : true; + pcntl_signal($signo, SIG_DFL, $restartSyscalls); + pcntl_sigprocmask(SIG_UNBLOCK, array($signo), $oldset); + posix_kill(posix_getpid(), $signo); + pcntl_signal_dispatch(); + pcntl_sigprocmask(SIG_SETMASK, $oldset); + pcntl_signal($signo, array($this, 'handleSignal'), $restartSyscalls); + } + } elseif (is_callable($this->previousSignalHandler[$signo])) { + if (PHP_VERSION_ID >= 70100) { + $this->previousSignalHandler[$signo]($signo, $siginfo); + } else { + $this->previousSignalHandler[$signo]($signo); + } + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Utils.php b/vendor/monolog/monolog/src/Monolog/Utils.php new file mode 100644 index 0000000..7f1ba12 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Utils.php @@ -0,0 +1,189 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +class Utils +{ + /** + * @internal + */ + public static function getClass($object) + { + $class = \get_class($object); + + return 'c' === $class[0] && 0 === strpos($class, "class@anonymous\0") ? get_parent_class($class).'@anonymous' : $class; + } + + /** + * Makes sure if a relative path is passed in it is turned into an absolute path + * + * @param string $streamUrl stream URL or path without protocol + * + * @return string + */ + public static function canonicalizePath($streamUrl) + { + $prefix = ''; + if ('file://' === substr($streamUrl, 0, 7)) { + $streamUrl = substr($streamUrl, 7); + $prefix = 'file://'; + } + + // other type of stream, not supported + if (false !== strpos($streamUrl, '://')) { + return $streamUrl; + } + + // already absolute + if (substr($streamUrl, 0, 1) === '/' || substr($streamUrl, 1, 1) === ':' || substr($streamUrl, 0, 2) === '\\\\') { + return $prefix.$streamUrl; + } + + $streamUrl = getcwd() . '/' . $streamUrl; + + return $prefix.$streamUrl; + } + + /** + * Return the JSON representation of a value + * + * @param mixed $data + * @param int $encodeFlags flags to pass to json encode, defaults to JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE + * @param bool $ignoreErrors whether to ignore encoding errors or to throw on error, when ignored and the encoding fails, "null" is returned which is valid json for null + * @throws \RuntimeException if encoding fails and errors are not ignored + * @return string + */ + public static function jsonEncode($data, $encodeFlags = null, $ignoreErrors = false) + { + if (null === $encodeFlags && version_compare(PHP_VERSION, '5.4.0', '>=')) { + $encodeFlags = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE; + } + + if ($ignoreErrors) { + $json = @json_encode($data, $encodeFlags); + if (false === $json) { + return 'null'; + } + + return $json; + } + + $json = json_encode($data, $encodeFlags); + if (false === $json) { + $json = self::handleJsonError(json_last_error(), $data); + } + + return $json; + } + + /** + * Handle a json_encode failure. + * + * If the failure is due to invalid string encoding, try to clean the + * input and encode again. If the second encoding attempt fails, the + * inital error is not encoding related or the input can't be cleaned then + * raise a descriptive exception. + * + * @param int $code return code of json_last_error function + * @param mixed $data data that was meant to be encoded + * @param int $encodeFlags flags to pass to json encode, defaults to JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE + * @throws \RuntimeException if failure can't be corrected + * @return string JSON encoded data after error correction + */ + public static function handleJsonError($code, $data, $encodeFlags = null) + { + if ($code !== JSON_ERROR_UTF8) { + self::throwEncodeError($code, $data); + } + + if (is_string($data)) { + self::detectAndCleanUtf8($data); + } elseif (is_array($data)) { + array_walk_recursive($data, array('Monolog\Utils', 'detectAndCleanUtf8')); + } else { + self::throwEncodeError($code, $data); + } + + if (null === $encodeFlags && version_compare(PHP_VERSION, '5.4.0', '>=')) { + $encodeFlags = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE; + } + + $json = json_encode($data, $encodeFlags); + + if ($json === false) { + self::throwEncodeError(json_last_error(), $data); + } + + return $json; + } + + /** + * Throws an exception according to a given code with a customized message + * + * @param int $code return code of json_last_error function + * @param mixed $data data that was meant to be encoded + * @throws \RuntimeException + */ + private static function throwEncodeError($code, $data) + { + switch ($code) { + case JSON_ERROR_DEPTH: + $msg = 'Maximum stack depth exceeded'; + break; + case JSON_ERROR_STATE_MISMATCH: + $msg = 'Underflow or the modes mismatch'; + break; + case JSON_ERROR_CTRL_CHAR: + $msg = 'Unexpected control character found'; + break; + case JSON_ERROR_UTF8: + $msg = 'Malformed UTF-8 characters, possibly incorrectly encoded'; + break; + default: + $msg = 'Unknown error'; + } + + throw new \RuntimeException('JSON encoding failed: '.$msg.'. Encoding: '.var_export($data, true)); + } + + /** + * Detect invalid UTF-8 string characters and convert to valid UTF-8. + * + * Valid UTF-8 input will be left unmodified, but strings containing + * invalid UTF-8 codepoints will be reencoded as UTF-8 with an assumed + * original encoding of ISO-8859-15. This conversion may result in + * incorrect output if the actual encoding was not ISO-8859-15, but it + * will be clean UTF-8 output and will not rely on expensive and fragile + * detection algorithms. + * + * Function converts the input in place in the passed variable so that it + * can be used as a callback for array_walk_recursive. + * + * @param mixed $data Input to check and convert if needed, passed by ref + * @private + */ + public static function detectAndCleanUtf8(&$data) + { + if (is_string($data) && !preg_match('//u', $data)) { + $data = preg_replace_callback( + '/[\x80-\xFF]+/', + function ($m) { return utf8_encode($m[0]); }, + $data + ); + $data = str_replace( + array('¤', '¦', '¨', '´', '¸', '¼', '½', '¾'), + array('€', 'Š', 'š', 'Ž', 'ž', 'Œ', 'œ', 'Ÿ'), + $data + ); + } + } +} diff --git a/vendor/nelexa/zip/.phpstorm.meta.php b/vendor/nelexa/zip/.phpstorm.meta.php new file mode 100644 index 0000000..239b859 --- /dev/null +++ b/vendor/nelexa/zip/.phpstorm.meta.php @@ -0,0 +1,128 @@ + Функционал +- Открытие и разархивирование ZIP-архивов. +- Создание ZIP-архивов. +- Модификация ZIP-архивов. +- Чистый php (не требуется расширение `php-zip` и класс `\ZipArchive`). +- Поддерживается сохранение архива в файл, вывод архива в браузер или вывод в виде строки, без сохранения в файл. +- Поддерживаются комментарии архива и комментарии отдельных записей. +- Получение подробной информации о каждой записи в архиве. +- Поддерживаются только следующие методы сжатия: + + Без сжатия (Stored). + + Deflate сжатие. + + BZIP2 сжатие при наличии расширения `php-bz2`. +- Поддержка `ZIP64` (размер файла более 4 GB или количество записей в архиве более 65535). +- Встроенная поддержка выравнивания архива для оптимизации Android пакетов (APK) [`zipalign`](https://developer.android.com/studio/command-line/zipalign.html). +- Работа с паролями для PHP 5.5 + > **Внимание!** + > + > Для 32-bit систем, в данный момент не поддерживается метод шифрование `Traditional PKWARE Encryption (ZipCrypto)`. + > Используйте метод шифрования `WinZIP AES Encryption`, когда это возможно. + + Установка пароля для чтения архива глобально или для некоторых записей. + + Изменение пароля архива, в том числе и для отдельных записей. + + Удаление пароля архива глобально или для отдельных записей. + + Установка пароля и/или метода шифрования, как для всех, так и для отдельных записей в архиве. + + Установка разных паролей и методов шифрования для разных записей. + + Удаление пароля для всех или для некоторых записей. + + Поддержка методов шифрования `Traditional PKWARE Encryption (ZipCrypto)` и `WinZIP AES Encryption (128, 192 или 256 bit)`. + + Установка метода шифрования для всех или для отдельных записей в архиве. + +### Требования +- `PHP` >= 5.5 (предпочтительно 64-bit). +- Опционально php-расширение `bzip2` для поддержки BZIP2 компрессии. +- Опционально php-расширение `openssl` или `mcrypt` для `WinZip Aes Encryption` шифрования. + +### Установка +`composer require nelexa/zip` + +Последняя стабильная версия: [![Latest Stable Version](https://poser.pugx.org/nelexa/zip/v/stable)](https://packagist.org/packages/nelexa/zip) + +### Примеры +```php +// создание нового архива +$zipFile = new \PhpZip\ZipFile(); +try{ + $zipFile + ->addFromString('zip/entry/filename', "Is file content") // добавить запись из строки + ->addFile('/path/to/file', 'data/tofile') // добавить запись из файла + ->addDir(__DIR__, 'to/path/') // добавить файлы из директории + ->saveAsFile($outputFilename) // сохранить архив в файл + ->close(); // закрыть архив + + // открытие архива, извлечение файлов, удаление файлов, добавление файлов, установка пароля и вывод архива в браузер. + $zipFile + ->openFile($outputFilename) // открыть архив из файла + ->extractTo($outputDirExtract) // извлечь файлы в заданную директорию + ->deleteFromRegex('~^\.~') // удалить все скрытые (Unix) файлы + ->addFromString('dir/file.txt', 'Test file') // добавить новую запись из строки + ->setPassword('password') // установить пароль на все записи + ->outputAsAttachment('library.jar'); // вывести в браузер без сохранения в файл +} +catch(\PhpZip\Exception\ZipException $e){ + // обработка исключения +} +finally{ + $zipFile->close(); +} +``` +Другие примеры можно посмотреть в папке `tests/`. + +### Глоссарий +**Запись в ZIP-архиве (Zip Entry)** - файл или папка в ZIP-архиве. У каждой записи в архиве есть определённые свойства, например: имя файла, метод сжатия, метод шифрования, размер файла до сжатия, размер файла после сжатия, CRC32 и другие. + +### Документация +#### Обзор методов класса `\PhpZip\ZipFile` +- [ZipFile::__construct](#Documentation-ZipFile-__construct) - инициализацирует ZIP-архив. +- [ZipFile::addAll](#Documentation-ZipFile-addAll) - добавляет все записи из массива. +- [ZipFile::addDir](#Documentation-ZipFile-addDir) - добавляет файлы из директории по указанному пути без вложенных директорий. +- [ZipFile::addDirRecursive](#Documentation-ZipFile-addDirRecursive) - добавляет файлы из директории по указанному пути c вложенными директориями. +- [ZipFile::addEmptyDir](#Documentation-ZipFile-addEmptyDir) - добавляет в ZIP-архив новую директорию. +- [ZipFile::addFile](#Documentation-ZipFile-addFile) - добавляет в ZIP-архив файл по указанному пути. +- [ZipFile::addSplFile](#Documentation-ZipFile-addSplFile) - добавляет объект `\SplFileInfo` в zip-архив. +- [ZipFile::addFromFinder](#Documentation-ZipFile-addFromFinder) - добавляет файлы из `Symfony\Component\Finder\Finder` в zip архив. +- [ZipFile::addFilesFromIterator](#Documentation-ZipFile-addFilesFromIterator) - добавляет файлы из итератора директорий. +- [ZipFile::addFilesFromGlob](#Documentation-ZipFile-addFilesFromGlob) - добавляет файлы из директории в соответствии с glob шаблоном без вложенных директорий. +- [ZipFile::addFilesFromGlobRecursive](#Documentation-ZipFile-addFilesFromGlobRecursive) - добавляет файлы из директории в соответствии с glob шаблоном c вложенными директориями. +- [ZipFile::addFilesFromRegex](#Documentation-ZipFile-addFilesFromRegex) - добавляет файлы из директории в соответствии с регулярным выражением без вложенных директорий. +- [ZipFile::addFilesFromRegexRecursive](#Documentation-ZipFile-addFilesFromRegexRecursive) - добавляет файлы из директории в соответствии с регулярным выражением c вложенными директориями. +- [ZipFile::addFromStream](#Documentation-ZipFile-addFromStream) - добавляет в ZIP-архив запись из потока. +- [ZipFile::addFromString](#Documentation-ZipFile-addFromString) - добавляет файл в ZIP-архив, используя его содержимое в виде строки. +- [ZipFile::close](#Documentation-ZipFile-close) - закрывает ZIP-архив. +- [ZipFile::count](#Documentation-ZipFile-count) - возвращает количество записей в архиве. +- [ZipFile::deleteFromName](#Documentation-ZipFile-deleteFromName) - удаляет запись по имени. +- [ZipFile::deleteFromGlob](#Documentation-ZipFile-deleteFromGlob) - удаляет записи в соответствии с glob шаблоном. +- [ZipFile::deleteFromRegex](#Documentation-ZipFile-deleteFromRegex) - удаляет записи в соответствии с регулярным выражением. +- [ZipFile::deleteAll](#Documentation-ZipFile-deleteAll) - удаляет все записи в ZIP-архиве. +- [ZipFile::disableEncryption](#Documentation-ZipFile-disableEncryption) - отключает шифрования всех записей, находящихся в архиве. +- [ZipFile::disableEncryptionEntry](#Documentation-ZipFile-disableEncryptionEntry) - отключает шифрование записи по её имени. +- [ZipFile::extractTo](#Documentation-ZipFile-extractTo) - извлекает содержимое архива в заданную директорию. +- [ZipFile::getAllInfo](#Documentation-ZipFile-getAllInfo) - возвращает подробную информацию обо всех записях в архиве. +- [ZipFile::getArchiveComment](#Documentation-ZipFile-getArchiveComment) - возвращает комментарий ZIP-архива. +- [ZipFile::getEntryComment](#Documentation-ZipFile-getEntryComment) - возвращает комментарий к записи, используя её имя. +- [ZipFile::getEntryContent](#Documentation-ZipFile-getEntryContent) - возвращает содержимое записи. +- [ZipFile::getEntryInfo](#Documentation-ZipFile-getEntryInfo) - возвращает подробную информацию о записи в архиве. +- [ZipFile::getListFiles](#Documentation-ZipFile-getListFiles) - возвращает список файлов архива. +- [ZipFile::hasEntry](#Documentation-ZipFile-hasEntry) - проверяет, присутствует ли запись в архиве. +- [ZipFile::isDirectory](#Documentation-ZipFile-isDirectory) - проверяет, является ли запись в архиве директорией. +- [ZipFile::matcher](#Documentation-ZipFile-matcher) - выборка записей в архиве для проведения операций над выбранными записями. +- [ZipFile::openFile](#Documentation-ZipFile-openFile) - открывает ZIP-архив из файла. +- [ZipFile::openFromString](#Documentation-ZipFile-openFromString) - открывает ZIP-архив из строки. +- [ZipFile::openFromStream](#Documentation-ZipFile-openFromStream) - открывает ZIP-архив из потока. +- [ZipFile::outputAsAttachment](#Documentation-ZipFile-outputAsAttachment) - выводит ZIP-архив в браузер. +- [ZipFile::outputAsResponse](#Documentation-ZipFile-outputAsResponse) - выводит ZIP-архив, как Response PSR-7. +- [ZipFile::outputAsString](#Documentation-ZipFile-outputAsString) - выводит ZIP-архив в виде строки. +- [ZipFile::rename](#Documentation-ZipFile-rename) - переименовывает запись по имени. +- [ZipFile::rewrite](#Documentation-ZipFile-rewrite) - сохраняет изменения и заново открывает изменившийся архив. +- [ZipFile::saveAsFile](#Documentation-ZipFile-saveAsFile) - сохраняет архив в файл. +- [ZipFile::saveAsStream](#Documentation-ZipFile-saveAsStream) - записывает архив в поток. +- [ZipFile::setArchiveComment](#Documentation-ZipFile-setArchiveComment) - устанавливает комментарий к ZIP-архиву. +- [ZipFile::setCompressionLevel](#Documentation-ZipFile-setCompressionLevel) - устанавливает уровень сжатия для всех файлов, находящихся в архиве. +- [ZipFile::setCompressionLevelEntry](#Documentation-ZipFile-setCompressionLevelEntry) - устанавливает уровень сжатия для определённой записи в архиве. +- [ZipFile::setCompressionMethodEntry](#Documentation-ZipFile-setCompressionMethodEntry) - устанавливает метод сжатия для определённой записи в архиве. +- [ZipFile::setEntryComment](#Documentation-ZipFile-setEntryComment) - устанавливает комментарий к записи, используя её имя. +- [ZipFile::setReadPassword](#Documentation-ZipFile-setReadPassword) - устанавливает пароль на чтение открытого запароленного архива для всех зашифрованных записей. +- [ZipFile::setReadPasswordEntry](#Documentation-ZipFile-setReadPasswordEntry) - устанавливает пароль на чтение конкретной зашифрованной записи открытого запароленного архива. +- ~~ZipFile::withNewPassword~~ - устаревший метод (**deprecated**) используйте метод [ZipFile::setPassword](#Documentation-ZipFile-setPassword). +- [ZipFile::setPassword](#Documentation-ZipFile-setPassword) - устанавливает новый пароль для всех файлов, находящихся в архиве. +- [ZipFile::setPasswordEntry](#Documentation-ZipFile-setPasswordEntry) - устанавливает новый пароль для конкретного файла. +- [ZipFile::setZipAlign](#Documentation-ZipFile-setZipAlign) - устанавливает выравнивание архива для оптимизации APK файлов (Android packages). +- [ZipFile::unchangeAll](#Documentation-ZipFile-unchangeAll) - отменяет все изменения, сделанные в архиве. +- [ZipFile::unchangeArchiveComment](#Documentation-ZipFile-unchangeArchiveComment) - отменяет изменения в комментарии к архиву. +- [ZipFile::unchangeEntry](#Documentation-ZipFile-unchangeEntry) - отменяет изменения для конкретной записи архива. +- ~~ZipFile::withoutPassword~~ - устаревший метод (**deprecated**) используйте метод [ZipFile::disableEncryption](#Documentation-ZipFile-disableEncryption). +- ~~ZipFile::withReadPassword~~ - устаревший метод (**deprecated**) используйте метод [ZipFile::setReadPassword](#Documentation-ZipFile-setReadPassword). + +#### Создание/Открытие ZIP-архива +**ZipFile::__construct** - Инициализацирует ZIP-архив. +```php +$zipFile = new \PhpZip\ZipFile(); +``` + **ZipFile::openFile** - открывает ZIP-архив из файла. +```php +$zipFile = new \PhpZip\ZipFile(); +$zipFile->openFile('file.zip'); +``` + **ZipFile::openFromString** - открывает ZIP-архив из строки. +```php +$zipFile = new \PhpZip\ZipFile(); +$zipFile->openFromString($stringContents); +``` + **ZipFile::openFromStream** - открывает ZIP-архив из потока. +```php +$stream = fopen('file.zip', 'rb'); + +$zipFile = new \PhpZip\ZipFile(); +$zipFile->openFromStream($stream); +``` +#### Чтение записей из архива + **ZipFile::count** - возвращает количество записей в архиве. +```php +$zipFile = new \PhpZip\ZipFile(); + +$count = count($zipFile); +// или +$count = $zipFile->count(); +``` + **ZipFile::getListFiles** - возвращает список файлов архива. +```php +$zipFile = new \PhpZip\ZipFile(); +$listFiles = $zipFile->getListFiles(); + +// Пример содержимого массива: +// array ( +// 0 => 'info.txt', +// 1 => 'path/to/file.jpg', +// 2 => 'another path/', +// ) +``` + **ZipFile::getEntryContent** - возвращает содержимое записи. +```php +// $entryName = 'path/to/example-entry-name.txt'; +$zipFile = new \PhpZip\ZipFile(); + +$contents = $zipFile[$entryName]; +// или +$contents = $zipFile->getEntryContents($entryName); +``` + **ZipFile::hasEntry** - проверяет, присутствует ли запись в архиве. +```php +// $entryName = 'path/to/example-entry-name.txt'; +$zipFile = new \PhpZip\ZipFile(); + +$hasEntry = isset($zipFile[$entryName]); +// или +$hasEntry = $zipFile->hasEntry($entryName); +``` + **ZipFile::isDirectory** - проверяет, является ли запись в архиве директорией. +```php +// $entryName = 'path/to/'; +$zipFile = new \PhpZip\ZipFile(); + +$isDirectory = $zipFile->isDirectory($entryName); +``` + **ZipFile::extractTo** - извлекает содержимое архива в заданную директорию. +Директория должна существовать. +```php +$zipFile = new \PhpZip\ZipFile(); +$zipFile->extractTo($directory); +``` +Можно извлечь только некоторые записи в заданную директорию. +Директория должна существовать. +```php +$extractOnlyFiles = [ + 'filename1', + 'filename2', + 'dir/dir/dir/' +]; +$zipFile = new \PhpZip\ZipFile(); +$zipFile->extractTo($toDirectory, $extractOnlyFiles); +``` +#### Перебор записей/Итератор +`ZipFile` является итератором. +Можно перебрать все записи, через цикл `foreach`. +```php +foreach($zipFile as $entryName => $contents){ + echo "Файл: $entryName" . PHP_EOL; + echo "Содержимое: $contents" . PHP_EOL; + echo '-----------------------------' . PHP_EOL; +} +``` +Можно использовать паттерн `Iterator`. +```php +$iterator = new \ArrayIterator($zipFile); +while ($iterator->valid()) +{ + $entryName = $iterator->key(); + $contents = $iterator->current(); + + echo "Файл: $entryName" . PHP_EOL; + echo "Содержимое: $contents" . PHP_EOL; + echo '-----------------------------' . PHP_EOL; + + $iterator->next(); +} +``` +#### Получение информации о записях + **ZipFile::getArchiveComment** - возвращает комментарий ZIP-архива. +```php +$commentArchive = $zipFile->getArchiveComment(); +``` + **ZipFile::getEntryComment** - возвращает комментарий к записи, используя её имя. +```php +$commentEntry = $zipFile->getEntryComment($entryName); +``` + **ZipFile::getEntryInfo** - возвращает подробную информацию о записи в архиве. +```php +$zipFile = new \PhpZip\ZipFile(); +$zipInfo = $zipFile->getEntryInfo('file.txt'); +``` + **ZipFile::getAllInfo** - возвращает подробную информацию обо всех записях в архиве. +```php +$zipAllInfo = $zipFile->getAllInfo(); +``` +#### Добавление записей в архив + +Все методы добавления записей в ZIP-архив позволяют указать метод сжатия содержимого. + +Доступны следующие методы сжатия: +- `\PhpZip\Constants\ZipCompressionMethod::STORED` - без сжатия +- `\PhpZip\Constants\ZipCompressionMethod::DEFLATED` - Deflate сжатие +- `\PhpZip\Constants\ZipCompressionMethod::BZIP2` - Bzip2 сжатие при наличии расширения `ext-bz2` + + **ZipFile::addFile** - добавляет в ZIP-архив файл по указанному пути из файловой системы. +```php +$zipFile = new \PhpZip\ZipFile(); +// $file = '...../file.ext'; +$zipFile->addFile($file); + +// можно указать имя записи в архиве (если null, то используется последний компонент из имени файла) +$zipFile->addFile($file, $entryName); + +// можно указать метод сжатия +$zipFile->addFile($file, $entryName, \PhpZip\Constants\ZipCompressionMethod::STORED); // Без сжатия +$zipFile->addFile($file, $entryName, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate сжатие +$zipFile->addFile($file, $entryName, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 сжатие +``` + +**ZipFile::addSplFile"** - добавляет объект `\SplFileInfo` в zip-архив. +```php +// $file = '...../file.ext'; +// $entryName = 'file2.ext' +$zipFile = new \PhpZip\ZipFile(); + +$splFile = new \SplFileInfo('README.md'); + +$zipFile->addSplFile($splFile); +$zipFile->addSplFile($splFile, $entryName); +// or +$zipFile[$entryName] = new \SplFileInfo($file); + +// установить метод сжатия +$zipFile->addSplFile($splFile, $entryName, $options = [ + \PhpZip\Constants\ZipOptions::COMPRESSION_METHOD => \PhpZip\Constants\ZipCompressionMethod::DEFLATED, +]); +``` + +**ZipFile::addFromFinder"** - добавляет файлы из `Symfony\Component\Finder\Finder` в zip архив. +https://symfony.com/doc/current/components/finder.html +```php +$finder = new \Symfony\Component\Finder\Finder(); +$finder + ->files() + ->name('*.{jpg,jpeg,gif,png}') + ->name('/^[0-9a-f]\./') + ->contains('/lorem\s+ipsum$/i') + ->in('path'); + +$zipFile = new \PhpZip\ZipFile(); +$zipFile->addFromFinder($finder, $options = [ + \PhpZip\Constants\ZipOptions::COMPRESSION_METHOD => \PhpZip\Constants\ZipCompressionMethod::DEFLATED, + \PhpZip\Constants\ZipOptions::MODIFIED_TIME => new \DateTimeImmutable('-1 day 5 min') +]); +``` + **ZipFile::addFromString** - добавляет файл в ZIP-архив, используя его содержимое в виде строки. +```php +$zipFile = new \PhpZip\ZipFile(); + +$zipFile[$entryName] = $contents; +// или +$zipFile->addFromString($entryName, $contents); + +// можно указать метод сжатия +$zipFile->addFromString($entryName, $contents, \PhpZip\Constants\ZipCompressionMethod::STORED); // Без сжатия +$zipFile->addFromString($entryName, $contents, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate сжатие +$zipFile->addFromString($entryName, $contents, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 сжатие +``` + **ZipFile::addFromStream** - добавляет в ZIP-архив запись из потока. +```php +// $stream = fopen(..., 'rb'); + +$zipFile->addFromStream($stream, $entryName); + +// можно указать метод сжатия +$zipFile->addFromStream($stream, $entryName, \PhpZip\Constants\ZipCompressionMethod::STORED); // Без сжатия +$zipFile->addFromStream($stream, $entryName, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate сжатие +$zipFile->addFromStream($stream, $entryName, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 сжатие +``` + **ZipFile::addEmptyDir** - добавляет в ZIP-архив новую (пустую) директорию. +```php +// $path = "path/to/"; + +$zipFile->addEmptyDir($path); +// или +$zipFile[$path] = null; +``` + **ZipFile::addAll** - добавляет все записи из массива. +```php +$entries = [ + 'file.txt' => 'file contents', // запись из строки данных + 'empty dir/' => null, // пустой каталог + 'path/to/file.jpg' => fopen('..../filename', 'r'), // запись из потока + 'path/to/file.dat' => new \SplFileInfo('..../filename'), // запись из файла +]; + +$zipFile->addAll($entries); +``` + **ZipFile::addDir** - добавляет файлы из директории по указанному пути без вложенных директорий. +```php +$zipFile->addDir($dirName); + +// можно указать путь в архиве в который необходимо поместить записи +$localPath = "to/path/"; +$zipFile->addDir($dirName, $localPath); + +// можно указать метод сжатия +$zipFile->addDir($dirName, $localPath, \PhpZip\Constants\ZipCompressionMethod::STORED); // Без сжатия +$zipFile->addDir($dirName, $localPath, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate сжатие +$zipFile->addDir($dirName, $localPath, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 сжатие +``` + **ZipFile::addDirRecursive** - добавляет файлы из директории по указанному пути c вложенными директориями. +```php +$zipFile->addDirRecursive($dirName); + +// можно указать путь в архиве в который необходимо поместить записи +$localPath = "to/path/"; +$zipFile->addDirRecursive($dirName, $localPath); + +// можно указать метод сжатия +$zipFile->addDirRecursive($dirName, $localPath, \PhpZip\Constants\ZipCompressionMethod::STORED); // Без сжатия +$zipFile->addDirRecursive($dirName, $localPath, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate сжатие +$zipFile->addDirRecursive($dirName, $localPath, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 сжатие +``` + **ZipFile::addFilesFromIterator** - добавляет файлы из итератора директорий. +```php +// $directoryIterator = new \DirectoryIterator($dir); // без вложенных директорий +// $directoryIterator = new \RecursiveDirectoryIterator($dir); // с вложенными директориями + +$zipFile->addFilesFromIterator($directoryIterator); + +// можно указать путь в архиве в который необходимо поместить записи +$localPath = "to/path/"; +$zipFile->addFilesFromIterator($directoryIterator, $localPath); +// или +$zipFile[$localPath] = $directoryIterator; + +// можно указать метод сжатия +$zipFile->addFilesFromIterator($directoryIterator, $localPath, \PhpZip\Constants\ZipCompressionMethod::STORED); // Без сжатия +$zipFile->addFilesFromIterator($directoryIterator, $localPath, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate сжатие +$zipFile->addFilesFromIterator($directoryIterator, $localPath, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 сжатие +``` +Пример добавления файлов из директории в архив с игнорированием некоторых файлов при помощи итератора директорий. +```php +$ignoreFiles = [ + "file_ignore.txt", + "dir_ignore/sub dir ignore/" +]; + +// $directoryIterator = new \DirectoryIterator($dir); // без вложенных директорий +// $directoryIterator = new \RecursiveDirectoryIterator($dir); // с вложенными директориями + +// используйте \PhpZip\Util\Iterator\IgnoreFilesFilterIterator для не рекурсивного поиска +$ignoreIterator = new \PhpZip\Util\Iterator\IgnoreFilesRecursiveFilterIterator( + $directoryIterator, + $ignoreFiles +); + +$zipFile->addFilesFromIterator($ignoreIterator); +``` + **ZipFile::addFilesFromGlob** - добавляет файлы из директории в соответствии с [glob шаблоном](https://en.wikipedia.org/wiki/Glob_(programming)) без вложенных директорий. +```php +$globPattern = '**.{jpg,jpeg,png,gif}'; // пример glob шаблона -> добавить все .jpg, .jpeg, .png и .gif файлы + +$zipFile->addFilesFromGlob($dir, $globPattern); + +// можно указать путь в архиве в который необходимо поместить записи +$localPath = "to/path/"; +$zipFile->addFilesFromGlob($dir, $globPattern, $localPath); + +// можно указать метод сжатия +$zipFile->addFilesFromGlob($dir, $globPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::STORED); // Без сжатия +$zipFile->addFilesFromGlob($dir, $globPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate сжатие +$zipFile->addFilesFromGlob($dir, $globPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 сжатие +``` + **ZipFile::addFilesFromGlobRecursive** - добавляет файлы из директории в соответствии с [glob шаблоном](https://en.wikipedia.org/wiki/Glob_(programming)) c вложенными директориями. +```php +$globPattern = '**.{jpg,jpeg,png,gif}'; // пример glob шаблона -> добавить все .jpg, .jpeg, .png и .gif файлы + +$zipFile->addFilesFromGlobRecursive($dir, $globPattern); + +// можно указать путь в архиве в который необходимо поместить записи +$localPath = "to/path/"; +$zipFile->addFilesFromGlobRecursive($dir, $globPattern, $localPath); + +// можно указать метод сжатия +$zipFile->addFilesFromGlobRecursive($dir, $globPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::STORED); // Без сжатия +$zipFile->addFilesFromGlobRecursive($dir, $globPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate сжатие +$zipFile->addFilesFromGlobRecursive($dir, $globPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 сжатие +``` + **ZipFile::addFilesFromRegex** - добавляет файлы из директории в соответствии с [регулярным выражением](https://en.wikipedia.org/wiki/Regular_expression) без вложенных директорий. +```php +$regexPattern = '/\.(jpe?g|png|gif)$/si'; // пример регулярного выражения -> добавить все .jpg, .jpeg, .png и .gif файлы + +$zipFile->addFilesFromRegex($dir, $regexPattern); + +// можно указать путь в архиве в который необходимо поместить записи +$localPath = "to/path/"; +$zipFile->addFilesFromRegex($dir, $regexPattern, $localPath); + +// можно указать метод сжатия +$zipFile->addFilesFromRegex($dir, $regexPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::STORED); // Без сжатия +$zipFile->addFilesFromRegex($dir, $regexPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate сжатие +$zipFile->addFilesFromRegex($dir, $regexPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 сжатие +``` + **ZipFile::addFilesFromRegexRecursive** - добавляет файлы из директории в соответствии с [регулярным выражением](https://en.wikipedia.org/wiki/Regular_expression) с вложенными директориями. +```php +$regexPattern = '/\.(jpe?g|png|gif)$/si'; // пример регулярного выражения -> добавить все .jpg, .jpeg, .png и .gif файлы + +$zipFile->addFilesFromRegexRecursive($dir, $regexPattern); + +// можно указать путь в архиве в который необходимо поместить записи +$localPath = "to/path/"; +$zipFile->addFilesFromRegexRecursive($dir, $regexPattern, $localPath); + +// можно указать метод сжатия +$zipFile->addFilesFromRegexRecursive($dir, $regexPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::STORED); // Без сжатия +$zipFile->addFilesFromRegexRecursive($dir, $regexPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate сжатие +$zipFile->addFilesFromRegexRecursive($dir, $regexPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 сжатие +``` +#### Удаление записей из архива + **ZipFile::deleteFromName** - удаляет запись по имени. +```php +$zipFile->deleteFromName($entryName); +``` + **ZipFile::deleteFromGlob** - удаляет записи в соответствии с [glob шаблоном](https://en.wikipedia.org/wiki/Glob_(programming)). +```php +$globPattern = '**.{jpg,jpeg,png,gif}'; // пример glob шаблона -> удалить все .jpg, .jpeg, .png и .gif файлы + +$zipFile->deleteFromGlob($globPattern); +``` + **ZipFile::deleteFromRegex** - удаляет записи в соответствии с [регулярным выражением](https://en.wikipedia.org/wiki/Regular_expression). +```php +$regexPattern = '/\.(jpe?g|png|gif)$/si'; // пример регулярному выражения -> удалить все .jpg, .jpeg, .png и .gif файлы + +$zipFile->deleteFromRegex($regexPattern); +``` + **ZipFile::deleteAll** - удаляет все записи в ZIP-архиве. +```php +$zipFile->deleteAll(); +``` +#### Работа с записями и с архивом + **ZipFile::rename** - переименовывает запись по имени. +```php +$zipFile->rename($oldName, $newName); +``` + **ZipFile::setCompressionLevel** - устанавливает уровень сжатия для всех файлов, находящихся в архиве. + +> _Обратите внимание, что действие данного метода не распространяется на записи, добавленные после выполнения этого метода._ + +По умолчанию используется уровень сжатия 5 (`\PhpZip\Constants\ZipCompressionLevel::NORMAL`) или уровень сжатия, определённый в архиве для Deflate сжатия. + +Поддерживаются диапазон значений от 1 (`\PhpZip\Constants\ZipCompressionLevel::SUPER_FAST`) до 9 (`\PhpZip\Constants\ZipCompressionLevel::MAXIMUM`). Чем выше число, тем лучше и дольше сжатие. +```php +$zipFile->setCompressionLevel(\PhpZip\Constants\ZipCompressionLevel::MAXIMUM); +``` + **ZipFile::setCompressionLevelEntry** - устанавливает уровень сжатия для определённой записи в архиве. + +Поддерживаются диапазон значений от 1 (`\PhpZip\Constants\ZipCompressionLevel::SUPER_FAST`) до 9 (`\PhpZip\Constants\ZipCompressionLevel::MAXIMUM`). Чем выше число, тем лучше и дольше сжатие. +```php +$zipFile->setCompressionLevelEntry($entryName, \PhpZip\Constants\ZipCompressionLevel::MAXIMUM); +``` + **ZipFile::setCompressionMethodEntry** - устанавливает метод сжатия для определённой записи в архиве. + +Доступны следующие методы сжатия: +- `\PhpZip\Constants\ZipCompressionMethod::STORED` - без сжатия +- `\PhpZip\Constants\ZipCompressionMethod::DEFLATED` - Deflate сжатие +- `\PhpZip\Constants\ZipCompressionMethod::BZIP2` - Bzip2 сжатие при наличии расширения `ext-bz2` +```php +$zipFile->setCompressionMethodEntry($entryName, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); +``` + **ZipFile::setArchiveComment** - устанавливает комментарий к ZIP-архиву. +```php +$zipFile->setArchiveComment($commentArchive); +``` + **ZipFile::setEntryComment** - устанавливает комментарий к записи, используя её имя. +```php +$zipFile->setEntryComment($entryName, $comment); +``` + **ZipFile::matcher** - выборка записей в архиве для проведения операций над выбранными записями. +```php +$matcher = $zipFile->matcher(); +``` +Выбор файлов из архива по одному: +```php +$matcher + ->add('entry name') + ->add('another entry'); +``` +Выбор нескольких файлов в архиве: +```php +$matcher->add([ + 'entry name', + 'another entry name', + 'path/' +]); +``` +Выбор файлов по регулярному выражению: +```php +$matcher->match('~\.jpe?g$~i'); +``` +Выбор всех файлов в архиве: +```php +$matcher->all(); +``` +count() - получает количество выбранных записей: +```php +$count = count($matcher); +// или +$count = $matcher->count(); +``` +getMatches() - получает список выбранных записей: +```php +$entries = $matcher->getMatches(); +// пример содержимого: ['entry name', 'another entry name']; +``` +invoke() - выполняет пользовательскую функцию над выбранными записями: +```php +// пример +$matcher->invoke(function($entryName) use($zipFile) { + $newName = preg_replace('~\.(jpe?g)$~i', '.no_optimize.$1', $entryName); + $zipFile->rename($entryName, $newName); +}); +``` +Функции для работы над выбранными записями: +```php +$matcher->delete(); // удалет выбранные записи из ZIP-архива +$matcher->setPassword($password); // устанавливает новый пароль на выбранные записи +$matcher->setPassword($password, $encryptionMethod); // устанавливает новый пароль и метод шифрования на выбранные записи +$matcher->setEncryptionMethod($encryptionMethod); // устанавливает метод шифрования на выбранные записи +$matcher->disableEncryption(); // отключает шифрование для выбранных записей +``` +#### Работа с паролями + +Реализована поддержка методов шифрования: +- `\PhpZip\Constants\ZipEncryptionMethod::PKWARE` - Traditional PKWARE encryption +- `\PhpZip\Constants\ZipEncryptionMethod::WINZIP_AES_256` - WinZip AES encryption 256 bit (рекомендуемое) +- `\PhpZip\Constants\ZipEncryptionMethod::WINZIP_AES_192` - WinZip AES encryption 192 bit +- `\PhpZip\Constants\ZipEncryptionMethod::WINZIP_AES_128` - WinZip AES encryption 128 bit + + **ZipFile::setReadPassword** - устанавливает пароль на чтение открытого запароленного архива для всех зашифрованных записей. + +> _Установка пароля не является обязательной для добавления новых записей или удаления существующих, но если вы захотите извлечь контент или изменить метод/уровень сжатия, метод шифрования или изменить пароль, то в этом случае пароль необходимо указать._ +```php +$zipFile->setReadPassword($password); +``` + **ZipFile::setReadPasswordEntry** - устанавливает пароль на чтение конкретной зашифрованной записи открытого запароленного архива. +```php +$zipFile->setReadPasswordEntry($entryName, $password); +``` + **ZipFile::setPassword** - устанавливает новый пароль для всех файлов, находящихся в архиве. + +> _Обратите внимание, что действие данного метода не распространяется на записи, добавленные после выполнения этого метода._ +```php +$zipFile->setPassword($password); +``` +Можно установить метод шифрования: +```php +$encryptionMethod = \PhpZip\Constants\ZipEncryptionMethod::WINZIP_AES_256; +$zipFile->setPassword($password, $encryptionMethod); +``` + **ZipFile::setPasswordEntry** - устанавливает новый пароль для конкретного файла. +```php +$zipFile->setPasswordEntry($entryName, $password); +``` +Можно установить метод шифрования: +```php +$encryptionMethod = \PhpZip\Constants\ZipEncryptionMethod::WINZIP_AES_256; +$zipFile->setPasswordEntry($entryName, $password, $encryptionMethod); +``` + **ZipFile::disableEncryption** - отключает шифрования всех записей, находящихся в архиве. + +> _Обратите внимание, что действие данного метода не распространяется на записи, добавленные после выполнения этого метода._ +```php +$zipFile->disableEncryption(); +``` + **ZipFile::disableEncryptionEntry** - отключает шифрование записи по её имени. +```php +$zipFile->disableEncryptionEntry($entryName); +``` +#### zipalign + **ZipFile::setZipAlign** - устанавливает выравнивание архива для оптимизации APK файлов (Android packages). + +Метод добавляет паддинги незашифрованным и не сжатым записям, для оптимизации расхода памяти в системе Android. Рекомендуется использовать для `APK` файлов. Файл может незначительно увеличиться. + +Этот метод является альтернативой вызова команды `zipalign -f -v 4 filename.zip`. + +Подробнее можно ознакомиться по [ссылке](https://developer.android.com/studio/command-line/zipalign.html). +```php +// вызовите до сохранения или вывода архива +$zipFile->setZipAlign(4); +``` +#### Отмена изменений + **ZipFile::unchangeAll** - отменяет все изменения, сделанные в архиве. +```php +$zipFile->unchangeAll(); +``` + **ZipFile::unchangeArchiveComment** - отменяет изменения в комментарии к архиву. +```php +$zipFile->unchangeArchiveComment(); +``` + **ZipFile::unchangeEntry** - отменяет изменения для конкретной записи архива. +```php +$zipFile->unchangeEntry($entryName); +``` +#### Сохранение файла или вывод в браузер + **ZipFile::saveAsFile** - сохраняет архив в файл. +```php +$zipFile->saveAsFile($filename); +``` + **ZipFile::saveAsStream** - записывает архив в поток. +```php +// $fp = fopen($filename, 'w+b'); + +$zipFile->saveAsStream($fp); +``` + **ZipFile::outputAsString** - выводит ZIP-архив в виде строки. +```php +$rawZipArchiveBytes = $zipFile->outputAsString(); +``` + **ZipFile::outputAsAttachment** - выводит ZIP-архив в браузер. + +При выводе устанавливаются необходимые заголовки, а после вывода завершается работа скрипта. +```php +$zipFile->outputAsAttachment($outputFilename); +``` +Можно установить MIME-тип: +```php +$mimeType = 'application/zip' +$zipFile->outputAsAttachment($outputFilename, $mimeType); +``` + **ZipFile::outputAsResponse** - выводит ZIP-архив, как Response [PSR-7](http://www.php-fig.org/psr/psr-7/). + +Метод вывода может использоваться в любом PSR-7 совместимом фреймворке. +```php +// $response = ....; // instance Psr\Http\Message\ResponseInterface +$zipFile->outputAsResponse($response, $outputFilename); +``` +Можно установить MIME-тип: +```php +$mimeType = 'application/zip' +$zipFile->outputAsResponse($response, $outputFilename, $mimeType); +``` +Пример для Slim Framework: +```php +$app = new \Slim\App; +$app->get('/download', function ($req, $res, $args) { + $zipFile = new \PhpZip\ZipFile(); + $zipFile['file.txt'] = 'content'; + return $zipFile->outputAsResponse($res, 'file.zip'); +}); +$app->run(); +``` + **ZipFile::rewrite** - сохраняет изменения и заново открывает изменившийся архив. +```php +$zipFile->rewrite(); +``` +#### Закрытие архива + **ZipFile::close** - закрывает ZIP-архив. +```php +$zipFile->close(); +``` +### Запуск тестов +Установите зависимости для разработки. +```bash +composer install --dev +``` +Запустите тесты: +```bash +vendor/bin/phpunit -v -c phpunit.xml +``` +### История изменений +История изменений на [странице релизов](https://github.com/Ne-Lexa/php-zip/releases). + +### Обновление версий +#### Обновление с версии 2 до версии 3.0 +Обновите мажорную версию в файле `composer.json` до `^3.0`. +```json +{ + "require": { + "nelexa/zip": "^3.0" + } +} +``` +Затем установите обновления с помощью `Composer`: +```bash +composer update nelexa/zip +``` +Обновите ваш код для работы с новой версией: +- Класс `ZipOutputFile` объединён с `ZipFile` и удалён. + + Замените `new \PhpZip\ZipOutputFile()` на `new \PhpZip\ZipFile()` +- Статичиская инициализация методов стала не статической. + + Замените `\PhpZip\ZipFile::openFromFile($filename);` на `(new \PhpZip\ZipFile())->openFile($filename);` + + Замените `\PhpZip\ZipOutputFile::openFromFile($filename);` на `(new \PhpZip\ZipFile())->openFile($filename);` + + Замените `\PhpZip\ZipFile::openFromString($contents);` на `(new \PhpZip\ZipFile())->openFromString($contents);` + + Замените `\PhpZip\ZipFile::openFromStream($stream);` на `(new \PhpZip\ZipFile())->openFromStream($stream);` + + Замените `\PhpZip\ZipOutputFile::create()` на `new \PhpZip\ZipFile()` + + Замените `\PhpZip\ZipOutputFile::openFromZipFile($zipFile)` на `(new \PhpZip\ZipFile())->openFile($filename);` +- Переименуйте методы: + + `addFromFile` в `addFile` + + `setLevel` в `setCompressionLevel` + + `ZipFile::setPassword` в `ZipFile::withReadPassword` + + `ZipOutputFile::setPassword` в `ZipFile::withNewPassword` + + `ZipOutputFile::disableEncryptionAllEntries` в `ZipFile::withoutPassword` + + `ZipOutputFile::setComment` в `ZipFile::setArchiveComment` + + `ZipFile::getComment` в `ZipFile::getArchiveComment` +- Изменились сигнатуры для методов `addDir`, `addFilesFromGlob`, `addFilesFromRegex`. +- Удалены методы: + + `getLevel` + + `setCompressionMethod` + + `setEntryPassword` diff --git a/vendor/nelexa/zip/README.md b/vendor/nelexa/zip/README.md new file mode 100644 index 0000000..44ea792 --- /dev/null +++ b/vendor/nelexa/zip/README.md @@ -0,0 +1,832 @@ +`PhpZip` +======== +`PhpZip` is a php-library for extended work with ZIP-archives. + +[![Build Status](https://travis-ci.org/Ne-Lexa/php-zip.svg?branch=master)](https://travis-ci.org/Ne-Lexa/php-zip) +[![Code Coverage](https://scrutinizer-ci.com/g/Ne-Lexa/php-zip/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/Ne-Lexa/php-zip/?branch=master) +[![Latest Stable Version](https://poser.pugx.org/nelexa/zip/v/stable)](https://packagist.org/packages/nelexa/zip) +[![Total Downloads](https://poser.pugx.org/nelexa/zip/downloads)](https://packagist.org/packages/nelexa/zip) +[![Minimum PHP Version](http://img.shields.io/badge/php-%3E%3D%205.5-8892BF.svg)](https://php.net/) +[![License](https://poser.pugx.org/nelexa/zip/license)](https://packagist.org/packages/nelexa/zip) + +[Russian Documentation](README.RU.md) + +Table of contents +----------------- +- [Features](#Features) +- [Requirements](#Requirements) +- [Installation](#Installation) +- [Examples](#Examples) +- [Glossary](#Glossary) +- [Documentation](#Documentation) + + [Overview of methods of the class `\PhpZip\ZipFile`](#Documentation-Overview) + + [Creation/Opening of ZIP-archive](#Documentation-Open-Zip-Archive) + + [Reading entries from the archive](#Documentation-Open-Zip-Entries) + + [Iterating entries](#Documentation-Zip-Iterate) + + [Getting information about entries](#Documentation-Zip-Info) + + [Adding entries to the archive](#Documentation-Add-Zip-Entries) + + [Deleting entries from the archive](#Documentation-Remove-Zip-Entries) + + [Working with entries and archive](#Documentation-Entries) + + [Working with passwords](#Documentation-Password) + + [zipalign - alignment tool for Android (APK) files](#Documentation-ZipAlign-Usage) + + [Undo changes](#Documentation-Unchanged) + + [Saving a file or output to a browser](#Documentation-Save-Or-Output-Entries) + + [Closing the archive](#Documentation-Close-Zip-Archive) +- [Running the tests](#Running-Tests) +- [Changelog](#Changelog) +- [Upgrade](#Upgrade) + + [Upgrade version 2 to version 3.0](#Upgrade-v2-to-v3) + +### Features +- Opening and unzipping zip files. +- Creating ZIP-archives. +- Modifying ZIP archives. +- Pure php (not require extension `php-zip` and class `\ZipArchive`). +- It supports saving the archive to a file, outputting the archive to the browser, or outputting it as a string without saving it to a file. +- Archival comments and comments of individual entry are supported. +- Get information about each entry in the archive. +- Only the following compression methods are supported: + + No compressed (Stored). + + Deflate compression. + + BZIP2 compression with the extension `php-bz2`. +- Support for `ZIP64` (file size is more than 4 GB or the number of entries in the archive is more than 65535). +- Built-in support for aligning the archive to optimize Android packages (APK) [`zipalign`](https://developer.android.com/studio/command-line/zipalign.html). +- Working with passwords for PHP 5.5 + > **Attention!** + > + > For 32-bit systems, the `Traditional PKWARE Encryption (ZipCrypto)` encryption method is not currently supported. + > Use the encryption method `WinZIP AES Encryption`, whenever possible. + + Set the password to read the archive for all entries or only for some. + + Change the password for the archive, including for individual entries. + + Delete the archive password for all or individual entries. + + Set the password and/or the encryption method, both for all, and for individual entries in the archive. + + Set different passwords and encryption methods for different entries. + + Delete the password for all or some entries. + + Support `Traditional PKWARE Encryption (ZipCrypto)` and `WinZIP AES Encryption` encryption methods. + + Set the encryption method for all or individual entries in the archive. + +### Requirements +- `PHP` >= 5.5 (preferably 64-bit). +- Optional php-extension `bzip2` for BZIP2 compression. +- Optional php-extension `openssl` or `mcrypt` for `WinZip Aes Encryption` support. + +### Installation +`composer require nelexa/zip` + +Latest stable version: [![Latest Stable Version](https://poser.pugx.org/nelexa/zip/v/stable)](https://packagist.org/packages/nelexa/zip) + +### Examples +```php +// create new archive +$zipFile = new \PhpZip\ZipFile(); +try{ + $zipFile + ->addFromString('zip/entry/filename', 'Is file content') // add an entry from the string + ->addFile('/path/to/file', 'data/tofile') // add an entry from the file + ->addDir(__DIR__, 'to/path/') // add files from the directory + ->saveAsFile($outputFilename) // save the archive to a file + ->close(); // close archive + + // open archive, extract, add files, set password and output to browser. + $zipFile + ->openFile($outputFilename) // open archive from file + ->extractTo($outputDirExtract) // extract files to the specified directory + ->deleteFromRegex('~^\.~') // delete all hidden (Unix) files + ->addFromString('dir/file.txt', 'Test file') // add a new entry from the string + ->setPassword('password') // set password for all entries + ->outputAsAttachment('library.jar'); // output to the browser without saving to a file +} +catch(\PhpZip\Exception\ZipException $e){ + // handle exception +} +finally{ + $zipFile->close(); +} +``` +Other examples can be found in the `tests/` folder + +### Glossary +**Zip Entry** - file or folder in a ZIP-archive. Each entry in the archive has certain properties, for example: file name, compression method, encryption method, file size before compression, file size after compression, CRC32 and others. + +### Documentation: +#### Overview of methods of the class `\PhpZip\ZipFile` +- [ZipFile::__construct](#Documentation-ZipFile-__construct) - initializes the ZIP archive. +- [ZipFile::addAll](#Documentation-ZipFile-addAll) - adds all entries from an array. +- [ZipFile::addDir](#Documentation-ZipFile-addDir) - adds files to the archive from the directory on the specified path without subdirectories. +- [ZipFile::addDirRecursive](#Documentation-ZipFile-addDirRecursive) - adds files to the archive from the directory on the specified path with subdirectories. +- [ZipFile::addEmptyDir](#Documentation-ZipFile-addEmptyDir) - add a new directory. +- [ZipFile::addFile](#Documentation-ZipFile-addFile) - adds a file to a ZIP archive from the given path. +- [ZipFile::addSplFile](#Documentation-ZipFile-addSplFile) - adds a `\SplFileInfo` to a ZIP archive. +- [ZipFile::addFromFinder](#Documentation-ZipFile-addFromFinder) - adds files from the `Symfony\Component\Finder\Finder` to a ZIP archive. +- [ZipFile::addFilesFromIterator](#Documentation-ZipFile-addFilesFromIterator) - adds files from the iterator of directories. +- [ZipFile::addFilesFromGlob](#Documentation-ZipFile-addFilesFromGlob) - adds files from a directory by glob pattern without subdirectories. +- [ZipFile::addFilesFromGlobRecursive](#Documentation-ZipFile-addFilesFromGlobRecursive) - adds files from a directory by glob pattern with subdirectories. +- [ZipFile::addFilesFromRegex](#Documentation-ZipFile-addFilesFromRegex) - adds files from a directory by PCRE pattern without subdirectories. +- [ZipFile::addFilesFromRegexRecursive](#Documentation-ZipFile-addFilesFromRegexRecursive) - adds files from a directory by PCRE pattern with subdirectories. +- [ZipFile::addFromStream](#Documentation-ZipFile-addFromStream) - adds a entry from the stream to the ZIP archive. +- [ZipFile::addFromString](#Documentation-ZipFile-addFromString) - adds a file to a ZIP archive using its contents. +- [ZipFile::close](#Documentation-ZipFile-close) - close the archive. +- [ZipFile::count](#Documentation-ZipFile-count) - returns the number of entries in the archive. +- [ZipFile::deleteFromName](#Documentation-ZipFile-deleteFromName) - deletes an entry in the archive using its name. +- [ZipFile::deleteFromGlob](#Documentation-ZipFile-deleteFromGlob) - deletes a entries in the archive using glob pattern. +- [ZipFile::deleteFromRegex](#Documentation-ZipFile-deleteFromRegex) - deletes a entries in the archive using PCRE pattern. +- [ZipFile::deleteAll](#Documentation-ZipFile-deleteAll) - deletes all entries in the ZIP archive. +- [ZipFile::disableEncryption](#Documentation-ZipFile-disableEncryption) - disable encryption for all entries that are already in the archive. +- [ZipFile::disableEncryptionEntry](#Documentation-ZipFile-disableEncryptionEntry) - disable encryption of an entry defined by its name. +- [ZipFile::extractTo](#Documentation-ZipFile-extractTo) - extract the archive contents. +- [ZipFile::getAllInfo](#Documentation-ZipFile-getAllInfo) - returns detailed information about all entries in the archive. +- [ZipFile::getArchiveComment](#Documentation-ZipFile-getArchiveComment) - returns the Zip archive comment. +- [ZipFile::getEntryComment](#Documentation-ZipFile-getEntryComment) - returns the comment of an entry using the entry name. +- [ZipFile::getEntryContent](#Documentation-ZipFile-getEntryContent) - returns the entry contents using its name. +- [ZipFile::getEntryInfo](#Documentation-ZipFile-getEntryInfo) - returns detailed information about the entry in the archive. +- [ZipFile::getListFiles](#Documentation-ZipFile-getListFiles) - returns list of archive files. +- [ZipFile::hasEntry](#Documentation-ZipFile-hasEntry) - checks if there is an entry in the archive. +- [ZipFile::isDirectory](#Documentation-ZipFile-isDirectory) - checks that the entry in the archive is a directory. +- [ZipFile::matcher](#Documentation-ZipFile-matcher) - selecting entries in the archive to perform operations on them. +- [ZipFile::openFile](#Documentation-ZipFile-openFile) - opens a zip-archive from a file. +- [ZipFile::openFromString](#Documentation-ZipFile-openFromString) - opens a zip-archive from a string. +- [ZipFile::openFromStream](#Documentation-ZipFile-openFromStream) - opens a zip-archive from the stream. +- [ZipFile::outputAsAttachment](#Documentation-ZipFile-outputAsAttachment) - outputs a ZIP-archive to the browser. +- [ZipFile::outputAsResponse](#Documentation-ZipFile-outputAsResponse) - outputs a ZIP-archive as PSR-7 Response. +- [ZipFile::outputAsString](#Documentation-ZipFile-outputAsString) - outputs a ZIP-archive as string. +- [ZipFile::rename](#Documentation-ZipFile-rename) - renames an entry defined by its name. +- [ZipFile::rewrite](#Documentation-ZipFile-rewrite) - save changes and re-open the changed archive. +- [ZipFile::saveAsFile](#Documentation-ZipFile-saveAsFile) - saves the archive to a file. +- [ZipFile::saveAsStream](#Documentation-ZipFile-saveAsStream) - writes the archive to the stream. +- [ZipFile::setArchiveComment](#Documentation-ZipFile-setArchiveComment) - set the comment of a ZIP archive. +- [ZipFile::setCompressionLevel](#Documentation-ZipFile-setCompressionLevel) - set the compression level for all files in the archive. +- [ZipFile::setCompressionLevelEntry](#Documentation-ZipFile-setCompressionLevelEntry) - sets the compression level for the entry by its name. +- [ZipFile::setCompressionMethodEntry](#Documentation-ZipFile-setCompressionMethodEntry) - sets the compression method for the entry by its name. +- [ZipFile::setEntryComment](#Documentation-ZipFile-setEntryComment) - set the comment of an entry defined by its name. +- [ZipFile::setReadPassword](#Documentation-ZipFile-setReadPassword) - set the password for the open archive. +- [ZipFile::setReadPasswordEntry](#Documentation-ZipFile-setReadPasswordEntry) - sets a password for reading of an entry defined by its name. +- ~~ZipFile::withNewPassword~~ - is an deprecated method, use the [ZipFile::setPassword](#Documentation-ZipFile-setPassword) method. +- [ZipFile::setPassword](#Documentation-ZipFile-setPassword) - sets a new password for all files in the archive. +- [ZipFile::setPasswordEntry](#Documentation-ZipFile-setPasswordEntry) - sets a new password of an entry defined by its name. +- [ZipFile::setZipAlign](#Documentation-ZipFile-setZipAlign) - sets the alignment of the archive to optimize APK files (Android packages). +- [ZipFile::unchangeAll](#Documentation-ZipFile-unchangeAll) - undo all changes done in the archive. +- [ZipFile::unchangeArchiveComment](#Documentation-ZipFile-unchangeArchiveComment) - undo changes to the archive comment. +- [ZipFile::unchangeEntry](#Documentation-ZipFile-unchangeEntry) - undo changes of an entry defined by its name. +- ~~ZipFile::withoutPassword~~ - is an deprecated method, use the [ZipFile::disableEncryption](#Documentation-ZipFile-disableEncryption) method. +- ~~ZipFile::withReadPassword~~ - is an deprecated method, use the [ZipFile::setReadPassword](#Documentation-ZipFile-setReadPassword) method. + +#### Creation/Opening of ZIP-archive +**ZipFile::__construct** - initializes the ZIP archive. +```php +$zipFile = new \PhpZip\ZipFile(); +``` + **ZipFile::openFile** - opens a zip-archive from a file. +```php +$zipFile = new \PhpZip\ZipFile(); +$zipFile->openFile('file.zip'); +``` + **ZipFile::openFromString** - opens a zip-archive from a string. +```php +$zipFile = new \PhpZip\ZipFile(); +$zipFile->openFromString($stringContents); +``` + **ZipFile::openFromStream** - opens a zip-archive from the stream. +```php +$stream = fopen('file.zip', 'rb'); + +$zipFile = new \PhpZip\ZipFile(); +$zipFile->openFromStream($stream); +``` +#### Reading entries from the archive + **ZipFile::count** - returns the number of entries in the archive. +```php +$zipFile = new \PhpZip\ZipFile(); + +$count = count($zipFile); +// or +$count = $zipFile->count(); +``` + **ZipFile::getListFiles** - returns list of archive files. +```php +$zipFile = new \PhpZip\ZipFile(); +$listFiles = $zipFile->getListFiles(); + +// example array contents: +// array ( +// 0 => 'info.txt', +// 1 => 'path/to/file.jpg', +// 2 => 'another path/', +// 3 => '0', +// ) +``` + **ZipFile::getEntryContent** - returns the entry contents using its name. +```php +// $entryName = 'path/to/example-entry-name.txt'; +$zipFile = new \PhpZip\ZipFile(); + +$contents = $zipFile[$entryName]; +// or +$contents = $zipFile->getEntryContents($entryName); +``` + **ZipFile::hasEntry** - checks if there is an entry in the archive. +```php +// $entryName = 'path/to/example-entry-name.txt'; +$zipFile = new \PhpZip\ZipFile(); + +$hasEntry = isset($zipFile[$entryName]); +// or +$hasEntry = $zipFile->hasEntry($entryName); +``` + **ZipFile::isDirectory** - checks that the entry in the archive is a directory. +```php +// $entryName = 'path/to/'; +$zipFile = new \PhpZip\ZipFile(); + +$isDirectory = $zipFile->isDirectory($entryName); +``` + **ZipFile::extractTo** - extract the archive contents. +The directory must exist. +```php +$zipFile = new \PhpZip\ZipFile(); +$zipFile->extractTo($directory); +``` +Extract some files to the directory. +The directory must exist. +```php +// $toDirectory = '/tmp'; +$extractOnlyFiles = [ + 'filename1', + 'filename2', + 'dir/dir/dir/' +]; +$zipFile = new \PhpZip\ZipFile(); +$zipFile->extractTo($toDirectory, $extractOnlyFiles); +``` +#### Iterating entries +`ZipFile` is an iterator. +Can iterate all the entries in the `foreach` loop. +```php +foreach($zipFile as $entryName => $contents){ + echo "Filename: $entryName" . PHP_EOL; + echo "Contents: $contents" . PHP_EOL; + echo '-----------------------------' . PHP_EOL; +} +``` +Can iterate through the `Iterator`. +```php +$iterator = new \ArrayIterator($zipFile); +while ($iterator->valid()) +{ + $entryName = $iterator->key(); + $contents = $iterator->current(); + + echo "Filename: $entryName" . PHP_EOL; + echo "Contents: $contents" . PHP_EOL; + echo '-----------------------------' . PHP_EOL; + + $iterator->next(); +} +``` +#### Getting information about entries + **ZipFile::getArchiveComment** - returns the Zip archive comment. +```php +$zipFile = new \PhpZip\ZipFile(); +$commentArchive = $zipFile->getArchiveComment(); +``` + **ZipFile::getEntryComment** - returns the comment of an entry using the entry name. +```php +$zipFile = new \PhpZip\ZipFile(); +$commentEntry = $zipFile->getEntryComment($entryName); +``` + **ZipFile::getEntryInfo** - returns detailed information about the entry in the archive +```php +$zipFile = new \PhpZip\ZipFile(); +$zipInfo = $zipFile->getEntryInfo('file.txt'); +``` + **ZipFile::getAllInfo** - returns detailed information about all entries in the archive. +```php +$zipAllInfo = $zipFile->getAllInfo(); +``` +#### Adding entries to the archive + +All methods of adding entries to a ZIP archive allow you to specify a method for compressing content. + +The following methods of compression are available: +- `\PhpZip\Constants\ZipCompressionMethod::STORED` - no compression +- `\PhpZip\Constants\ZipCompressionMethod::DEFLATED` - Deflate compression +- `\PhpZip\Constants\ZipCompressionMethod::BZIP2` - Bzip2 compression with the extension `ext-bz2` + + **ZipFile::addFile** - adds a file to a ZIP archive from the given path. +```php +$zipFile = new \PhpZip\ZipFile(); +// $file = '...../file.ext'; +// $entryName = 'file2.ext' +$zipFile->addFile($file); + +// you can specify the name of the entry in the archive (if null, then the last component from the file name is used) +$zipFile->addFile($file, $entryName); + +// you can specify a compression method +$zipFile->addFile($file, $entryName, \PhpZip\Constants\ZipCompressionMethod::STORED); // No compression +$zipFile->addFile($file, $entryName, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate compression +$zipFile->addFile($file, $entryName, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 compression +``` + +**ZipFile::addSplFile"** - adds a `\SplFileInfo` to a ZIP archive. +```php +// $file = '...../file.ext'; +// $entryName = 'file2.ext' +$zipFile = new \PhpZip\ZipFile(); + +$splFile = new \SplFileInfo('README.md'); + +$zipFile->addSplFile($splFile); +$zipFile->addSplFile($splFile, $entryName); +// or +$zipFile[$entryName] = new \SplFileInfo($file); + +// set compression method +$zipFile->addSplFile($splFile, $entryName, $options = [ + \PhpZip\Constants\ZipOptions::COMPRESSION_METHOD => \PhpZip\Constants\ZipCompressionMethod::DEFLATED, +]); +``` + +**ZipFile::addFromFinder"** - adds files from the `Symfony\Component\Finder\Finder` to a ZIP archive. +https://symfony.com/doc/current/components/finder.html +```php +$finder = new \Symfony\Component\Finder\Finder(); +$finder + ->files() + ->name('*.{jpg,jpeg,gif,png}') + ->name('/^[0-9a-f]\./') + ->contains('/lorem\s+ipsum$/i') + ->in('path'); + +$zipFile = new \PhpZip\ZipFile(); +$zipFile->addFromFinder($finder, $options = [ + \PhpZip\Constants\ZipOptions::COMPRESSION_METHOD => \PhpZip\Constants\ZipCompressionMethod::DEFLATED, + \PhpZip\Constants\ZipOptions::MODIFIED_TIME => new \DateTimeImmutable('-1 day 5 min') +]); +``` + **ZipFile::addFromString** - adds a file to a ZIP archive using its contents. +```php +$zipFile = new \PhpZip\ZipFile(); + +$zipFile[$entryName] = $contents; +// or +$zipFile->addFromString($entryName, $contents); + +// you can specify a compression method +$zipFile->addFromString($entryName, $contents, \PhpZip\Constants\ZipCompressionMethod::STORED); // No compression +$zipFile->addFromString($entryName, $contents, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate compression +$zipFile->addFromString($entryName, $contents, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 compression +``` + **ZipFile::addFromStream** - adds a entry from the stream to the ZIP archive. +```php +$zipFile = new \PhpZip\ZipFile(); +// $stream = fopen(..., 'rb'); + +$zipFile->addFromStream($stream, $entryName); +// or +$zipFile[$entryName] = $stream; + +// you can specify a compression method +$zipFile->addFromStream($stream, $entryName, \PhpZip\Constants\ZipCompressionMethod::STORED); // No compression +$zipFile->addFromStream($stream, $entryName, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate compression +$zipFile->addFromStream($stream, $entryName, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 compression +``` + **ZipFile::addEmptyDir** - add a new directory. +```php +$zipFile = new \PhpZip\ZipFile(); +// $path = "path/to/"; +$zipFile->addEmptyDir($path); +// or +$zipFile[$path] = null; +``` + **ZipFile::addAll** - adds all entries from an array. +```php +$entries = [ + 'file.txt' => 'file contents', // add an entry from the string contents + 'empty dir/' => null, // add empty directory + 'path/to/file.jpg' => fopen('..../filename', 'rb'), // add an entry from the stream + 'path/to/file.dat' => new \SplFileInfo('..../filename'), // add an entry from the file +]; + +$zipFile = new \PhpZip\ZipFile(); +$zipFile->addAll($entries); +``` + **ZipFile::addDir** - adds files to the archive from the directory on the specified path without subdirectories. +```php +$zipFile = new \PhpZip\ZipFile(); +$zipFile->addDir($dirName); + +// you can specify the path in the archive to which you want to put entries +$localPath = 'to/path/'; +$zipFile->addDir($dirName, $localPath); + +// you can specify a compression method +$zipFile->addDir($dirName, $localPath, \PhpZip\Constants\ZipCompressionMethod::STORED); // No compression +$zipFile->addDir($dirName, $localPath, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate compression +$zipFile->addDir($dirName, $localPath, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 compression +``` + **ZipFile::addDirRecursive** - adds files to the archive from the directory on the specified path with subdirectories. +```php +$zipFile = new \PhpZip\ZipFile(); +$zipFile->addDirRecursive($dirName); + +// you can specify the path in the archive to which you want to put entries +$localPath = 'to/path/'; +$zipFile->addDirRecursive($dirName, $localPath); + +// you can specify a compression method +$zipFile->addDirRecursive($dirName, $localPath, \PhpZip\Constants\ZipCompressionMethod::STORED); // No compression +$zipFile->addDirRecursive($dirName, $localPath, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate compression +$zipFile->addDirRecursive($dirName, $localPath, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 compression +``` + **ZipFile::addFilesFromIterator** - adds files from the iterator of directories. +```php +// $directoryIterator = new \DirectoryIterator($dir); // without subdirectories +// $directoryIterator = new \RecursiveDirectoryIterator($dir); // with subdirectories +$zipFile = new \PhpZip\ZipFile(); +$zipFile->addFilesFromIterator($directoryIterator); + +// you can specify the path in the archive to which you want to put entries +$localPath = 'to/path/'; +$zipFile->addFilesFromIterator($directoryIterator, $localPath); +// or +$zipFile[$localPath] = $directoryIterator; + +// you can specify a compression method +$zipFile->addFilesFromIterator($directoryIterator, $localPath, \PhpZip\Constants\ZipCompressionMethod::STORED); // No compression +$zipFile->addFilesFromIterator($directoryIterator, $localPath, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate compression +$zipFile->addFilesFromIterator($directoryIterator, $localPath, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 compression +``` +Example with some files ignoring: +```php +$ignoreFiles = [ + 'file_ignore.txt', + 'dir_ignore/sub dir ignore/' +]; + +// $directoryIterator = new \DirectoryIterator($dir); // without subdirectories +// $directoryIterator = new \RecursiveDirectoryIterator($dir); // with subdirectories +// use \PhpZip\Util\Iterator\IgnoreFilesFilterIterator for non-recursive search + +$zipFile = new \PhpZip\ZipFile(); +$ignoreIterator = new \PhpZip\Util\Iterator\IgnoreFilesRecursiveFilterIterator( + $directoryIterator, + $ignoreFiles +); + +$zipFile->addFilesFromIterator($ignoreIterator); +``` + **ZipFile::addFilesFromGlob** - adds files from a directory by [glob pattern](https://en.wikipedia.org/wiki/Glob_(programming)) without subdirectories. +```php +$globPattern = '**.{jpg,jpeg,png,gif}'; // example glob pattern -> add all .jpg, .jpeg, .png and .gif files + +$zipFile = new \PhpZip\ZipFile(); +$zipFile->addFilesFromGlob($dir, $globPattern); + +// you can specify the path in the archive to which you want to put entries +$localPath = 'to/path/'; +$zipFile->addFilesFromGlob($dir, $globPattern, $localPath); + +// you can specify a compression method +$zipFile->addFilesFromGlob($dir, $globPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::STORED); // No compression +$zipFile->addFilesFromGlob($dir, $globPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate compression +$zipFile->addFilesFromGlob($dir, $globPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 compression +``` + **ZipFile::addFilesFromGlobRecursive** - adds files from a directory by [glob pattern](https://en.wikipedia.org/wiki/Glob_(programming)) with subdirectories. +```php +$globPattern = '**.{jpg,jpeg,png,gif}'; // example glob pattern -> add all .jpg, .jpeg, .png and .gif files + +$zipFile = new \PhpZip\ZipFile(); +$zipFile->addFilesFromGlobRecursive($dir, $globPattern); + +// you can specify the path in the archive to which you want to put entries +$localPath = 'to/path/'; +$zipFile->addFilesFromGlobRecursive($dir, $globPattern, $localPath); + +// you can specify a compression method +$zipFile->addFilesFromGlobRecursive($dir, $globPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::STORED); // No compression +$zipFile->addFilesFromGlobRecursive($dir, $globPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate compression +$zipFile->addFilesFromGlobRecursive($dir, $globPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 compression +``` + **ZipFile::addFilesFromRegex** - adds files from a directory by [PCRE pattern](https://en.wikipedia.org/wiki/Regular_expression) without subdirectories. +```php +$regexPattern = '/\.(jpe?g|png|gif)$/si'; // example regex pattern -> add all .jpg, .jpeg, .png and .gif files + +$zipFile = new \PhpZip\ZipFile(); +$zipFile->addFilesFromRegex($dir, $regexPattern); + +// you can specify the path in the archive to which you want to put entries +$localPath = 'to/path/'; +$zipFile->addFilesFromRegex($dir, $regexPattern, $localPath); + +// you can specify a compression method +$zipFile->addFilesFromRegex($dir, $regexPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::STORED); // No compression +$zipFile->addFilesFromRegex($dir, $regexPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate compression +$zipFile->addFilesFromRegex($dir, $regexPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 compression +``` + **ZipFile::addFilesFromRegexRecursive** - adds files from a directory by [PCRE pattern](https://en.wikipedia.org/wiki/Regular_expression) with subdirectories. +```php +$regexPattern = '/\.(jpe?g|png|gif)$/si'; // example regex pattern -> add all .jpg, .jpeg, .png and .gif files + + +$zipFile->addFilesFromRegexRecursive($dir, $regexPattern); + +// you can specify the path in the archive to which you want to put entries +$localPath = 'to/path/'; +$zipFile->addFilesFromRegexRecursive($dir, $regexPattern, $localPath); + +// you can specify a compression method +$zipFile->addFilesFromRegexRecursive($dir, $regexPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::STORED); // No compression +$zipFile->addFilesFromRegexRecursive($dir, $regexPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); // Deflate compression +$zipFile->addFilesFromRegexRecursive($dir, $regexPattern, $localPath, \PhpZip\Constants\ZipCompressionMethod::BZIP2); // BZIP2 compression +``` +#### Deleting entries from the archive + **ZipFile::deleteFromName** - deletes an entry in the archive using its name. +```php +$zipFile = new \PhpZip\ZipFile(); +$zipFile->deleteFromName($entryName); +``` + **ZipFile::deleteFromGlob** - deletes a entries in the archive using [glob pattern](https://en.wikipedia.org/wiki/Glob_(programming)). +```php +$globPattern = '**.{jpg,jpeg,png,gif}'; // example glob pattern -> delete all .jpg, .jpeg, .png and .gif files + +$zipFile = new \PhpZip\ZipFile(); +$zipFile->deleteFromGlob($globPattern); +``` + **ZipFile::deleteFromRegex** - deletes a entries in the archive using [PCRE pattern](https://en.wikipedia.org/wiki/Regular_expression). +```php +$regexPattern = '/\.(jpe?g|png|gif)$/si'; // example regex pattern -> delete all .jpg, .jpeg, .png and .gif files + +$zipFile = new \PhpZip\ZipFile(); +$zipFile->deleteFromRegex($regexPattern); +``` + **ZipFile::deleteAll** - deletes all entries in the ZIP archive. +```php +$zipFile = new \PhpZip\ZipFile(); +$zipFile->deleteAll(); +``` +#### Working with entries and archive + **ZipFile::rename** - renames an entry defined by its name. +```php +$zipFile = new \PhpZip\ZipFile(); +$zipFile->rename($oldName, $newName); +``` + **ZipFile::setCompressionLevel** - set the compression level for all files in the archive. + +> _Note that this method does not apply to entries that are added after this method is run._ + +By default, the compression level is 5 (`\PhpZip\Constants\ZipCompressionLevel::NORMAL`) or the compression level specified in the archive for Deflate compression. + +The values range from 1 (`\PhpZip\Constants\ZipCompressionLevel::SUPER_FAST`) to 9 (`\PhpZip\Constants\ZipCompressionLevel::MAXIMUM`) are supported. The higher the number, the better and longer the compression. +```php +$zipFile = new \PhpZip\ZipFile(); +$zipFile->setCompressionLevel(\PhpZip\Constants\ZipCompressionLevel::MAXIMUM); +``` + **ZipFile::setCompressionLevelEntry** - sets the compression level for the entry by its name. + +The values range from 1 (`\PhpZip\Constants\ZipCompressionLevel::SUPER_FAST`) to 9 (`\PhpZip\Constants\ZipCompressionLevel::MAXIMUM`) are supported. The higher the number, the better and longer the compression. +```php +$zipFile = new \PhpZip\ZipFile(); +$zipFile->setCompressionLevelEntry($entryName, \PhpZip\Constants\ZipCompressionLevel::FAST); +``` + **ZipFile::setCompressionMethodEntry** - sets the compression method for the entry by its name. + +The following compression methods are available: +- `\PhpZip\Constants\ZipCompressionMethod::STORED` - No compression +- `\PhpZip\Constants\ZipCompressionMethod::DEFLATED` - Deflate compression +- `\PhpZip\Constants\ZipCompressionMethod::BZIP2` - Bzip2 compression with the extension `ext-bz2` +```php +$zipFile = new \PhpZip\ZipFile(); +$zipFile->setCompressionMethodEntry($entryName, \PhpZip\Constants\ZipCompressionMethod::DEFLATED); +``` + **ZipFile::setArchiveComment** - set the comment of a ZIP archive. +```php +$zipFile = new \PhpZip\ZipFile(); +$zipFile->setArchiveComment($commentArchive); +``` + **ZipFile::setEntryComment** - set the comment of an entry defined by its name. +```php +$zipFile = new \PhpZip\ZipFile(); +$zipFile->setEntryComment($entryName, $comment); +``` + **ZipFile::matcher** - selecting entries in the archive to perform operations on them. +```php +$zipFile = new \PhpZip\ZipFile(); +$matcher = $zipFile->matcher(); +``` +Selecting files from the archive one at a time: +```php +$matcher + ->add('entry name') + ->add('another entry'); +``` +Select multiple files in the archive: +```php +$matcher->add([ + 'entry name', + 'another entry name', + 'path/' +]); +``` +Selecting files by regular expression: +```php +$matcher->match('~\.jpe?g$~i'); +``` +Select all files in the archive: +```php +$matcher->all(); +``` +count() - gets the number of selected entries: +```php +$count = count($matcher); +// or +$count = $matcher->count(); +``` +getMatches() - returns a list of selected entries: +```php +$entries = $matcher->getMatches(); +// example array contents: ['entry name', 'another entry name']; +``` +invoke() - invoke a callable function on selected entries: +```php +// example +$matcher->invoke(static function($entryName) use($zipFile) { + $newName = preg_replace('~\.(jpe?g)$~i', '.no_optimize.$1', $entryName); + $zipFile->rename($entryName, $newName); +}); +``` +Functions for working on the selected entries: +```php +$matcher->delete(); // remove selected entries from a ZIP archive +$matcher->setPassword($password); // sets a new password for the selected entries +$matcher->setPassword($password, $encryptionMethod); // sets a new password and encryption method to selected entries +$matcher->setEncryptionMethod($encryptionMethod); // sets the encryption method to the selected entries +$matcher->disableEncryption(); // disables encryption for selected entries +``` +#### Working with passwords + +Implemented support for encryption methods: +- `\PhpZip\Constants\ZipEncryptionMethod::PKWARE` - Traditional PKWARE encryption (legacy) +- `\PhpZip\Constants\ZipEncryptionMethod::WINZIP_AES_256` - WinZip AES encryption 256 bit (recommended) +- `\PhpZip\Constants\ZipEncryptionMethod::WINZIP_AES_192` - WinZip AES encryption 192 bit +- `\PhpZip\Constants\ZipEncryptionMethod::WINZIP_AES_128` - WinZip AES encryption 128 bit + + **ZipFile::setReadPassword** - set the password for the open archive. + +> _Setting a password is not required for adding new entries or deleting existing ones, but if you want to extract the content or change the method / compression level, the encryption method, or change the password, in this case the password must be specified._ +```php +$zipFile->setReadPassword($password); +``` + **ZipFile::setReadPasswordEntry** - gets a password for reading of an entry defined by its name. +```php +$zipFile->setReadPasswordEntry($entryName, $password); +``` + **ZipFile::setPassword** - sets a new password for all files in the archive. + +> _Note that this method does not apply to entries that are added after this method is run._ +```php +$zipFile->setPassword($password); +``` +You can set the encryption method: +```php +$encryptionMethod = \PhpZip\Constants\ZipEncryptionMethod::WINZIP_AES_256; +$zipFile->setPassword($password, $encryptionMethod); +``` + **ZipFile::setPasswordEntry** - sets a new password of an entry defined by its name. +```php +$zipFile->setPasswordEntry($entryName, $password); +``` +You can set the encryption method: +```php +$encryptionMethod = \PhpZip\Constants\ZipEncryptionMethod::WINZIP_AES_256; +$zipFile->setPasswordEntry($entryName, $password, $encryptionMethod); +``` + **ZipFile::disableEncryption** - disable encryption for all entries that are already in the archive. + +> _Note that this method does not apply to entries that are added after this method is run._ +```php +$zipFile->disableEncryption(); +``` + **ZipFile::disableEncryptionEntry** - disable encryption of an entry defined by its name. +```php +$zipFile->disableEncryptionEntry($entryName); +``` +#### zipalign + **ZipFile::setZipAlign** - sets the alignment of the archive to optimize APK files (Android packages). + +This method adds padding to unencrypted and not compressed entries, to optimize memory consumption in the Android system. It is recommended to use for `APK` files. The file may grow slightly. + +This method is an alternative to executing the `zipalign -f -v 4 filename.zip`. + +More details can be found on the [link](https://developer.android.com/studio/command-line/zipalign.html). +```php +$zipFile->setZipAlign(4); +``` +#### Undo changes + **ZipFile::unchangeAll** - undo all changes done in the archive. +```php +$zipFile->unchangeAll(); +``` + **ZipFile::unchangeArchiveComment** - undo changes to the archive comment. +```php +$zipFile->unchangeArchiveComment(); +``` + **ZipFile::unchangeEntry** - undo changes of an entry defined by its name. +```php +$zipFile->unchangeEntry($entryName); +``` +#### Saving a file or output to a browser + **ZipFile::saveAsFile** - saves the archive to a file. +```php +$zipFile->saveAsFile($filename); +``` + **ZipFile::saveAsStream** - writes the archive to the stream. +```php +// $fp = fopen($filename, 'w+b'); + +$zipFile->saveAsStream($fp); +``` + **ZipFile::outputAsString** - outputs a ZIP-archive as string. +```php +$rawZipArchiveBytes = $zipFile->outputAsString(); +``` + **ZipFile::outputAsAttachment** - outputs a ZIP-archive to the browser. +```php +$zipFile->outputAsAttachment($outputFilename); +``` +You can set the Mime-Type: +```php +$mimeType = 'application/zip'; +$zipFile->outputAsAttachment($outputFilename, $mimeType); +``` + **ZipFile::outputAsResponse** - outputs a ZIP-archive as [PSR-7 Response](http://www.php-fig.org/psr/psr-7/). + +The output method can be used in any PSR-7 compatible framework. +```php +// $response = ....; // instance Psr\Http\Message\ResponseInterface +$zipFile->outputAsResponse($response, $outputFilename); +``` +You can set the Mime-Type: +```php +$mimeType = 'application/zip'; +$zipFile->outputAsResponse($response, $outputFilename, $mimeType); +``` + **ZipFile::rewrite** - save changes and re-open the changed archive. +```php +$zipFile->rewrite(); +``` +#### Closing the archive + **ZipFile::close** - close the archive. +```php +$zipFile->close(); +``` +### Running the tests +Install the dependencies for the development: +```bash +composer install --dev +``` +Run the tests: +```bash +vendor/bin/phpunit +``` +### Changelog +Changes are documented in the [releases page](https://github.com/Ne-Lexa/php-zip/releases). + +### Upgrade +#### Upgrade version 2 to version 3.0 +Update the major version in the file `composer.json` to `^3.0`. +```json +{ + "require": { + "nelexa/zip": "^3.0" + } +} +``` +Then install updates using `Composer`: +```bash +composer update nelexa/zip +``` +Update your code to work with the new version: +- Class `ZipOutputFile` merged to `ZipFile` and removed. + + `new \PhpZip\ZipOutputFile()` to `new \PhpZip\ZipFile()` +- Static initialization methods are now not static. + + `\PhpZip\ZipFile::openFromFile($filename);` to `(new \PhpZip\ZipFile())->openFile($filename);` + + `\PhpZip\ZipOutputFile::openFromFile($filename);` to `(new \PhpZip\ZipFile())->openFile($filename);` + + `\PhpZip\ZipFile::openFromString($contents);` to `(new \PhpZip\ZipFile())->openFromString($contents);` + + `\PhpZip\ZipFile::openFromStream($stream);` to `(new \PhpZip\ZipFile())->openFromStream($stream);` + + `\PhpZip\ZipOutputFile::create()` to `new \PhpZip\ZipFile()` + + `\PhpZip\ZipOutputFile::openFromZipFile(\PhpZip\ZipFile $zipFile)` > `(new \PhpZip\ZipFile())->openFile($filename);` +- Rename methods: + + `addFromFile` to `addFile` + + `setLevel` to `setCompressionLevel` + + `ZipFile::setPassword` to `ZipFile::withReadPassword` + + `ZipOutputFile::setPassword` to `ZipFile::withNewPassword` + + `ZipOutputFile::disableEncryptionAllEntries` to `ZipFile::withoutPassword` + + `ZipOutputFile::setComment` to `ZipFile::setArchiveComment` + + `ZipFile::getComment` to `ZipFile::getArchiveComment` +- Changed signature for methods `addDir`, `addFilesFromGlob`, `addFilesFromRegex`. +- Remove methods: + + `getLevel` + + `setCompressionMethod` + + `setEntryPassword` + + diff --git a/vendor/nelexa/zip/composer.json b/vendor/nelexa/zip/composer.json new file mode 100644 index 0000000..b1b5927 --- /dev/null +++ b/vendor/nelexa/zip/composer.json @@ -0,0 +1,60 @@ +{ + "name": "nelexa/zip", + "type": "library", + "description": "PhpZip is a php-library for extended work with ZIP-archives. Open, create, update, delete, extract and get info tool. Supports appending to existing ZIP files, WinZip AES encryption, Traditional PKWARE Encryption, ZipAlign tool, BZIP2 compression, external file attributes and ZIP64 extensions. Alternative ZipArchive. It does not require php-zip extension.", + "keywords": [ + "zip", + "unzip", + "archive", + "extract", + "winzip", + "zipalign", + "ziparchive" + ], + "homepage": "https://github.com/Ne-Lexa/php-zip", + "license": "MIT", + "authors": [ + { + "name": "Ne-Lexa", + "email": "alexey@nelexa.ru", + "role": "Developer" + } + ], + "require": { + "php": "^5.5.9 || ^7.0", + "ext-zlib": "*", + "psr/http-message": "^1.0", + "paragonie/random_compat": "*", + "symfony/finder": "^3.0|^4.0|^5.0" + }, + "require-dev": { + "ext-bz2": "*", + "ext-openssl": "*", + "ext-fileinfo": "*", + "ext-xml": "*", + "guzzlehttp/psr7": "^1.6", + "phpunit/phpunit": "^4.8|^5.7", + "symfony/var-dumper": "^3.0|^4.0|^5.0" + }, + "autoload": { + "psr-4": { + "PhpZip\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "PhpZip\\Tests\\": "tests/" + } + }, + "suggest": { + "ext-openssl": "Needed to support encrypt zip entries or use ext-mcrypt", + "ext-mcrypt": "Needed to support encrypt zip entries or use ext-openssl", + "ext-bz2": "Needed to support BZIP2 compression", + "ext-fileinfo": "Needed to get mime-type file" + }, + "minimum-stability": "stable", + "scripts": { + "php:fix": "php .php_cs --force", + "php:fix:debug": "php .php_cs" + } +} diff --git a/vendor/nelexa/zip/src/Constants/DosAttrs.php b/vendor/nelexa/zip/src/Constants/DosAttrs.php new file mode 100644 index 0000000..5bebe65 --- /dev/null +++ b/vendor/nelexa/zip/src/Constants/DosAttrs.php @@ -0,0 +1,33 @@ + 'Stored', + 1 => 'Shrunk', + 2 => 'Reduced compression factor 1', + 3 => 'Reduced compression factor 2', + 4 => 'Reduced compression factor 3', + 5 => 'Reduced compression factor 4', + 6 => 'Imploded', + 7 => 'Reserved for Tokenizing compression algorithm', + self::DEFLATED => 'Deflated', + 9 => 'Enhanced Deflating using Deflate64(tm)', + 10 => 'PKWARE Data Compression Library Imploding', + 11 => 'Reserved by PKWARE', + self::BZIP2 => 'BZIP2', + 13 => 'Reserved by PKWARE', + 14 => 'LZMA', + 15 => 'Reserved by PKWARE', + 16 => 'Reserved by PKWARE', + 17 => 'Reserved by PKWARE', + 18 => 'File is compressed using IBM TERSE (new)', + 19 => 'IBM LZ77 z Architecture (PFS)', + 96 => 'WinZip JPEG Compression', + 97 => 'WavPack compressed data', + 98 => 'PPMd version I, Rev 1', + self::WINZIP_AES => 'AES Encryption', + ]; + + /** + * @param int $value + * + * @return string + */ + public static function getCompressionMethodName($value) + { + return isset(self::$ZIP_COMPRESSION_METHODS[$value]) ? + self::$ZIP_COMPRESSION_METHODS[$value] : + 'Unknown Method'; + } + + /** + * @return int[] + */ + public static function getSupportMethods() + { + static $methods; + + if ($methods === null) { + $methods = [ + self::STORED, + self::DEFLATED, + ]; + + if (\extension_loaded('bz2')) { + $methods[] = self::BZIP2; + } + } + + return $methods; + } + + /** + * @param int $compressionMethod + * + * @throws ZipUnsupportMethodException + */ + public static function checkSupport($compressionMethod) + { + $compressionMethod = (int) $compressionMethod; + + if (!\in_array($compressionMethod, self::getSupportMethods(), true)) { + throw new ZipUnsupportMethodException(sprintf( + 'Compression method %d (%s) is not supported.', + $compressionMethod, + self::getCompressionMethodName($compressionMethod) + )); + } + } +} diff --git a/vendor/nelexa/zip/src/Constants/ZipConstants.php b/vendor/nelexa/zip/src/Constants/ZipConstants.php new file mode 100644 index 0000000..39fca0f --- /dev/null +++ b/vendor/nelexa/zip/src/Constants/ZipConstants.php @@ -0,0 +1,99 @@ + */ + private static $ENCRYPTION_METHODS = [ + self::NONE => 'no encryption', + self::PKWARE => 'Traditional PKWARE encryption', + self::WINZIP_AES_128 => 'WinZip AES-128', + self::WINZIP_AES_192 => 'WinZip AES-192', + self::WINZIP_AES_256 => 'WinZip AES-256', + ]; + + /** + * @param int $value + * + * @return string + */ + public static function getEncryptionMethodName($value) + { + $value = (int) $value; + + return isset(self::$ENCRYPTION_METHODS[$value]) ? + self::$ENCRYPTION_METHODS[$value] : + 'Unknown Encryption Method'; + } + + /** + * @param int $encryptionMethod + * + * @return bool + */ + public static function hasEncryptionMethod($encryptionMethod) + { + return isset(self::$ENCRYPTION_METHODS[$encryptionMethod]); + } + + /** + * @param int $encryptionMethod + * + * @return bool + */ + public static function isWinZipAesMethod($encryptionMethod) + { + return \in_array( + (int) $encryptionMethod, + [ + self::WINZIP_AES_256, + self::WINZIP_AES_192, + self::WINZIP_AES_128, + ], + true + ); + } + + /** + * @param int $encryptionMethod + * + * @throws InvalidArgumentException + */ + public static function checkSupport($encryptionMethod) + { + $encryptionMethod = (int) $encryptionMethod; + + if (!self::hasEncryptionMethod($encryptionMethod)) { + throw new InvalidArgumentException(sprintf( + 'Encryption method %d is not supported.', + $encryptionMethod + )); + } + } +} diff --git a/vendor/nelexa/zip/src/Constants/ZipOptions.php b/vendor/nelexa/zip/src/Constants/ZipOptions.php new file mode 100644 index 0000000..c5d9671 --- /dev/null +++ b/vendor/nelexa/zip/src/Constants/ZipOptions.php @@ -0,0 +1,62 @@ + 'MS-DOS', + 1 => 'Amiga', + 2 => 'OpenVMS', + self::OS_UNIX => 'Unix', + 4 => 'VM/CMS', + 5 => 'Atari ST', + 6 => 'HPFS (OS/2, NT 3.x)', + 7 => 'Macintosh', + 8 => 'Z-System', + 9 => 'CP/M', + 10 => 'Windows NTFS or TOPS-20', + 11 => 'MVS or NTFS', + 12 => 'VSE or SMS/QDOS', + 13 => 'Acorn RISC OS', + 14 => 'VFAT', + 15 => 'alternate MVS', + 16 => 'BeOS', + 17 => 'Tandem', + 18 => 'OS/400', + self::OS_MAC_OSX => 'OS/X (Darwin)', + 30 => 'AtheOS/Syllable', + ]; + + /** + * @param int $platform + * + * @return string + */ + public static function getPlatformName($platform) + { + return isset(self::$platforms[$platform]) ? self::$platforms[$platform] : 'Unknown'; + } +} diff --git a/vendor/nelexa/zip/src/Constants/ZipVersion.php b/vendor/nelexa/zip/src/Constants/ZipVersion.php new file mode 100644 index 0000000..58c090d --- /dev/null +++ b/vendor/nelexa/zip/src/Constants/ZipVersion.php @@ -0,0 +1,81 @@ +expectedCrc = $expected; + $this->actualCrc = $actual; + } + + /** + * Returns expected crc. + * + * @return int + */ + public function getExpectedCrc() + { + return $this->expectedCrc; + } + + /** + * Returns actual crc. + * + * @return int + */ + public function getActualCrc() + { + return $this->actualCrc; + } +} diff --git a/vendor/nelexa/zip/src/Exception/InvalidArgumentException.php b/vendor/nelexa/zip/src/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..24ccc22 --- /dev/null +++ b/vendor/nelexa/zip/src/Exception/InvalidArgumentException.php @@ -0,0 +1,14 @@ +getName() : $entryName; + parent::__construct(sprintf( + 'Zip Entry "%s" was not found in the archive.', + $entryName + )); + $this->entryName = $entryName; + } + + /** + * @return string + */ + public function getEntryName() + { + return $this->entryName; + } +} diff --git a/vendor/nelexa/zip/src/Exception/ZipException.php b/vendor/nelexa/zip/src/Exception/ZipException.php new file mode 100644 index 0000000..e59ec60 --- /dev/null +++ b/vendor/nelexa/zip/src/Exception/ZipException.php @@ -0,0 +1,15 @@ +keys = [ + 305419896, + 591751049, + 878082192, + ]; + + foreach (unpack('C*', $password) as $b) { + $this->updateKeys($b); + } + } + + /** + * @param string $header + * @param int $checkByte + * + * @throws ZipAuthenticationException + */ + public function checkHeader($header, $checkByte) + { + $byte = 0; + + foreach (unpack('C*', $header) as $byte) { + $byte = ($byte ^ $this->decryptByte()) & 0xff; + $this->updateKeys($byte); + } + + if ($byte !== $checkByte) { + throw new ZipAuthenticationException(sprintf('Invalid password')); + } + } + + /** + * @param string $content + * + * @return string + */ + public function decryptString($content) + { + $decryptContent = ''; + + foreach (unpack('C*', $content) as $byte) { + $byte = ($byte ^ $this->decryptByte()) & 0xff; + $this->updateKeys($byte); + $decryptContent .= \chr($byte); + } + + return $decryptContent; + } + + /** + * Decrypt byte. + * + * @return int + */ + private function decryptByte() + { + $temp = $this->keys[2] | 2; + + return (($temp * ($temp ^ 1)) >> 8) & 0xffffff; + } + + /** + * Update keys. + * + * @param int $charAt + */ + private function updateKeys($charAt) + { + $this->keys[0] = $this->crc32($this->keys[0], $charAt); + $this->keys[1] += ($this->keys[0] & 0xff); + $this->keys[1] = PackUtil::toSignedInt32($this->keys[1] * 134775813 + 1); + $this->keys[2] = PackUtil::toSignedInt32($this->crc32($this->keys[2], ($this->keys[1] >> 24) & 0xff)); + } + + /** + * Update crc. + * + * @param int $oldCrc + * @param int $charAt + * + * @return int + */ + private function crc32($oldCrc, $charAt) + { + return (($oldCrc >> 8) & 0xffffff) ^ self::$CRC_TABLE[($oldCrc ^ $charAt) & 0xff]; + } + + /** + * @param string $content + * + * @return string + */ + public function encryptString($content) + { + $encryptContent = ''; + + foreach (unpack('C*', $content) as $val) { + $encryptContent .= pack('c', $this->encryptByte($val)); + } + + return $encryptContent; + } + + /** + * @param int $byte + * + * @return int + */ + private function encryptByte($byte) + { + $tempVal = $byte ^ $this->decryptByte() & 0xff; + $this->updateKeys($byte); + + return $tempVal; + } +} diff --git a/vendor/nelexa/zip/src/IO/Filter/Cipher/Pkware/PKDecryptionStreamFilter.php b/vendor/nelexa/zip/src/IO/Filter/Cipher/Pkware/PKDecryptionStreamFilter.php new file mode 100644 index 0000000..97b1276 --- /dev/null +++ b/vendor/nelexa/zip/src/IO/Filter/Cipher/Pkware/PKDecryptionStreamFilter.php @@ -0,0 +1,118 @@ +params['entry'])) { + return false; + } + + if (!($this->params['entry'] instanceof ZipEntry)) { + throw new \RuntimeException('ZipEntry expected'); + } + /** @var ZipEntry $entry */ + $entry = $this->params['entry']; + $password = $entry->getPassword(); + + if ($password === null) { + return false; + } + + $this->size = $entry->getCompressedSize(); + + // init context + $this->context = new PKCryptContext($password); + + // init check byte + if ($entry->isDataDescriptorEnabled()) { + $this->checkByte = ($entry->getDosTime() >> 8) & 0xff; + } else { + $this->checkByte = ($entry->getCrc() >> 24) & 0xff; + } + + $this->readLength = 0; + $this->readHeader = false; + + return true; + } + + /** + * Decryption filter. + * + * @param resource $in + * @param resource $out + * @param int $consumed + * @param bool $closing + * + * @throws ZipException + * + * @return int + * + * @todo USE FFI in php 7.4 + */ + public function filter($in, $out, &$consumed, $closing) + { + while ($bucket = stream_bucket_make_writeable($in)) { + $buffer = $bucket->data; + $this->readLength += $bucket->datalen; + + if ($this->readLength > $this->size) { + $buffer = substr($buffer, 0, $this->size - $this->readLength); + } + + if (!$this->readHeader) { + $header = substr($buffer, 0, PKCryptContext::STD_DEC_HDR_SIZE); + $this->context->checkHeader($header, $this->checkByte); + + $buffer = substr($buffer, PKCryptContext::STD_DEC_HDR_SIZE); + $this->readHeader = true; + } + + $bucket->data = $this->context->decryptString($buffer); + + $consumed += $bucket->datalen; + stream_bucket_append($out, $bucket); + } + + return \PSFS_PASS_ON; + } +} diff --git a/vendor/nelexa/zip/src/IO/Filter/Cipher/Pkware/PKEncryptionStreamFilter.php b/vendor/nelexa/zip/src/IO/Filter/Cipher/Pkware/PKEncryptionStreamFilter.php new file mode 100644 index 0000000..cd54145 --- /dev/null +++ b/vendor/nelexa/zip/src/IO/Filter/Cipher/Pkware/PKEncryptionStreamFilter.php @@ -0,0 +1,128 @@ +params['entry'], $this->params['size'])) { + return false; + } + + if (!($this->params['entry'] instanceof ZipEntry)) { + throw new \RuntimeException('ZipEntry expected'); + } + /** @var ZipEntry $entry */ + $entry = $this->params['entry']; + $password = $entry->getPassword(); + + if ($password === null) { + return false; + } + + $this->size = (int) $this->params['size']; + + // init keys + $this->context = new PKCryptContext($password); + + $crc = $entry->isDataDescriptorRequired() || $entry->getCrc() === ZipEntry::UNKNOWN ? + ($entry->getDosTime() & 0x0000ffff) << 16 : + $entry->getCrc(); + + try { + $headerBytes = random_bytes(PKCryptContext::STD_DEC_HDR_SIZE); + } catch (\Exception $e) { + throw new \RuntimeException('Oops, our server is bust and cannot generate any random data.', 1, $e); + } + + $headerBytes[PKCryptContext::STD_DEC_HDR_SIZE - 1] = pack('c', ($crc >> 24) & 0xff); + $headerBytes[PKCryptContext::STD_DEC_HDR_SIZE - 2] = pack('c', ($crc >> 16) & 0xff); + + $this->headerBytes = $headerBytes; + $this->writeLength = 0; + $this->writeHeader = false; + + return true; + } + + /** + * Encryption filter. + * + * @param resource $in + * @param resource $out + * @param int $consumed + * @param bool $closing + * + * @return int + * + * @todo USE FFI in php 7.4 + */ + public function filter($in, $out, &$consumed, $closing) + { + while ($bucket = stream_bucket_make_writeable($in)) { + $buffer = $bucket->data; + $this->writeLength += $bucket->datalen; + + if ($this->writeLength > $this->size) { + $buffer = substr($buffer, 0, $this->size - $this->writeLength); + } + + $data = ''; + + if (!$this->writeHeader) { + $data .= $this->context->encryptString($this->headerBytes); + $this->writeHeader = true; + } + + $data .= $this->context->encryptString($buffer); + + $bucket->data = $data; + + $consumed += $bucket->datalen; + stream_bucket_append($out, $bucket); + } + + return \PSFS_PASS_ON; + } +} diff --git a/vendor/nelexa/zip/src/IO/Filter/Cipher/WinZipAes/WinZipAesContext.php b/vendor/nelexa/zip/src/IO/Filter/Cipher/WinZipAes/WinZipAesContext.php new file mode 100644 index 0000000..2691160 --- /dev/null +++ b/vendor/nelexa/zip/src/IO/Filter/Cipher/WinZipAes/WinZipAesContext.php @@ -0,0 +1,166 @@ +iv = str_repeat("\0", self::IV_SIZE); + $keyStrengthBytes = (int) ($encryptionStrengthBits / 8); + $hashLength = $keyStrengthBytes * 2 + self::PASSWORD_VERIFIER_SIZE * 8; + + $hash = hash_pbkdf2( + 'sha1', + $password, + $salt, + self::ITERATION_COUNT, + $hashLength, + true + ); + + $this->key = substr($hash, 0, $keyStrengthBytes); + $sha1Mac = substr($hash, $keyStrengthBytes, $keyStrengthBytes); + $this->hmacContext = hash_init('sha1', \HASH_HMAC, $sha1Mac); + $this->passwordVerifier = substr($hash, 2 * $keyStrengthBytes, self::PASSWORD_VERIFIER_SIZE); + } + + /** + * @return string + */ + public function getPasswordVerifier() + { + return $this->passwordVerifier; + } + + public function updateIv() + { + for ($ivCharIndex = 0; $ivCharIndex < self::IV_SIZE; $ivCharIndex++) { + $ivByte = \ord($this->iv[$ivCharIndex]); + + if (++$ivByte === 256) { + // overflow, set this one to 0, increment next + $this->iv[$ivCharIndex] = "\0"; + } else { + // no overflow, just write incremented number back and abort + $this->iv[$ivCharIndex] = \chr($ivByte); + + break; + } + } + } + + /** + * @param string $data + * + * @return string + */ + public function decryption($data) + { + hash_update($this->hmacContext, $data); + + return CryptoUtil::decryptAesCtr($data, $this->key, $this->iv); + } + + /** + * @param string $data + * + * @return string + */ + public function encrypt($data) + { + $encryptionData = CryptoUtil::encryptAesCtr($data, $this->key, $this->iv); + hash_update($this->hmacContext, $encryptionData); + + return $encryptionData; + } + + /** + * @param string $authCode + * + * @throws ZipAuthenticationException + */ + public function checkAuthCode($authCode) + { + $hmac = $this->getHmac(); + + // check authenticationCode + if (strcmp($hmac, $authCode) !== 0) { + throw new ZipAuthenticationException('Authenticated WinZip AES entry content has been tampered with.'); + } + } + + /** + * @return string + */ + public function getHmac() + { + return substr( + hash_final($this->hmacContext, true), + 0, + self::FOOTER_SIZE + ); + } +} diff --git a/vendor/nelexa/zip/src/IO/Filter/Cipher/WinZipAes/WinZipAesDecryptionStreamFilter.php b/vendor/nelexa/zip/src/IO/Filter/Cipher/WinZipAes/WinZipAesDecryptionStreamFilter.php new file mode 100644 index 0000000..7839172 --- /dev/null +++ b/vendor/nelexa/zip/src/IO/Filter/Cipher/WinZipAes/WinZipAesDecryptionStreamFilter.php @@ -0,0 +1,187 @@ +params['entry'])) { + return false; + } + + if (!($this->params['entry'] instanceof ZipEntry)) { + throw new \RuntimeException('ZipEntry expected'); + } + $this->entry = $this->params['entry']; + + if ( + $this->entry->getPassword() === null || + !$this->entry->isEncrypted() || + !$this->entry->hasExtraField(WinZipAesExtraField::HEADER_ID) + ) { + return false; + } + + $this->buffer = ''; + + return true; + } + + /** + * @param resource $in + * @param resource $out + * @param int $consumed + * @param bool $closing + * + * @throws ZipAuthenticationException + * + * @return int + */ + public function filter($in, $out, &$consumed, $closing) + { + while ($bucket = stream_bucket_make_writeable($in)) { + $this->buffer .= $bucket->data; + $this->readLength += $bucket->datalen; + + if ($this->readLength > $this->entry->getCompressedSize()) { + $this->buffer = substr($this->buffer, 0, $this->entry->getCompressedSize() - $this->readLength); + } + + // read header + if ($this->context === null) { + /** + * @var WinZipAesExtraField|null $winZipExtra + */ + $winZipExtra = $this->entry->getExtraField(WinZipAesExtraField::HEADER_ID); + + if ($winZipExtra === null) { + throw new RuntimeException('$winZipExtra is null'); + } + $saltSize = $winZipExtra->getSaltSize(); + $headerSize = $saltSize + WinZipAesContext::PASSWORD_VERIFIER_SIZE; + + if (\strlen($this->buffer) < $headerSize) { + return \PSFS_FEED_ME; + } + + $salt = substr($this->buffer, 0, $saltSize); + $passwordVerifier = substr($this->buffer, $saltSize, WinZipAesContext::PASSWORD_VERIFIER_SIZE); + $password = $this->entry->getPassword(); + + if ($password === null) { + throw new RuntimeException('$password is null'); + } + $this->context = new WinZipAesContext($winZipExtra->getEncryptionStrength(), $password, $salt); + unset($password); + + // Verify password. + if ($passwordVerifier !== $this->context->getPasswordVerifier()) { + throw new ZipAuthenticationException('Invalid password'); + } + + $this->encBlockPosition = 0; + $this->encBlockLength = $this->entry->getCompressedSize() - $headerSize - WinZipAesContext::FOOTER_SIZE; + + $this->buffer = substr($this->buffer, $headerSize); + } + + // encrypt data + $plainText = ''; + $offset = 0; + $len = \strlen($this->buffer); + $remaining = $this->encBlockLength - $this->encBlockPosition; + + if ($remaining >= WinZipAesContext::BLOCK_SIZE && $len < WinZipAesContext::BLOCK_SIZE) { + return \PSFS_FEED_ME; + } + $limit = min($len, $remaining); + + if ($remaining > $limit && ($limit % WinZipAesContext::BLOCK_SIZE) !== 0) { + $limit -= ($limit % WinZipAesContext::BLOCK_SIZE); + } + + while ($offset < $limit) { + $this->context->updateIv(); + $length = min(WinZipAesContext::BLOCK_SIZE, $limit - $offset); + $data = substr($this->buffer, 0, $length); + $plainText .= $this->context->decryption($data); + $offset += $length; + $this->buffer = substr($this->buffer, $length); + } + $this->encBlockPosition += $offset; + + if ( + $this->encBlockPosition === $this->encBlockLength && + \strlen($this->buffer) === WinZipAesContext::FOOTER_SIZE + ) { + $this->authenticationCode = $this->buffer; + $this->buffer = ''; + } + + $bucket->data = $plainText; + $consumed += $bucket->datalen; + stream_bucket_append($out, $bucket); + } + + return \PSFS_PASS_ON; + } + + /** + * @see http://php.net/manual/en/php-user-filter.onclose.php + * + * @throws ZipAuthenticationException + */ + public function onClose() + { + $this->buffer = ''; + + if ($this->context !== null) { + $this->context->checkAuthCode($this->authenticationCode); + } + } +} diff --git a/vendor/nelexa/zip/src/IO/Filter/Cipher/WinZipAes/WinZipAesEncryptionStreamFilter.php b/vendor/nelexa/zip/src/IO/Filter/Cipher/WinZipAes/WinZipAesEncryptionStreamFilter.php new file mode 100644 index 0000000..12d12fe --- /dev/null +++ b/vendor/nelexa/zip/src/IO/Filter/Cipher/WinZipAes/WinZipAesEncryptionStreamFilter.php @@ -0,0 +1,158 @@ +params['entry'])) { + return false; + } + + if (!($this->params['entry'] instanceof ZipEntry)) { + throw new \RuntimeException('ZipEntry expected'); + } + $this->entry = $this->params['entry']; + + if ( + $this->entry->getPassword() === null || + !$this->entry->isEncrypted() || + !$this->entry->hasExtraField(WinZipAesExtraField::HEADER_ID) + ) { + return false; + } + + $this->size = (int) $this->params['size']; + $this->context = null; + $this->buffer = ''; + + return true; + } + + /** + * @param resource $in + * @param resource $out + * @param int $consumed + * @param bool $closing + * + * @return int + */ + public function filter($in, $out, &$consumed, $closing) + { + while ($bucket = stream_bucket_make_writeable($in)) { + $this->buffer .= $bucket->data; + $this->remaining += $bucket->datalen; + + if ($this->remaining > $this->size) { + $this->buffer = substr($this->buffer, 0, $this->size - $this->remaining); + $this->remaining = $this->size; + } + + $encryptionText = ''; + + // write header + if ($this->context === null) { + /** + * @var WinZipAesExtraField|null $winZipExtra + */ + $winZipExtra = $this->entry->getExtraField(WinZipAesExtraField::HEADER_ID); + + if ($winZipExtra === null) { + throw new RuntimeException('$winZipExtra is null'); + } + $saltSize = $winZipExtra->getSaltSize(); + + try { + $salt = random_bytes($saltSize); + } catch (\Exception $e) { + throw new \RuntimeException('Oops, our server is bust and cannot generate any random data.', 1, $e); + } + $password = $this->entry->getPassword(); + + if ($password === null) { + throw new RuntimeException('$password is null'); + } + $this->context = new WinZipAesContext( + $winZipExtra->getEncryptionStrength(), + $password, + $salt + ); + + $encryptionText .= $salt . $this->context->getPasswordVerifier(); + } + + // encrypt data + $offset = 0; + $len = \strlen($this->buffer); + $remaining = $this->remaining - $this->size; + + if ($remaining >= WinZipAesContext::BLOCK_SIZE && $len < WinZipAesContext::BLOCK_SIZE) { + return \PSFS_FEED_ME; + } + $limit = max($len, $remaining); + + if ($remaining > $limit && ($limit % WinZipAesContext::BLOCK_SIZE) !== 0) { + $limit -= ($limit % WinZipAesContext::BLOCK_SIZE); + } + + while ($offset < $limit) { + $this->context->updateIv(); + $length = min(WinZipAesContext::BLOCK_SIZE, $limit - $offset); + $encryptionText .= $this->context->encrypt( + substr($this->buffer, 0, $length) + ); + $offset += $length; + $this->buffer = substr($this->buffer, $length); + } + + if ($remaining === 0) { + $encryptionText .= $this->context->getHmac(); + } + + $bucket->data = $encryptionText; + $consumed += $bucket->datalen; + + stream_bucket_append($out, $bucket); + } + + return \PSFS_PASS_ON; + } +} diff --git a/vendor/nelexa/zip/src/IO/Stream/ResponseStream.php b/vendor/nelexa/zip/src/IO/Stream/ResponseStream.php new file mode 100644 index 0000000..e016103 --- /dev/null +++ b/vendor/nelexa/zip/src/IO/Stream/ResponseStream.php @@ -0,0 +1,338 @@ + [ + 'r' => true, + 'w+' => true, + 'r+' => true, + 'x+' => true, + 'c+' => true, + 'rb' => true, + 'w+b' => true, + 'r+b' => true, + 'x+b' => true, + 'c+b' => true, + 'rt' => true, + 'w+t' => true, + 'r+t' => true, + 'x+t' => true, + 'c+t' => true, + 'a+' => true, + ], + 'write' => [ + 'w' => true, + 'w+' => true, + 'rw' => true, + 'r+' => true, + 'x+' => true, + 'c+' => true, + 'wb' => true, + 'w+b' => true, + 'r+b' => true, + 'x+b' => true, + 'c+b' => true, + 'w+t' => true, + 'r+t' => true, + 'x+t' => true, + 'c+t' => true, + 'a' => true, + 'a+' => true, + ], + ]; + + /** @var resource */ + private $stream; + + /** @var int|null */ + private $size; + + /** @var bool */ + private $seekable; + + /** @var bool */ + private $readable; + + /** @var bool */ + private $writable; + + /** @var string|null */ + private $uri; + + /** + * @param resource $stream stream resource to wrap + * + * @throws \InvalidArgumentException if the stream is not a stream resource + */ + public function __construct($stream) + { + if (!\is_resource($stream)) { + throw new \InvalidArgumentException('Stream must be a resource'); + } + $this->stream = $stream; + $meta = stream_get_meta_data($this->stream); + $this->seekable = $meta['seekable']; + $this->readable = isset(self::$readWriteHash['read'][$meta['mode']]); + $this->writable = isset(self::$readWriteHash['write'][$meta['mode']]); + $this->uri = $this->getMetadata('uri'); + } + + /** + * 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. + * + * @see 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) + { + if (!$this->stream) { + return $key ? null : []; + } + $meta = stream_get_meta_data($this->stream); + + return isset($meta[$key]) ? $meta[$key] : null; + } + + /** + * 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() + { + if (!$this->stream) { + return ''; + } + $this->rewind(); + + return (string) stream_get_contents($this->stream); + } + + /** + * 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). + * + * @throws \RuntimeException on failure + * + * @see http://www.php.net/manual/en/function.fseek.php + * @see seek() + */ + public function rewind() + { + $this->seekable && rewind($this->stream); + } + + /** + * 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() + { + if ($this->size !== null) { + return $this->size; + } + + if (!$this->stream) { + return null; + } + // Clear the stat cache if the stream has a URI + if ($this->uri !== null) { + clearstatcache(true, $this->uri); + } + $stats = fstat($this->stream); + + if (isset($stats['size'])) { + $this->size = $stats['size']; + + return $this->size; + } + + return null; + } + + /** + * Returns the current position of the file read/write pointer. + * + * @throws \RuntimeException on error + * + * @return int Position of the file pointer + */ + public function tell() + { + return $this->stream ? ftell($this->stream) : false; + } + + /** + * Returns true if the stream is at the end of the stream. + * + * @return bool + */ + public function eof() + { + return !$this->stream || feof($this->stream); + } + + /** + * Returns whether or not the stream is seekable. + * + * @return bool + */ + public function isSeekable() + { + return $this->seekable; + } + + /** + * Seek to a position in the stream. + * + * @see 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) + { + $this->seekable && fseek($this->stream, $offset, $whence); + } + + /** + * Returns whether or not the stream is writable. + * + * @return bool + */ + public function isWritable() + { + return $this->writable; + } + + /** + * Write data to the stream. + * + * @param string $string the string that is to be written + * + * @throws \RuntimeException on failure + * + * @return int returns the number of bytes written to the stream + */ + public function write($string) + { + $this->size = null; + + return $this->writable ? fwrite($this->stream, $string) : false; + } + + /** + * Returns whether or not the stream is readable. + * + * @return bool + */ + public function isReadable() + { + return $this->readable; + } + + /** + * 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. + * + * @throws \RuntimeException if an error occurs + * + * @return string returns the data read from the stream, or an empty string + * if no bytes are available + */ + public function read($length) + { + return $this->readable ? fread($this->stream, $length) : ''; + } + + /** + * Returns the remaining contents in a string. + * + * @throws \RuntimeException if unable to read or an error occurs while + * reading + * + * @return string + */ + public function getContents() + { + return $this->stream ? stream_get_contents($this->stream) : ''; + } + + /** + * Closes the stream when the destructed. + */ + public function __destruct() + { + $this->close(); + } + + /** + * Closes the stream and any underlying resources. + */ + public function close() + { + if (\is_resource($this->stream)) { + fclose($this->stream); + } + $this->detach(); + } + + /** + * 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() + { + $result = $this->stream; + $this->stream = null; + $this->size = null; + $this->uri = null; + $this->readable = false; + $this->writable = false; + $this->seekable = false; + + return $result; + } +} diff --git a/vendor/nelexa/zip/src/IO/Stream/ZipEntryStreamWrapper.php b/vendor/nelexa/zip/src/IO/Stream/ZipEntryStreamWrapper.php new file mode 100644 index 0000000..aa13f07 --- /dev/null +++ b/vendor/nelexa/zip/src/IO/Stream/ZipEntryStreamWrapper.php @@ -0,0 +1,309 @@ + [ + 'entry' => $entry, + ], + ] + ); + + $uri = self::PROTOCOL . '://' . $entry->getName(); + $fp = fopen($uri, 'r+b', false, $context); + + if ($fp === false) { + throw new \RuntimeException('Error open ' . $uri); + } + + return $fp; + } + + /** + * Opens file or URL. + * + * This method is called immediately after the wrapper is + * initialized (f.e. by {@see fopen()} and {@see file_get_contents()}). + * + * @param string $path specifies the URL that was passed to + * the original function + * @param string $mode the mode used to open the file, as detailed + * for {@see fopen()} + * @param int $options Holds additional flags set by the streams + * API. It can hold one or more of the + * following values OR'd together. + * @param string $opened_path if the path is opened successfully, and + * STREAM_USE_PATH is set in options, + * opened_path should be set to the + * full path of the file/resource that + * was actually opened + * + * @throws ZipException + * + * @return bool + * + * @see https://www.php.net/streamwrapper.stream-open + */ + public function stream_open($path, $mode, $options, &$opened_path) + { + if ($this->context === null) { + throw new \RuntimeException('stream context is null'); + } + $streamOptions = stream_context_get_options($this->context); + + if (!isset($streamOptions[self::PROTOCOL]['entry'])) { + throw new \RuntimeException('no stream option ["' . self::PROTOCOL . '"]["entry"]'); + } + $zipEntry = $streamOptions[self::PROTOCOL]['entry']; + + if (!$zipEntry instanceof ZipEntry) { + throw new \RuntimeException('invalid stream context'); + } + + $zipData = $zipEntry->getData(); + + if ($zipData === null) { + throw new ZipException(sprintf('No data for zip entry "%s"', $zipEntry->getName())); + } + $this->fp = $zipData->getDataAsStream(); + + return $this->fp !== false; + } + + /** + * Read from stream. + * + * This method is called in response to {@see fread()} and {@see fgets()}. + * + * Note: Remember to update the read/write position of the stream + * (by the number of bytes that were successfully read). + * + * @param int $count how many bytes of data from the current + * position should be returned + * + * @return false|string If there are less than count bytes available, + * return as many as are available. If no more data + * is available, return either FALSE or + * an empty string. + * + * @see https://www.php.net/streamwrapper.stream-read + */ + public function stream_read($count) + { + return fread($this->fp, $count); + } + + /** + * Seeks to specific location in a stream. + * + * This method is called in response to {@see fseek()}. + * The read/write position of the stream should be updated according + * to the offset and whence. + * + * @param int $offset the stream offset to seek to + * @param int $whence Possible values: + * {@see \SEEK_SET} - Set position equal to offset bytes. + * {@see \SEEK_CUR} - Set position to current location plus offset. + * {@see \SEEK_END} - Set position to end-of-file plus offset. + * + * @return bool return TRUE if the position was updated, FALSE otherwise + * + * @see https://www.php.net/streamwrapper.stream-seek + */ + public function stream_seek($offset, $whence = \SEEK_SET) + { + return fseek($this->fp, $offset, $whence) === 0; + } + + /** + * Retrieve the current position of a stream. + * + * This method is called in response to {@see fseek()} to determine + * the current position. + * + * @return int should return the current position of the stream + * + * @see https://www.php.net/streamwrapper.stream-tell + */ + public function stream_tell() + { + $pos = ftell($this->fp); + + if ($pos === false) { + throw new \RuntimeException('Cannot get stream position.'); + } + + return $pos; + } + + /** + * Tests for end-of-file on a file pointer. + * + * This method is called in response to {@see feof()}. + * + * @return bool should return TRUE if the read/write position is at + * the end of the stream and if no more data is available + * to be read, or FALSE otherwise + * + * @see https://www.php.net/streamwrapper.stream-eof + */ + public function stream_eof() + { + return feof($this->fp); + } + + /** + * Retrieve information about a file resource. + * + * This method is called in response to {@see fstat()}. + * + * @return array + * + * @see https://www.php.net/streamwrapper.stream-stat + * @see https://www.php.net/stat + * @see https://www.php.net/fstat + */ + public function stream_stat() + { + return fstat($this->fp); + } + + /** + * Flushes the output. + * + * This method is called in response to {@see fflush()} and when the + * stream is being closed while any unflushed data has been written to + * it before. + * If you have cached data in your stream but not yet stored it into + * the underlying storage, you should do so now. + * + * @return bool should return TRUE if the cached data was successfully + * stored (or if there was no data to store), or FALSE + * if the data could not be stored + * + * @see https://www.php.net/streamwrapper.stream-flush + */ + public function stream_flush() + { + return fflush($this->fp); + } + + /** + * Truncate stream. + * + * Will respond to truncation, e.g., through {@see ftruncate()}. + * + * @param int $new_size the new size + * + * @return bool returns TRUE on success or FALSE on failure + * + * @see https://www.php.net/streamwrapper.stream-truncate + */ + public function stream_truncate($new_size) + { + return ftruncate($this->fp, (int) $new_size); + } + + /** + * Write to stream. + * + * This method is called in response to {@see fwrite().} + * + * Note: Remember to update the current position of the stream by + * number of bytes that were successfully written. + * + * @param string $data should be stored into the underlying stream + * + * @return int should return the number of bytes that were successfully stored, or 0 if none could be stored + * + * @see https://www.php.net/streamwrapper.stream-write + */ + public function stream_write($data) + { + $bytes = fwrite($this->fp, $data); + + return $bytes === false ? 0 : $bytes; + } + + /** + * Retrieve the underlaying resource. + * + * This method is called in response to {@see stream_select()}. + * + * @param int $cast_as can be {@see STREAM_CAST_FOR_SELECT} when {@see stream_select()} + * is callingstream_cast() or {@see STREAM_CAST_AS_STREAM} when + * stream_cast() is called for other uses + * + * @return resource + */ + public function stream_cast($cast_as) + { + return $this->fp; + } + + /** + * Close a resource. + * + * This method is called in response to {@see fclose()}. + * All resources that were locked, or allocated, by the wrapper should be released. + * + * @see https://www.php.net/streamwrapper.stream-close + */ + public function stream_close() + { + } +} diff --git a/vendor/nelexa/zip/src/IO/ZipReader.php b/vendor/nelexa/zip/src/IO/ZipReader.php new file mode 100644 index 0000000..f445bf8 --- /dev/null +++ b/vendor/nelexa/zip/src/IO/ZipReader.php @@ -0,0 +1,898 @@ +size = fstat($inStream)['size']; + $this->inStream = $inStream; + + /** @noinspection AdditionOperationOnArraysInspection */ + $options += $this->getDefaultOptions(); + $this->options = $options; + } + + /** + * @return array + */ + protected function getDefaultOptions() + { + return [ + ZipOptions::CHARSET => null, + ]; + } + + /** + * @throws ZipException + * + * @return ImmutableZipContainer + */ + public function read() + { + if ($this->size < ZipConstants::END_CD_MIN_LEN) { + throw new ZipException('Corrupt zip file'); + } + + $endOfCentralDirectory = $this->readEndOfCentralDirectory(); + $entries = $this->readCentralDirectory($endOfCentralDirectory); + + return new ImmutableZipContainer($entries, $endOfCentralDirectory->getComment()); + } + + /** + * @return array + */ + public function getStreamMetaData() + { + return stream_get_meta_data($this->inStream); + } + + /** + * Read End of central directory record. + * + * end of central dir signature 4 bytes (0x06054b50) + * number of this disk 2 bytes + * number of the disk with the + * start of the central directory 2 bytes + * total number of entries in the + * central directory on this disk 2 bytes + * total number of entries in + * the central directory 2 bytes + * size of the central directory 4 bytes + * offset of start of central + * directory with respect to + * the starting disk number 4 bytes + * .ZIP file comment length 2 bytes + * .ZIP file comment (variable size) + * + * @throws ZipException + * + * @return EndOfCentralDirectory + */ + protected function readEndOfCentralDirectory() + { + if (!$this->findEndOfCentralDirectory()) { + throw new ZipException('Invalid zip file. The end of the central directory could not be found.'); + } + + $positionECD = ftell($this->inStream) - 4; + $sizeECD = $this->size - ftell($this->inStream); + $buffer = fread($this->inStream, $sizeECD); + + $unpack = unpack( + 'vdiskNo/vcdDiskNo/vcdEntriesDisk/' . + 'vcdEntries/VcdSize/VcdPos/vcommentLength', + substr($buffer, 0, 18) + ); + + if ( + $unpack['diskNo'] !== 0 || + $unpack['cdDiskNo'] !== 0 || + $unpack['cdEntriesDisk'] !== $unpack['cdEntries'] + ) { + throw new ZipException( + 'ZIP file spanning/splitting is not supported!' + ); + } + // .ZIP file comment (variable sizeECD) + $comment = null; + + if ($unpack['commentLength'] > 0) { + $comment = substr($buffer, 18, $unpack['commentLength']); + } + + // Check for ZIP64 End Of Central Directory Locator exists. + $zip64ECDLocatorPosition = $positionECD - ZipConstants::ZIP64_END_CD_LOC_LEN; + fseek($this->inStream, $zip64ECDLocatorPosition); + // zip64 end of central dir locator + // signature 4 bytes (0x07064b50) + if ($zip64ECDLocatorPosition > 0 && unpack( + 'V', + fread($this->inStream, 4) + )[1] === ZipConstants::ZIP64_END_CD_LOC) { + if (!$this->isZip64Support()) { + throw new ZipException('ZIP64 not supported this archive.'); + } + + $positionECD = $this->findZip64ECDPosition(); + $endCentralDirectory = $this->readZip64EndOfCentralDirectory($positionECD); + $endCentralDirectory->setComment($comment); + } else { + $endCentralDirectory = new EndOfCentralDirectory( + $unpack['cdEntries'], + $unpack['cdPos'], + $unpack['cdSize'], + false, + $comment + ); + } + + return $endCentralDirectory; + } + + /** + * @return bool + */ + protected function findEndOfCentralDirectory() + { + $max = $this->size - ZipConstants::END_CD_MIN_LEN; + $min = $max >= 0xffff ? $max - 0xffff : 0; + // Search for End of central directory record. + for ($position = $max; $position >= $min; $position--) { + fseek($this->inStream, $position); + // end of central dir signature 4 bytes (0x06054b50) + if (unpack('V', fread($this->inStream, 4))[1] !== ZipConstants::END_CD) { + continue; + } + + return true; + } + + return false; + } + + /** + * Read Zip64 end of central directory locator and returns + * Zip64 end of central directory position. + * + * number of the disk with the + * start of the zip64 end of + * central directory 4 bytes + * relative offset of the zip64 + * end of central directory record 8 bytes + * total number of disks 4 bytes + * + * @throws ZipException + * + * @return int Zip64 End Of Central Directory position + */ + protected function findZip64ECDPosition() + { + $diskNo = unpack('V', fread($this->inStream, 4))[1]; + $zip64ECDPos = PackUtil::unpackLongLE(fread($this->inStream, 8)); + $totalDisks = unpack('V', fread($this->inStream, 4))[1]; + + if ($diskNo !== 0 || $totalDisks > 1) { + throw new ZipException('ZIP file spanning/splitting is not supported!'); + } + + return $zip64ECDPos; + } + + /** + * Read zip64 end of central directory locator and zip64 end + * of central directory record. + * + * zip64 end of central dir + * signature 4 bytes (0x06064b50) + * size of zip64 end of central + * directory record 8 bytes + * version made by 2 bytes + * version needed to extract 2 bytes + * number of this disk 4 bytes + * number of the disk with the + * start of the central directory 4 bytes + * total number of entries in the + * central directory on this disk 8 bytes + * total number of entries in the + * central directory 8 bytes + * size of the central directory 8 bytes + * offset of start of central + * directory with respect to + * the starting disk number 8 bytes + * zip64 extensible data sector (variable size) + * + * @param int $zip64ECDPosition + * + * @throws ZipException + * + * @return EndOfCentralDirectory + */ + protected function readZip64EndOfCentralDirectory($zip64ECDPosition) + { + fseek($this->inStream, $zip64ECDPosition); + + $buffer = fread($this->inStream, ZipConstants::ZIP64_END_OF_CD_LEN); + + if (unpack('V', $buffer)[1] !== ZipConstants::ZIP64_END_CD) { + throw new ZipException('Expected ZIP64 End Of Central Directory Record!'); + } + + $data = unpack( +// 'Psize/vversionMadeBy/vextractVersion/' . + 'VdiskNo/VcdDiskNo', + substr($buffer, 16, 8) + ); + + $cdEntriesDisk = PackUtil::unpackLongLE(substr($buffer, 24, 8)); + $entryCount = PackUtil::unpackLongLE(substr($buffer, 32, 8)); + $cdSize = PackUtil::unpackLongLE(substr($buffer, 40, 8)); + $cdPos = PackUtil::unpackLongLE(substr($buffer, 48, 8)); + +// $platform = ZipPlatform::fromValue(($data['versionMadeBy'] & 0xFF00) >> 8); +// $softwareVersion = $data['versionMadeBy'] & 0x00FF; + + if ($data['diskNo'] !== 0 || $data['cdDiskNo'] !== 0 || $entryCount !== $cdEntriesDisk) { + throw new ZipException('ZIP file spanning/splitting is not supported!'); + } + + if ($entryCount < 0 || $entryCount > 0x7fffffff) { + throw new ZipException('Total Number Of Entries In The Central Directory out of range!'); + } + + // skip zip64 extensible data sector (variable sizeEndCD) + + return new EndOfCentralDirectory( + $entryCount, + $cdPos, + $cdSize, + true + ); + } + + /** + * Reads the central directory from the given seekable byte channel + * and populates the internal tables with ZipEntry instances. + * + * The ZipEntry's will know all data that can be obtained from the + * central directory alone, but not the data that requires the local + * file header or additional data to be read. + * + * @param EndOfCentralDirectory $endCD + * + * @throws ZipException + * + * @return ZipEntry[] + */ + protected function readCentralDirectory(EndOfCentralDirectory $endCD) + { + $entries = []; + + $cdOffset = $endCD->getCdOffset(); + fseek($this->inStream, $cdOffset); + + if (!($cdStream = fopen('php://temp', 'w+b'))) { + // @codeCoverageIgnoreStart + throw new ZipException('A temporary resource cannot be opened for writing.'); + // @codeCoverageIgnoreEnd + } + stream_copy_to_stream($this->inStream, $cdStream, $endCD->getCdSize()); + rewind($cdStream); + for ($numEntries = $endCD->getEntryCount(); $numEntries > 0; $numEntries--) { + $zipEntry = $this->readZipEntry($cdStream); + + $entryName = $zipEntry->getName(); + + /** @var UnicodePathExtraField|null $unicodePathExtraField */ + $unicodePathExtraField = $zipEntry->getExtraField(UnicodePathExtraField::HEADER_ID); + + if ($unicodePathExtraField !== null && $unicodePathExtraField->getCrc32() === crc32($entryName)) { + $unicodePath = $unicodePathExtraField->getUnicodeValue(); + + if ($unicodePath !== null) { + $unicodePath = str_replace('\\', '/', $unicodePath); + + if ( + $unicodePath !== '' && + substr_count($entryName, '/') === substr_count($unicodePath, '/') + ) { + $entryName = $unicodePath; + } + } + } + + $entries[$entryName] = $zipEntry; + } + + return $entries; + } + + /** + * Read central directory entry. + * + * central file header signature 4 bytes (0x02014b50) + * version made by 2 bytes + * version needed to extract 2 bytes + * general purpose bit flag 2 bytes + * compression method 2 bytes + * last mod file time 2 bytes + * last mod file date 2 bytes + * crc-32 4 bytes + * compressed size 4 bytes + * uncompressed size 4 bytes + * file name length 2 bytes + * extra field length 2 bytes + * file comment length 2 bytes + * disk number start 2 bytes + * internal file attributes 2 bytes + * external file attributes 4 bytes + * relative offset of local header 4 bytes + * + * file name (variable size) + * extra field (variable size) + * file comment (variable size) + * + * @param resource $stream + * + * @throws ZipException + * + * @return ZipEntry + */ + protected function readZipEntry($stream) + { + if (unpack('V', fread($stream, 4))[1] !== ZipConstants::CENTRAL_FILE_HEADER) { + throw new ZipException('Corrupt zip file. Cannot read zip entry.'); + } + + $unpack = unpack( + 'vversionMadeBy/vversionNeededToExtract/' . + 'vgeneralPurposeBitFlag/vcompressionMethod/' . + 'VlastModFile/Vcrc/VcompressedSize/' . + 'VuncompressedSize/vfileNameLength/vextraFieldLength/' . + 'vfileCommentLength/vdiskNumberStart/vinternalFileAttributes/' . + 'VexternalFileAttributes/VoffsetLocalHeader', + fread($stream, 42) + ); + + if ($unpack['diskNumberStart'] !== 0) { + throw new ZipException('ZIP file spanning/splitting is not supported!'); + } + + $generalPurposeBitFlags = $unpack['generalPurposeBitFlag']; + $isUtf8 = ($generalPurposeBitFlags & GeneralPurposeBitFlag::UTF8) !== 0; + + $name = fread($stream, $unpack['fileNameLength']); + + $createdOS = ($unpack['versionMadeBy'] & 0xFF00) >> 8; + $softwareVersion = $unpack['versionMadeBy'] & 0x00FF; + + $extractedOS = ($unpack['versionNeededToExtract'] & 0xFF00) >> 8; + $extractVersion = $unpack['versionNeededToExtract'] & 0x00FF; + + $dosTime = $unpack['lastModFile']; + + $comment = null; + + if ($unpack['fileCommentLength'] > 0) { + $comment = fread($stream, $unpack['fileCommentLength']); + } + + // decode code page names + $fallbackCharset = null; + + if (!$isUtf8 && isset($this->options[ZipOptions::CHARSET])) { + $charset = $this->options[ZipOptions::CHARSET]; + + $fallbackCharset = $charset; + $name = DosCodePage::toUTF8($name, $charset); + + if ($comment !== null) { + $comment = DosCodePage::toUTF8($comment, $charset); + } + } + + $zipEntry = ZipEntry::create( + $name, + $createdOS, + $extractedOS, + $softwareVersion, + $extractVersion, + $unpack['compressionMethod'], + $generalPurposeBitFlags, + $dosTime, + $unpack['crc'], + $unpack['compressedSize'], + $unpack['uncompressedSize'], + $unpack['internalFileAttributes'], + $unpack['externalFileAttributes'], + $unpack['offsetLocalHeader'], + $comment, + $fallbackCharset + ); + + if ($unpack['extraFieldLength'] > 0) { + $this->parseExtraFields( + fread($stream, $unpack['extraFieldLength']), + $zipEntry, + false + ); + + /** @var Zip64ExtraField|null $extraZip64 */ + $extraZip64 = $zipEntry->getCdExtraField(Zip64ExtraField::HEADER_ID); + + if ($extraZip64 !== null) { + $this->handleZip64Extra($extraZip64, $zipEntry); + } + } + + $this->loadLocalExtraFields($zipEntry); + $this->handleExtraEncryptionFields($zipEntry); + $this->handleExtraFields($zipEntry); + + return $zipEntry; + } + + /** + * @param string $buffer + * @param ZipEntry $zipEntry + * @param bool $local + * + * @return ExtraFieldsCollection + */ + protected function parseExtraFields($buffer, ZipEntry $zipEntry, $local = false) + { + $collection = $local ? + $zipEntry->getLocalExtraFields() : + $zipEntry->getCdExtraFields(); + + if (!empty($buffer)) { + $pos = 0; + $endPos = \strlen($buffer); + + while ($endPos - $pos >= 4) { + /** @var int[] $data */ + $data = unpack('vheaderId/vdataSize', substr($buffer, $pos, 4)); + $pos += 4; + + if ($endPos - $pos - $data['dataSize'] < 0) { + break; + } + $bufferData = substr($buffer, $pos, $data['dataSize']); + $headerId = $data['headerId']; + + /** @var string|ZipExtraField|null $className */ + $className = ZipExtraDriver::getClassNameOrNull($headerId); + + try { + if ($className !== null) { + try { + $extraField = $local ? + \call_user_func([$className, 'unpackLocalFileData'], $bufferData, $zipEntry) : + \call_user_func([$className, 'unpackCentralDirData'], $bufferData, $zipEntry); + } catch (\Throwable $e) { + // skip errors while parsing invalid data + continue; + } + } else { + $extraField = new UnrecognizedExtraField($headerId, $bufferData); + } + $collection->add($extraField); + } finally { + $pos += $data['dataSize']; + } + } + } + + return $collection; + } + + /** + * @param Zip64ExtraField $extraZip64 + * @param ZipEntry $zipEntry + */ + protected function handleZip64Extra(Zip64ExtraField $extraZip64, ZipEntry $zipEntry) + { + $uncompressedSize = $extraZip64->getUncompressedSize(); + $compressedSize = $extraZip64->getCompressedSize(); + $localHeaderOffset = $extraZip64->getLocalHeaderOffset(); + + if ($uncompressedSize !== null) { + $zipEntry->setUncompressedSize($uncompressedSize); + } + + if ($compressedSize !== null) { + $zipEntry->setCompressedSize($compressedSize); + } + + if ($localHeaderOffset !== null) { + $zipEntry->setLocalHeaderOffset($localHeaderOffset); + } + } + + /** + * Read Local File Header. + * + * local file header signature 4 bytes (0x04034b50) + * version needed to extract 2 bytes + * general purpose bit flag 2 bytes + * compression method 2 bytes + * last mod file time 2 bytes + * last mod file date 2 bytes + * crc-32 4 bytes + * compressed size 4 bytes + * uncompressed size 4 bytes + * file name length 2 bytes + * extra field length 2 bytes + * file name (variable size) + * extra field (variable size) + * + * @param ZipEntry $entry + * + * @throws ZipException + */ + protected function loadLocalExtraFields(ZipEntry $entry) + { + $offsetLocalHeader = $entry->getLocalHeaderOffset(); + + fseek($this->inStream, $offsetLocalHeader); + + if (unpack('V', fread($this->inStream, 4))[1] !== ZipConstants::LOCAL_FILE_HEADER) { + throw new ZipException(sprintf('%s (expected Local File Header)', $entry->getName())); + } + + fseek($this->inStream, $offsetLocalHeader + ZipConstants::LFH_FILENAME_LENGTH_POS); + $unpack = unpack('vfileNameLength/vextraFieldLength', fread($this->inStream, 4)); + $offsetData = ftell($this->inStream) + + $unpack['fileNameLength'] + + $unpack['extraFieldLength']; + + fseek($this->inStream, $unpack['fileNameLength'], \SEEK_CUR); + + if ($unpack['extraFieldLength'] > 0) { + $this->parseExtraFields( + fread($this->inStream, $unpack['extraFieldLength']), + $entry, + true + ); + } + + $zipData = new ZipSourceFileData($this, $entry, $offsetData); + $entry->setData($zipData); + } + + /** + * @param ZipEntry $zipEntry + * + * @throws ZipException + */ + private function handleExtraEncryptionFields(ZipEntry $zipEntry) + { + if ($zipEntry->isEncrypted()) { + if ($zipEntry->getCompressionMethod() === ZipCompressionMethod::WINZIP_AES) { + /** @var WinZipAesExtraField|null $extraField */ + $extraField = $zipEntry->getExtraField(WinZipAesExtraField::HEADER_ID); + + if ($extraField === null) { + throw new ZipException( + sprintf( + 'Extra field 0x%04x (WinZip-AES Encryption) expected for compression method %d', + WinZipAesExtraField::HEADER_ID, + $zipEntry->getCompressionMethod() + ) + ); + } + $zipEntry->setCompressionMethod($extraField->getCompressionMethod()); + $zipEntry->setEncryptionMethod($extraField->getEncryptionMethod()); + } else { + $zipEntry->setEncryptionMethod(ZipEncryptionMethod::PKWARE); + } + } + } + + /** + * Handle extra data in zip records. + * + * This is a special method in which you can process ExtraField + * and make changes to ZipEntry. + * + * @param ZipEntry $zipEntry + */ + protected function handleExtraFields(ZipEntry $zipEntry) + { + } + + /** + * @param ZipSourceFileData $zipFileData + * + * @throws ZipException + * @throws Crc32Exception + * + * @return resource + */ + public function getEntryStream(ZipSourceFileData $zipFileData) + { + $outStream = fopen('php://temp', 'w+b'); + $this->copyUncompressedDataToStream($zipFileData, $outStream); + rewind($outStream); + + return $outStream; + } + + /** + * @param ZipSourceFileData $zipFileData + * @param resource $outStream + * + * @throws Crc32Exception + * @throws ZipException + */ + public function copyUncompressedDataToStream(ZipSourceFileData $zipFileData, $outStream) + { + if (!\is_resource($outStream)) { + throw new InvalidArgumentException('outStream is not resource'); + } + + $entry = $zipFileData->getSourceEntry(); + +// if ($entry->isDirectory()) { +// throw new InvalidArgumentException('Streams not supported for directories'); +// } + + if ($entry->isStrongEncryption()) { + throw new ZipException('Not support encryption zip.'); + } + + $compressionMethod = $entry->getCompressionMethod(); + + fseek($this->inStream, $zipFileData->getOffset()); + + $filters = []; + + $skipCheckCrc = false; + $isEncrypted = $entry->isEncrypted(); + + if ($isEncrypted) { + if ($entry->getPassword() === null) { + throw new ZipException('Can not password from entry ' . $entry->getName()); + } + + if (ZipEncryptionMethod::isWinZipAesMethod($entry->getEncryptionMethod())) { + /** @var WinZipAesExtraField|null $winZipAesExtra */ + $winZipAesExtra = $entry->getExtraField(WinZipAesExtraField::HEADER_ID); + + if ($winZipAesExtra === null) { + throw new ZipException( + sprintf('WinZip AES must contain the extra field %s', WinZipAesExtraField::HEADER_ID) + ); + } + $compressionMethod = $winZipAesExtra->getCompressionMethod(); + + WinZipAesDecryptionStreamFilter::register(); + $cipherFilterName = WinZipAesDecryptionStreamFilter::FILTER_NAME; + + if ($winZipAesExtra->isV2()) { + $skipCheckCrc = true; + } + } else { + PKDecryptionStreamFilter::register(); + $cipherFilterName = PKDecryptionStreamFilter::FILTER_NAME; + } + $encContextFilter = stream_filter_append( + $this->inStream, + $cipherFilterName, + \STREAM_FILTER_READ, + [ + 'entry' => $entry, + ] + ); + + if (!$encContextFilter) { + throw new \RuntimeException('Not apply filter ' . $cipherFilterName); + } + $filters[] = $encContextFilter; + } + + // hack, see https://groups.google.com/forum/#!topic/alt.comp.lang.php/37_JZeW63uc + $pos = ftell($this->inStream); + rewind($this->inStream); + fseek($this->inStream, $pos); + + $contextDecompress = null; + switch ($compressionMethod) { + case ZipCompressionMethod::STORED: + // file without compression, do nothing + break; + + case ZipCompressionMethod::DEFLATED: + if (!($contextDecompress = stream_filter_append( + $this->inStream, + 'zlib.inflate', + \STREAM_FILTER_READ + ))) { + throw new \RuntimeException('Could not append filter "zlib.inflate" to stream'); + } + $filters[] = $contextDecompress; + + break; + + case ZipCompressionMethod::BZIP2: + if (!($contextDecompress = stream_filter_append( + $this->inStream, + 'bzip2.decompress', + \STREAM_FILTER_READ + ))) { + throw new \RuntimeException('Could not append filter "bzip2.decompress" to stream'); + } + $filters[] = $contextDecompress; + + break; + + default: + throw new ZipException( + sprintf( + '%s (compression method %d (%s) is not supported)', + $entry->getName(), + $compressionMethod, + ZipCompressionMethod::getCompressionMethodName($compressionMethod) + ) + ); + } + + $limit = $zipFileData->getUncompressedSize(); + + $offset = 0; + $chunkSize = 8192; + + try { + if ($skipCheckCrc) { + while ($offset < $limit) { + $length = min($chunkSize, $limit - $offset); + $buffer = fread($this->inStream, $length); + + if ($buffer === false) { + throw new ZipException(sprintf('Error reading the contents of entry "%s".', $entry->getName())); + } + fwrite($outStream, $buffer); + $offset += $length; + } + } else { + $contextHash = hash_init('crc32b'); + + while ($offset < $limit) { + $length = min($chunkSize, $limit - $offset); + $buffer = fread($this->inStream, $length); + + if ($buffer === false) { + throw new ZipException(sprintf('Error reading the contents of entry "%s".', $entry->getName())); + } + fwrite($outStream, $buffer); + hash_update($contextHash, $buffer); + $offset += $length; + } + + $expectedCrc = (int) hexdec(hash_final($contextHash)); + + if ($expectedCrc !== $entry->getCrc()) { + throw new Crc32Exception($entry->getName(), $expectedCrc, $entry->getCrc()); + } + } + } finally { + for ($i = \count($filters); $i > 0; $i--) { + stream_filter_remove($filters[$i - 1]); + } + } + } + + /** + * @param ZipSourceFileData $zipData + * @param resource $outStream + */ + public function copyCompressedDataToStream(ZipSourceFileData $zipData, $outStream) + { + if ($zipData->getCompressedSize() > 0) { + fseek($this->inStream, $zipData->getOffset()); + stream_copy_to_stream($this->inStream, $outStream, $zipData->getCompressedSize()); + } + } + + /** + * @return bool + */ + protected function isZip64Support() + { + return \PHP_INT_SIZE === 8; // true for 64bit system + } + + public function close() + { + if (\is_resource($this->inStream)) { + fclose($this->inStream); + } + } + + public function __destruct() + { + $this->close(); + } +} diff --git a/vendor/nelexa/zip/src/IO/ZipWriter.php b/vendor/nelexa/zip/src/IO/ZipWriter.php new file mode 100644 index 0000000..b5cc8a8 --- /dev/null +++ b/vendor/nelexa/zip/src/IO/ZipWriter.php @@ -0,0 +1,886 @@ +zipContainer = clone $container; + } + + /** + * @param resource $outStream + * + * @throws ZipException + */ + public function write($outStream) + { + if (!\is_resource($outStream)) { + throw new \InvalidArgumentException('$outStream must be resource'); + } + $this->beforeWrite(); + $this->writeLocalBlock($outStream); + $cdOffset = ftell($outStream); + $this->writeCentralDirectoryBlock($outStream); + $cdSize = ftell($outStream) - $cdOffset; + $this->writeEndOfCentralDirectoryBlock($outStream, $cdOffset, $cdSize); + } + + protected function beforeWrite() + { + } + + /** + * @param resource $outStream + * + * @throws ZipException + */ + protected function writeLocalBlock($outStream) + { + $zipEntries = $this->zipContainer->getEntries(); + + foreach ($zipEntries as $zipEntry) { + $this->writeLocalHeader($outStream, $zipEntry); + $this->writeData($outStream, $zipEntry); + + if ($zipEntry->isDataDescriptorEnabled()) { + $this->writeDataDescriptor($outStream, $zipEntry); + } + } + } + + /** + * @param resource $outStream + * @param ZipEntry $entry + * + * @throws ZipException + */ + protected function writeLocalHeader($outStream, ZipEntry $entry) + { + // todo in 4.0 version move zipalign functional to ApkWriter class + if ($this->zipContainer->isZipAlign()) { + $this->zipAlign($outStream, $entry); + } + + $relativeOffset = ftell($outStream); + $entry->setLocalHeaderOffset($relativeOffset); + + if ($entry->isEncrypted() && $entry->getEncryptionMethod() === ZipEncryptionMethod::PKWARE) { + $entry->enableDataDescriptor(true); + } + + $dd = $entry->isDataDescriptorRequired() || + $entry->isDataDescriptorEnabled(); + + $compressedSize = $entry->getCompressedSize(); + $uncompressedSize = $entry->getUncompressedSize(); + + $entry->getLocalExtraFields()->remove(Zip64ExtraField::HEADER_ID); + + if ($compressedSize > ZipConstants::ZIP64_MAGIC || $uncompressedSize > ZipConstants::ZIP64_MAGIC) { + $entry->getLocalExtraFields()->add( + new Zip64ExtraField($uncompressedSize, $compressedSize) + ); + + $compressedSize = ZipConstants::ZIP64_MAGIC; + $uncompressedSize = ZipConstants::ZIP64_MAGIC; + } + + $compressionMethod = $entry->getCompressionMethod(); + $crc = $entry->getCrc(); + + if ($entry->isEncrypted() && ZipEncryptionMethod::isWinZipAesMethod($entry->getEncryptionMethod())) { + /** @var WinZipAesExtraField|null $winZipAesExtra */ + $winZipAesExtra = $entry->getLocalExtraField(WinZipAesExtraField::HEADER_ID); + + if ($winZipAesExtra === null) { + $winZipAesExtra = WinZipAesExtraField::create($entry); + } + + if ($winZipAesExtra->isV2()) { + $crc = 0; + } + $compressionMethod = ZipCompressionMethod::WINZIP_AES; + } + + $extra = $this->getExtraFieldsContents($entry, true); + $name = $entry->getName(); + $dosCharset = $entry->getCharset(); + + if ($dosCharset !== null && !$entry->isUtf8Flag()) { + $name = DosCodePage::fromUTF8($name, $dosCharset); + } + + $nameLength = \strlen($name); + $extraLength = \strlen($extra); + + $size = $nameLength + $extraLength; + + if ($size > 0xffff) { + throw new ZipException( + sprintf( + '%s (the total size of %s bytes for the name, extra fields and comment exceeds the maximum size of %d bytes)', + $entry->getName(), + $size, + 0xffff + ) + ); + } + + $extractedBy = ($entry->getExtractedOS() << 8) | $entry->getExtractVersion(); + + fwrite( + $outStream, + pack( + 'VvvvVVVVvv', + // local file header signature 4 bytes (0x04034b50) + ZipConstants::LOCAL_FILE_HEADER, + // version needed to extract 2 bytes + $extractedBy, + // general purpose bit flag 2 bytes + $entry->getGeneralPurposeBitFlags(), + // compression method 2 bytes + $compressionMethod, + // last mod file time 2 bytes + // last mod file date 2 bytes + $entry->getDosTime(), + // crc-32 4 bytes + $dd ? 0 : $crc, + // compressed size 4 bytes + $dd ? 0 : $compressedSize, + // uncompressed size 4 bytes + $dd ? 0 : $uncompressedSize, + // file name length 2 bytes + $nameLength, + // extra field length 2 bytes + $extraLength + ) + ); + + if ($nameLength > 0) { + fwrite($outStream, $name); + } + + if ($extraLength > 0) { + fwrite($outStream, $extra); + } + } + + /** + * @param resource $outStream + * @param ZipEntry $entry + * + * @throws ZipException + */ + private function zipAlign($outStream, ZipEntry $entry) + { + if (!$entry->isDirectory() && $entry->getCompressionMethod() === ZipCompressionMethod::STORED) { + $entry->removeExtraField(ApkAlignmentExtraField::HEADER_ID); + + $extra = $this->getExtraFieldsContents($entry, true); + $extraLength = \strlen($extra); + $name = $entry->getName(); + + $dosCharset = $entry->getCharset(); + + if ($dosCharset !== null && !$entry->isUtf8Flag()) { + $name = DosCodePage::fromUTF8($name, $dosCharset); + } + $nameLength = \strlen($name); + + $multiple = ApkAlignmentExtraField::ALIGNMENT_BYTES; + + if (StringUtil::endsWith($name, '.so')) { + $multiple = ApkAlignmentExtraField::COMMON_PAGE_ALIGNMENT_BYTES; + } + + $offset = ftell($outStream); + + $dataMinStartOffset = + $offset + + ZipConstants::LFH_FILENAME_POS + + $extraLength + + $nameLength; + + $padding = + ($multiple - ($dataMinStartOffset % $multiple)) + % $multiple; + + if ($padding > 0) { + $dataMinStartOffset += ApkAlignmentExtraField::MIN_SIZE; + $padding = + ($multiple - ($dataMinStartOffset % $multiple)) + % $multiple; + + $entry->getLocalExtraFields()->add( + new ApkAlignmentExtraField($multiple, $padding) + ); + } + } + } + + /** + * Merges the local file data fields of the given ZipExtraFields. + * + * @param ZipEntry $entry + * @param bool $local + * + * @throws ZipException + * + * @return string + */ + protected function getExtraFieldsContents(ZipEntry $entry, $local) + { + $local = (bool) $local; + $collection = $local ? + $entry->getLocalExtraFields() : + $entry->getCdExtraFields(); + $extraData = ''; + + foreach ($collection as $extraField) { + if ($local) { + $data = $extraField->packLocalFileData(); + } else { + $data = $extraField->packCentralDirData(); + } + $extraData .= pack( + 'vv', + $extraField->getHeaderId(), + \strlen($data) + ); + $extraData .= $data; + } + + $size = \strlen($extraData); + + if ($size > 0xffff) { + throw new ZipException( + sprintf( + 'Size extra out of range: %d. Extra data: %s', + $size, + $extraData + ) + ); + } + + return $extraData; + } + + /** + * @param resource $outStream + * @param ZipEntry $entry + * + * @throws ZipException + */ + protected function writeData($outStream, ZipEntry $entry) + { + $zipData = $entry->getData(); + + if ($zipData === null) { + if ($entry->isDirectory()) { + return; + } + + throw new ZipException(sprintf('No zip data for entry "%s"', $entry->getName())); + } + + // data write variants: + // -------------------- + // * data of source zip file -> copy compressed data + // * store - simple write + // * store and encryption - apply encryption filter and simple write + // * deflate or bzip2 - apply compression filter and simple write + // * (deflate or bzip2) and encryption - create temp stream and apply + // compression filter to it, then apply encryption filter to root + // stream and write temp stream data. + // (PHP cannot apply the filter for encryption after the compression + // filter, so a temporary stream is created for the compressed data) + + if ($zipData instanceof ZipSourceFileData && !$zipData->hasRecompressData($entry)) { + // data of source zip file -> copy compressed data + $zipData->copyCompressedDataToStream($outStream); + + return; + } + + $entryStream = $zipData->getDataAsStream(); + + if (stream_get_meta_data($entryStream)['seekable']) { + rewind($entryStream); + } + + $uncompressedSize = $entry->getUncompressedSize(); + + $posBeforeWrite = ftell($outStream); + $compressionMethod = $entry->getCompressionMethod(); + + if ($entry->isEncrypted()) { + if ($compressionMethod === ZipCompressionMethod::STORED) { + $contextFilter = $this->appendEncryptionFilter($outStream, $entry, $uncompressedSize); + $checksum = $this->writeAndCountChecksum($entryStream, $outStream, $uncompressedSize); + } else { + $compressStream = fopen('php://temp', 'w+b'); + $contextFilter = $this->appendCompressionFilter($compressStream, $entry); + $checksum = $this->writeAndCountChecksum($entryStream, $compressStream, $uncompressedSize); + + if ($contextFilter !== null) { + stream_filter_remove($contextFilter); + $contextFilter = null; + } + + rewind($compressStream); + + $compressedSize = fstat($compressStream)['size']; + $contextFilter = $this->appendEncryptionFilter($outStream, $entry, $compressedSize); + + stream_copy_to_stream($compressStream, $outStream); + } + } else { + $contextFilter = $this->appendCompressionFilter($outStream, $entry); + $checksum = $this->writeAndCountChecksum($entryStream, $outStream, $uncompressedSize); + } + + if ($contextFilter !== null) { + stream_filter_remove($contextFilter); + $contextFilter = null; + } + + // my hack {@see https://bugs.php.net/bug.php?id=49874} + fseek($outStream, 0, \SEEK_END); + $compressedSize = ftell($outStream) - $posBeforeWrite; + + $entry->setCompressedSize($compressedSize); + $entry->setCrc($checksum); + + if (!$entry->isDataDescriptorEnabled()) { + if ($uncompressedSize > ZipConstants::ZIP64_MAGIC || $compressedSize > ZipConstants::ZIP64_MAGIC) { + /** @var Zip64ExtraField|null $zip64ExtraLocal */ + $zip64ExtraLocal = $entry->getLocalExtraField(Zip64ExtraField::HEADER_ID); + + // if there is a zip64 extra record, then update it; + // if not, write data to data descriptor + if ($zip64ExtraLocal !== null) { + $zip64ExtraLocal->setCompressedSize($compressedSize); + $zip64ExtraLocal->setUncompressedSize($uncompressedSize); + + $posExtra = $entry->getLocalHeaderOffset() + ZipConstants::LFH_FILENAME_POS + \strlen($entry->getName()); + fseek($outStream, $posExtra); + fwrite($outStream, $this->getExtraFieldsContents($entry, true)); + } else { + $posGPBF = $entry->getLocalHeaderOffset() + 6; + $entry->enableDataDescriptor(true); + fseek($outStream, $posGPBF); + fwrite( + $outStream, + pack( + 'v', + // general purpose bit flag 2 bytes + $entry->getGeneralPurposeBitFlags() + ) + ); + } + + $compressedSize = ZipConstants::ZIP64_MAGIC; + $uncompressedSize = ZipConstants::ZIP64_MAGIC; + } + + $posChecksum = $entry->getLocalHeaderOffset() + 14; + + /** @var WinZipAesExtraField|null $winZipAesExtra */ + $winZipAesExtra = $entry->getLocalExtraField(WinZipAesExtraField::HEADER_ID); + + if ($winZipAesExtra !== null && $winZipAesExtra->isV2()) { + $checksum = 0; + } + + fseek($outStream, $posChecksum); + fwrite( + $outStream, + pack( + 'VVV', + // crc-32 4 bytes + $checksum, + // compressed size 4 bytes + $compressedSize, + // uncompressed size 4 bytes + $uncompressedSize + ) + ); + fseek($outStream, 0, \SEEK_END); + } + } + + /** + * @param resource $inStream + * @param resource $outStream + * @param int $size + * + * @return int + */ + private function writeAndCountChecksum($inStream, $outStream, $size) + { + $contextHash = hash_init('crc32b'); + $offset = 0; + + while ($offset < $size) { + $read = min(self::CHUNK_SIZE, $size - $offset); + $buffer = fread($inStream, $read); + fwrite($outStream, $buffer); + hash_update($contextHash, $buffer); + $offset += $read; + } + + return (int) hexdec(hash_final($contextHash)); + } + + /** + * @param resource $outStream + * @param ZipEntry $entry + * + * @throws ZipUnsupportMethodException + * + * @return resource|null + */ + protected function appendCompressionFilter($outStream, ZipEntry $entry) + { + $contextCompress = null; + switch ($entry->getCompressionMethod()) { + case ZipCompressionMethod::DEFLATED: + if (!($contextCompress = stream_filter_append( + $outStream, + 'zlib.deflate', + \STREAM_FILTER_WRITE, + ['level' => $entry->getCompressionLevel()] + ))) { + throw new \RuntimeException('Could not append filter "zlib.deflate" to out stream'); + } + break; + + case ZipCompressionMethod::BZIP2: + if (!($contextCompress = stream_filter_append( + $outStream, + 'bzip2.compress', + \STREAM_FILTER_WRITE, + ['blocks' => $entry->getCompressionLevel(), 'work' => 0] + ))) { + throw new \RuntimeException('Could not append filter "bzip2.compress" to out stream'); + } + break; + + case ZipCompressionMethod::STORED: + // file without compression, do nothing + break; + + default: + throw new ZipUnsupportMethodException( + sprintf( + '%s (compression method %d (%s) is not supported)', + $entry->getName(), + $entry->getCompressionMethod(), + ZipCompressionMethod::getCompressionMethodName($entry->getCompressionMethod()) + ) + ); + } + + return $contextCompress; + } + + /** + * @param resource $outStream + * @param ZipEntry $entry + * @param int $size + * + * @return resource|null + */ + protected function appendEncryptionFilter($outStream, ZipEntry $entry, $size) + { + $encContextFilter = null; + + if ($entry->isEncrypted()) { + if ($entry->getEncryptionMethod() === ZipEncryptionMethod::PKWARE) { + PKEncryptionStreamFilter::register(); + $cipherFilterName = PKEncryptionStreamFilter::FILTER_NAME; + } else { + WinZipAesEncryptionStreamFilter::register(); + $cipherFilterName = WinZipAesEncryptionStreamFilter::FILTER_NAME; + } + $encContextFilter = stream_filter_append( + $outStream, + $cipherFilterName, + \STREAM_FILTER_WRITE, + [ + 'entry' => $entry, + 'size' => $size, + ] + ); + + if (!$encContextFilter) { + throw new \RuntimeException('Not apply filter ' . $cipherFilterName); + } + } + + return $encContextFilter; + } + + /** + * @param resource $outStream + * @param ZipEntry $entry + */ + protected function writeDataDescriptor($outStream, ZipEntry $entry) + { + $crc = $entry->getCrc(); + + /** @var WinZipAesExtraField|null $winZipAesExtra */ + $winZipAesExtra = $entry->getLocalExtraField(WinZipAesExtraField::HEADER_ID); + + if ($winZipAesExtra !== null && $winZipAesExtra->isV2()) { + $crc = 0; + } + + fwrite( + $outStream, + pack( + 'VV', + // data descriptor signature 4 bytes (0x08074b50) + ZipConstants::DATA_DESCRIPTOR, + // crc-32 4 bytes + $crc + ) + ); + + if ( + $entry->isZip64ExtensionsRequired() || + $entry->getLocalExtraFields()->has(Zip64ExtraField::HEADER_ID) + ) { + $dd = + // compressed size 8 bytes + PackUtil::packLongLE($entry->getCompressedSize()) . + // uncompressed size 8 bytes + PackUtil::packLongLE($entry->getUncompressedSize()); + } else { + $dd = pack( + 'VV', + // compressed size 4 bytes + $entry->getCompressedSize(), + // uncompressed size 4 bytes + $entry->getUncompressedSize() + ); + } + + fwrite($outStream, $dd); + } + + /** + * @param resource $outStream + * + * @throws ZipException + */ + protected function writeCentralDirectoryBlock($outStream) + { + foreach ($this->zipContainer->getEntries() as $outputEntry) { + $this->writeCentralDirectoryHeader($outStream, $outputEntry); + } + } + + /** + * Writes a Central File Header record. + * + * @param resource $outStream + * @param ZipEntry $entry + * + * @throws ZipException + */ + protected function writeCentralDirectoryHeader($outStream, ZipEntry $entry) + { + $compressedSize = $entry->getCompressedSize(); + $uncompressedSize = $entry->getUncompressedSize(); + $localHeaderOffset = $entry->getLocalHeaderOffset(); + + $entry->getCdExtraFields()->remove(Zip64ExtraField::HEADER_ID); + + if ( + $localHeaderOffset > ZipConstants::ZIP64_MAGIC || + $compressedSize > ZipConstants::ZIP64_MAGIC || + $uncompressedSize > ZipConstants::ZIP64_MAGIC + ) { + $zip64ExtraField = new Zip64ExtraField(); + + if ($uncompressedSize >= ZipConstants::ZIP64_MAGIC) { + $zip64ExtraField->setUncompressedSize($uncompressedSize); + $uncompressedSize = ZipConstants::ZIP64_MAGIC; + } + + if ($compressedSize >= ZipConstants::ZIP64_MAGIC) { + $zip64ExtraField->setCompressedSize($compressedSize); + $compressedSize = ZipConstants::ZIP64_MAGIC; + } + + if ($localHeaderOffset >= ZipConstants::ZIP64_MAGIC) { + $zip64ExtraField->setLocalHeaderOffset($localHeaderOffset); + $localHeaderOffset = ZipConstants::ZIP64_MAGIC; + } + + $entry->getCdExtraFields()->add($zip64ExtraField); + } + + $extra = $this->getExtraFieldsContents($entry, false); + $extraLength = \strlen($extra); + + $name = $entry->getName(); + $comment = $entry->getComment(); + + $dosCharset = $entry->getCharset(); + + if ($dosCharset !== null && !$entry->isUtf8Flag()) { + $name = DosCodePage::fromUTF8($name, $dosCharset); + + if ($comment) { + $comment = DosCodePage::fromUTF8($comment, $dosCharset); + } + } + + $commentLength = \strlen($comment); + + $compressionMethod = $entry->getCompressionMethod(); + $crc = $entry->getCrc(); + + /** @var WinZipAesExtraField|null $winZipAesExtra */ + $winZipAesExtra = $entry->getLocalExtraField(WinZipAesExtraField::HEADER_ID); + + if ($winZipAesExtra !== null) { + if ($winZipAesExtra->isV2()) { + $crc = 0; + } + $compressionMethod = ZipCompressionMethod::WINZIP_AES; + } + + fwrite( + $outStream, + pack( + 'VvvvvVVVVvvvvvVV', + // central file header signature 4 bytes (0x02014b50) + ZipConstants::CENTRAL_FILE_HEADER, + // version made by 2 bytes + ($entry->getCreatedOS() << 8) | $entry->getSoftwareVersion(), + // version needed to extract 2 bytes + ($entry->getExtractedOS() << 8) | $entry->getExtractVersion(), + // general purpose bit flag 2 bytes + $entry->getGeneralPurposeBitFlags(), + // compression method 2 bytes + $compressionMethod, + // last mod file datetime 4 bytes + $entry->getDosTime(), + // crc-32 4 bytes + $crc, + // compressed size 4 bytes + $compressedSize, + // uncompressed size 4 bytes + $uncompressedSize, + // file name length 2 bytes + \strlen($name), + // extra field length 2 bytes + $extraLength, + // file comment length 2 bytes + $commentLength, + // disk number start 2 bytes + 0, + // internal file attributes 2 bytes + $entry->getInternalAttributes(), + // external file attributes 4 bytes + $entry->getExternalAttributes(), + // relative offset of local header 4 bytes + $localHeaderOffset + ) + ); + + // file name (variable size) + fwrite($outStream, $name); + + if ($extraLength > 0) { + // extra field (variable size) + fwrite($outStream, $extra); + } + + if ($commentLength > 0) { + // file comment (variable size) + fwrite($outStream, $comment); + } + } + + /** + * @param resource $outStream + * @param int $centralDirectoryOffset + * @param int $centralDirectorySize + */ + protected function writeEndOfCentralDirectoryBlock( + $outStream, + $centralDirectoryOffset, + $centralDirectorySize + ) { + $cdEntriesCount = \count($this->zipContainer); + + $cdEntriesZip64 = $cdEntriesCount > 0xffff; + $cdSizeZip64 = $centralDirectorySize > ZipConstants::ZIP64_MAGIC; + $cdOffsetZip64 = $centralDirectoryOffset > ZipConstants::ZIP64_MAGIC; + + $zip64Required = $cdEntriesZip64 + || $cdSizeZip64 + || $cdOffsetZip64; + + if ($zip64Required) { + $zip64EndOfCentralDirectoryOffset = ftell($outStream); + + // find max software version, version needed to extract and most common platform + list($softwareVersion, $versionNeededToExtract) = array_reduce( + $this->zipContainer->getEntries(), + static function (array $carry, ZipEntry $entry) { + $carry[0] = max($carry[0], $entry->getSoftwareVersion() & 0xFF); + $carry[1] = max($carry[1], $entry->getExtractVersion() & 0xFF); + + return $carry; + }, + [ZipVersion::v10_DEFAULT_MIN, ZipVersion::v45_ZIP64_EXT] + ); + + $createdOS = $extractedOS = ZipPlatform::OS_DOS; + $versionMadeBy = ($createdOS << 8) | max($softwareVersion, ZipVersion::v45_ZIP64_EXT); + $versionExtractedBy = ($extractedOS << 8) | max($versionNeededToExtract, ZipVersion::v45_ZIP64_EXT); + + // write zip64 end of central directory signature + fwrite( + $outStream, + pack( + 'V', + // signature 4 bytes (0x06064b50) + ZipConstants::ZIP64_END_CD + ) + ); + // size of zip64 end of central + // directory record 8 bytes + fwrite($outStream, PackUtil::packLongLE(ZipConstants::ZIP64_END_OF_CD_LEN - 12)); + fwrite( + $outStream, + pack( + 'vvVV', + // version made by 2 bytes + $versionMadeBy & 0xFFFF, + // version needed to extract 2 bytes + $versionExtractedBy & 0xFFFF, + // number of this disk 4 bytes + 0, + // number of the disk with the + // start of the central directory 4 bytes + 0 + ) + ); + + fwrite( + $outStream, + // total number of entries in the + // central directory on this disk 8 bytes + PackUtil::packLongLE($cdEntriesCount) . + // total number of entries in the + // central directory 8 bytes + PackUtil::packLongLE($cdEntriesCount) . + // size of the central directory 8 bytes + PackUtil::packLongLE($centralDirectorySize) . + // offset of start of central + // directory with respect to + // the starting disk number 8 bytes + PackUtil::packLongLE($centralDirectoryOffset) + ); + + // write zip64 end of central directory locator + fwrite( + $outStream, + pack( + 'VV', + // zip64 end of central dir locator + // signature 4 bytes (0x07064b50) + ZipConstants::ZIP64_END_CD_LOC, + // number of the disk with the + // start of the zip64 end of + // central directory 4 bytes + 0 + ) . + // relative offset of the zip64 + // end of central directory record 8 bytes + PackUtil::packLongLE($zip64EndOfCentralDirectoryOffset) . + // total number of disks 4 bytes + pack('V', 1) + ); + } + + $comment = $this->zipContainer->getArchiveComment(); + $commentLength = $comment !== null ? \strlen($comment) : 0; + + fwrite( + $outStream, + pack( + 'VvvvvVVv', + // end of central dir signature 4 bytes (0x06054b50) + ZipConstants::END_CD, + // number of this disk 2 bytes + 0, + // number of the disk with the + // start of the central directory 2 bytes + 0, + // total number of entries in the + // central directory on this disk 2 bytes + $cdEntriesZip64 ? 0xffff : $cdEntriesCount, + // total number of entries in + // the central directory 2 bytes + $cdEntriesZip64 ? 0xffff : $cdEntriesCount, + // size of the central directory 4 bytes + $cdSizeZip64 ? ZipConstants::ZIP64_MAGIC : $centralDirectorySize, + // offset of start of central + // directory with respect to + // the starting disk number 4 bytes + $cdOffsetZip64 ? ZipConstants::ZIP64_MAGIC : $centralDirectoryOffset, + // .ZIP file comment length 2 bytes + $commentLength + ) + ); + + if ($comment !== null && $commentLength > 0) { + // .ZIP file comment (variable size) + fwrite($outStream, $comment); + } + } +} diff --git a/vendor/nelexa/zip/src/Model/Data/ZipFileData.php b/vendor/nelexa/zip/src/Model/Data/ZipFileData.php new file mode 100644 index 0000000..43590c5 --- /dev/null +++ b/vendor/nelexa/zip/src/Model/Data/ZipFileData.php @@ -0,0 +1,81 @@ +isFile()) { + throw new ZipException('$fileInfo is not a file.'); + } + + if (!$fileInfo->isReadable()) { + throw new ZipException('$fileInfo is not readable.'); + } + + $this->file = $fileInfo; + $zipEntry->setUncompressedSize($fileInfo->getSize()); + } + + /** + * @throws ZipException + * + * @return resource returns stream data + */ + public function getDataAsStream() + { + if (!$this->file->isReadable()) { + throw new ZipException(sprintf('The %s file is no longer readable.', $this->file->getPathname())); + } + + return fopen($this->file->getPathname(), 'rb'); + } + + /** + * @throws ZipException + * + * @return string returns data as string + */ + public function getDataAsString() + { + if (!$this->file->isReadable()) { + throw new ZipException(sprintf('The %s file is no longer readable.', $this->file->getPathname())); + } + + return file_get_contents($this->file->getPathname()); + } + + /** + * @param resource $outStream + * + * @throws ZipException + */ + public function copyDataToStream($outStream) + { + try { + $stream = $this->getDataAsStream(); + stream_copy_to_stream($stream, $outStream); + } finally { + fclose($stream); + } + } +} diff --git a/vendor/nelexa/zip/src/Model/Data/ZipNewData.php b/vendor/nelexa/zip/src/Model/Data/ZipNewData.php new file mode 100644 index 0000000..e149638 --- /dev/null +++ b/vendor/nelexa/zip/src/Model/Data/ZipNewData.php @@ -0,0 +1,132 @@ + array of resource ids and the number of class clones + */ + private static $guardClonedStream = []; + + /** @var ZipEntry */ + private $zipEntry; + + /** @var resource */ + private $stream; + + /** + * ZipStringData constructor. + * + * @param ZipEntry $zipEntry + * @param string|resource $data + */ + public function __construct(ZipEntry $zipEntry, $data) + { + $this->zipEntry = $zipEntry; + + if (\is_string($data)) { + $zipEntry->setUncompressedSize(\strlen($data)); + + if (!($handle = fopen('php://temp', 'w+b'))) { + // @codeCoverageIgnoreStart + throw new \RuntimeException('A temporary resource cannot be opened for writing.'); + // @codeCoverageIgnoreEnd + } + fwrite($handle, $data); + rewind($handle); + $this->stream = $handle; + } elseif (\is_resource($data)) { + $this->stream = $data; + } + + $resourceId = (int) $this->stream; + self::$guardClonedStream[$resourceId] = + isset(self::$guardClonedStream[$resourceId]) ? + self::$guardClonedStream[$resourceId] + 1 : + 0; + } + + /** + * @return resource returns stream data + */ + public function getDataAsStream() + { + if (!\is_resource($this->stream)) { + throw new \LogicException(sprintf('Resource has been closed (entry=%s).', $this->zipEntry->getName())); + } + + return $this->stream; + } + + /** + * @return string returns data as string + */ + public function getDataAsString() + { + $stream = $this->getDataAsStream(); + $pos = ftell($stream); + + try { + rewind($stream); + + return stream_get_contents($stream); + } finally { + fseek($stream, $pos); + } + } + + /** + * @param resource $outStream + */ + public function copyDataToStream($outStream) + { + $stream = $this->getDataAsStream(); + rewind($stream); + stream_copy_to_stream($stream, $outStream); + } + + /** + * @see https://php.net/manual/en/language.oop5.cloning.php + */ + public function __clone() + { + $resourceId = (int) $this->stream; + self::$guardClonedStream[$resourceId] = + isset(self::$guardClonedStream[$resourceId]) ? + self::$guardClonedStream[$resourceId] + 1 : + 1; + } + + /** + * The stream will be closed when closing the zip archive. + * + * The method implements protection against closing the stream of the cloned object. + * + * @see ZipFile::close() + */ + public function __destruct() + { + $resourceId = (int) $this->stream; + + if (isset(self::$guardClonedStream[$resourceId]) && self::$guardClonedStream[$resourceId] > 0) { + self::$guardClonedStream[$resourceId]--; + + return; + } + + if (\is_resource($this->stream)) { + fclose($this->stream); + } + } +} diff --git a/vendor/nelexa/zip/src/Model/Data/ZipSourceFileData.php b/vendor/nelexa/zip/src/Model/Data/ZipSourceFileData.php new file mode 100644 index 0000000..c53df05 --- /dev/null +++ b/vendor/nelexa/zip/src/Model/Data/ZipSourceFileData.php @@ -0,0 +1,172 @@ +zipReader = $zipReader; + $this->offset = $offsetData; + $this->sourceEntry = $zipEntry; + $this->compressedSize = $zipEntry->getCompressedSize(); + $this->uncompressedSize = $zipEntry->getUncompressedSize(); + } + + /** + * @param ZipEntry $entry + * + * @return bool + */ + public function hasRecompressData(ZipEntry $entry) + { + return $this->sourceEntry->getCompressionLevel() !== $entry->getCompressionLevel() || + $this->sourceEntry->getCompressionMethod() !== $entry->getCompressionMethod() || + $this->sourceEntry->isEncrypted() !== $entry->isEncrypted() || + $this->sourceEntry->getEncryptionMethod() !== $entry->getEncryptionMethod() || + $this->sourceEntry->getPassword() !== $entry->getPassword() || + $this->sourceEntry->getCompressedSize() !== $entry->getCompressedSize() || + $this->sourceEntry->getUncompressedSize() !== $entry->getUncompressedSize() || + $this->sourceEntry->getCrc() !== $entry->getCrc(); + } + + /** + * @throws ZipException + * + * @return resource returns stream data + */ + public function getDataAsStream() + { + if (!\is_resource($this->stream)) { + $this->stream = $this->zipReader->getEntryStream($this); + } + + return $this->stream; + } + + /** + * @throws ZipException + * + * @return string returns data as string + */ + public function getDataAsString() + { + $autoClosable = $this->stream === null; + + $stream = $this->getDataAsStream(); + $pos = ftell($stream); + + try { + rewind($stream); + + return stream_get_contents($stream); + } finally { + if ($autoClosable) { + fclose($stream); + $this->stream = null; + } else { + fseek($stream, $pos); + } + } + } + + /** + * @param resource $outputStream Output stream + * + * @throws ZipException + * @throws Crc32Exception + */ + public function copyDataToStream($outputStream) + { + if (\is_resource($this->stream)) { + rewind($this->stream); + stream_copy_to_stream($this->stream, $outputStream); + } else { + $this->zipReader->copyUncompressedDataToStream($this, $outputStream); + } + } + + /** + * @param resource $outputStream Output stream + */ + public function copyCompressedDataToStream($outputStream) + { + $this->zipReader->copyCompressedDataToStream($this, $outputStream); + } + + /** + * @return ZipEntry + */ + public function getSourceEntry() + { + return $this->sourceEntry; + } + + /** + * @return int + */ + public function getCompressedSize() + { + return $this->compressedSize; + } + + /** + * @return int + */ + public function getUncompressedSize() + { + return $this->uncompressedSize; + } + + /** + * @return int + */ + public function getOffset() + { + return $this->offset; + } + + /** + * {@inheritdoc} + */ + public function __destruct() + { + if (\is_resource($this->stream)) { + fclose($this->stream); + } + } +} diff --git a/vendor/nelexa/zip/src/Model/EndOfCentralDirectory.php b/vendor/nelexa/zip/src/Model/EndOfCentralDirectory.php new file mode 100644 index 0000000..d312cfe --- /dev/null +++ b/vendor/nelexa/zip/src/Model/EndOfCentralDirectory.php @@ -0,0 +1,93 @@ +entryCount = $entryCount; + $this->cdOffset = $cdOffset; + $this->cdSize = $cdSize; + $this->zip64 = $zip64; + $this->comment = $comment; + } + + /** + * @param string|null $comment + */ + public function setComment($comment) + { + $this->comment = $comment; + } + + /** + * @return int + */ + public function getEntryCount() + { + return $this->entryCount; + } + + /** + * @return int + */ + public function getCdOffset() + { + return $this->cdOffset; + } + + /** + * @return int + */ + public function getCdSize() + { + return $this->cdSize; + } + + /** + * @return string|null + */ + public function getComment() + { + return $this->comment; + } + + /** + * @return bool + */ + public function isZip64() + { + return $this->zip64; + } +} diff --git a/vendor/nelexa/zip/src/Model/Extra/ExtraFieldsCollection.php b/vendor/nelexa/zip/src/Model/Extra/ExtraFieldsCollection.php new file mode 100644 index 0000000..9cc2020 --- /dev/null +++ b/vendor/nelexa/zip/src/Model/Extra/ExtraFieldsCollection.php @@ -0,0 +1,276 @@ +collection); + } + + /** + * Returns the Extra Field with the given Header ID or null + * if no such Extra Field exists. + * + * @param int $headerId the requested Header ID + * + * @return ZipExtraField|null the Extra Field with the given Header ID or + * if no such Extra Field exists + */ + public function get($headerId) + { + $this->validateHeaderId($headerId); + + return isset($this->collection[$headerId]) ? $this->collection[$headerId] : null; + } + + /** + * @param int $headerId + */ + private function validateHeaderId($headerId) + { + if ($headerId < 0 || $headerId > 0xffff) { + throw new \InvalidArgumentException('$headerId out of range'); + } + } + + /** + * Stores the given Extra Field in this collection. + * + * @param ZipExtraField $extraField the Extra Field to store in this collection + * + * @return ZipExtraField the Extra Field previously associated with the Header ID of + * of the given Extra Field or null if no such Extra Field existed + */ + public function add(ZipExtraField $extraField) + { + $headerId = $extraField->getHeaderId(); + + $this->validateHeaderId($headerId); + $this->collection[$headerId] = $extraField; + + return $extraField; + } + + /** + * @param ZipExtraField[] $extraFields + */ + public function addAll(array $extraFields) + { + foreach ($extraFields as $extraField) { + $this->add($extraField); + } + } + + /** + * @param ExtraFieldsCollection $collection + */ + public function addCollection(self $collection) + { + $this->addAll($collection->collection); + } + + /** + * @return ZipExtraField[] + */ + public function getAll() + { + return $this->collection; + } + + /** + * Returns Extra Field exists. + * + * @param int $headerId the requested Header ID + * + * @return bool + */ + public function has($headerId) + { + return isset($this->collection[$headerId]); + } + + /** + * Removes the Extra Field with the given Header ID. + * + * @param int $headerId the requested Header ID + * + * @return ZipExtraField|null the Extra Field with the given Header ID or null + * if no such Extra Field exists + */ + public function remove($headerId) + { + $this->validateHeaderId($headerId); + + if (isset($this->collection[$headerId])) { + $ef = $this->collection[$headerId]; + unset($this->collection[$headerId]); + + return $ef; + } + + return null; + } + + /** + * Whether a offset exists. + * + * @see http://php.net/manual/en/arrayaccess.offsetexists.php + * + * @param int $offset an offset to check for + * + * @return bool true on success or false on failure + */ + public function offsetExists($offset) + { + return isset($this->collection[(int) $offset]); + } + + /** + * Offset to retrieve. + * + * @see http://php.net/manual/en/arrayaccess.offsetget.php + * + * @param int $offset the offset to retrieve + * + * @return ZipExtraField|null + */ + public function offsetGet($offset) + { + return isset($this->collection[$offset]) ? $this->collection[$offset] : null; + } + + /** + * Offset to set. + * + * @see http://php.net/manual/en/arrayaccess.offsetset.php + * + * @param mixed $offset the offset to assign the value to + * @param ZipExtraField $value the value to set + */ + public function offsetSet($offset, $value) + { + if (!$value instanceof ZipExtraField) { + throw new \InvalidArgumentException('value is not instanceof ' . ZipExtraField::class); + } + $this->add($value); + } + + /** + * Offset to unset. + * + * @see http://php.net/manual/en/arrayaccess.offsetunset.php + * + * @param mixed $offset the offset to unset + */ + public function offsetUnset($offset) + { + $this->remove($offset); + } + + /** + * Return the current element. + * + * @see http://php.net/manual/en/iterator.current.php + * + * @return ZipExtraField + */ + public function current() + { + return current($this->collection); + } + + /** + * Move forward to next element. + * + * @see http://php.net/manual/en/iterator.next.php + */ + public function next() + { + next($this->collection); + } + + /** + * Return the key of the current element. + * + * @see http://php.net/manual/en/iterator.key.php + * + * @return int scalar on success, or null on failure + */ + public function key() + { + return key($this->collection); + } + + /** + * Checks if current position is valid. + * + * @see http://php.net/manual/en/iterator.valid.php + * + * @return bool The return value will be casted to boolean and then evaluated. + * Returns true on success or false on failure. + */ + public function valid() + { + return key($this->collection) !== null; + } + + /** + * Rewind the Iterator to the first element. + * + * @see http://php.net/manual/en/iterator.rewind.php + */ + public function rewind() + { + reset($this->collection); + } + + public function clear() + { + $this->collection = []; + } + + /** + * @return string + */ + public function __toString() + { + $formats = []; + + foreach ($this->collection as $key => $value) { + $formats[] = (string) $value; + } + + return implode("\n", $formats); + } + + /** + * If clone extra fields. + */ + public function __clone() + { + foreach ($this->collection as $k => $v) { + $this->collection[$k] = clone $v; + } + } +} diff --git a/vendor/nelexa/zip/src/Model/Extra/Fields/AbstractUnicodeExtraField.php b/vendor/nelexa/zip/src/Model/Extra/Fields/AbstractUnicodeExtraField.php new file mode 100644 index 0000000..dd6f566 --- /dev/null +++ b/vendor/nelexa/zip/src/Model/Extra/Fields/AbstractUnicodeExtraField.php @@ -0,0 +1,133 @@ +crc32 = (int) $crc32; + $this->unicodeValue = (string) $unicodeValue; + } + + /** + * @return int the CRC32 checksum of the filename or comment as + * encoded in the central directory of the zip file + */ + public function getCrc32() + { + return $this->crc32; + } + + /** + * @param int $crc32 + */ + public function setCrc32($crc32) + { + $this->crc32 = (int) $crc32; + } + + /** + * @return string + */ + public function getUnicodeValue() + { + return $this->unicodeValue; + } + + /** + * @param string $unicodeValue the UTF-8 encoded name to set + */ + public function setUnicodeValue($unicodeValue) + { + $this->unicodeValue = $unicodeValue; + } + + /** + * Populate data from this array as if it was in local file data. + * + * @param string $buffer the buffer to read data from + * @param ZipEntry|null $entry + * + * @throws ZipException on error + * + * @return static + */ + public static function unpackLocalFileData($buffer, ZipEntry $entry = null) + { + if (\strlen($buffer) < 5) { + throw new ZipException('Unicode path extra data must have at least 5 bytes.'); + } + + $data = unpack('Cversion/Vcrc32', $buffer); + + if ($data['version'] !== self::DEFAULT_VERSION) { + throw new ZipException(sprintf('Unsupported version [%d] for Unicode path extra data.', $data['version'])); + } + + $unicodeValue = substr($buffer, 5); + + return new static($data['crc32'], $unicodeValue); + } + + /** + * Populate data from this array as if it was in central directory data. + * + * @param string $buffer the buffer to read data from + * @param ZipEntry|null $entry + * + * @throws ZipException on error + * + * @return static + */ + public static function unpackCentralDirData($buffer, ZipEntry $entry = null) + { + return self::unpackLocalFileData($buffer, $entry); + } + + /** + * The actual data to put into local file data - without Header-ID + * or length specifier. + * + * @return string the data + */ + public function packLocalFileData() + { + return pack( + 'CV', + self::DEFAULT_VERSION, + $this->crc32 + ) . + $this->unicodeValue; + } + + /** + * The actual data to put into central directory - without Header-ID or + * length specifier. + * + * @return string the data + */ + public function packCentralDirData() + { + return $this->packLocalFileData(); + } +} diff --git a/vendor/nelexa/zip/src/Model/Extra/Fields/ApkAlignmentExtraField.php b/vendor/nelexa/zip/src/Model/Extra/Fields/ApkAlignmentExtraField.php new file mode 100644 index 0000000..69c26b8 --- /dev/null +++ b/vendor/nelexa/zip/src/Model/Extra/Fields/ApkAlignmentExtraField.php @@ -0,0 +1,176 @@ +multiple = $multiple; + $this->padding = $padding; + } + + /** + * Returns the Header ID (type) of this Extra Field. + * The Header ID is an unsigned short integer (two bytes) + * which must be constant during the life cycle of this object. + * + * @return int + */ + public function getHeaderId() + { + return self::HEADER_ID; + } + + /** + * @return int + */ + public function getMultiple() + { + return $this->multiple; + } + + /** + * @return int + */ + public function getPadding() + { + return $this->padding; + } + + /** + * @param int $multiple + */ + public function setMultiple($multiple) + { + $this->multiple = (int) $multiple; + } + + /** + * @param int $padding + */ + public function setPadding($padding) + { + $this->padding = (int) $padding; + } + + /** + * Populate data from this array as if it was in local file data. + * + * @param string $buffer the buffer to read data from + * @param ZipEntry|null $entry + * + * @throws ZipException + * + * @return ApkAlignmentExtraField + */ + public static function unpackLocalFileData($buffer, ZipEntry $entry = null) + { + $length = \strlen($buffer); + + if ($length < 2) { + // This is APK alignment field. + // FORMAT: + // * uint16 alignment multiple (in bytes) + // * remaining bytes -- padding to achieve alignment of data which starts after + // the extra field + throw new ZipException( + 'Minimum 6 bytes of the extensible data block/field used for alignment of uncompressed entries.' + ); + } + $multiple = unpack('v', $buffer)[1]; + $padding = $length - 2; + + return new self($multiple, $padding); + } + + /** + * Populate data from this array as if it was in central directory data. + * + * @param string $buffer the buffer to read data from + * @param ZipEntry|null $entry + * + * @throws ZipException on error + * + * @return ApkAlignmentExtraField + */ + public static function unpackCentralDirData($buffer, ZipEntry $entry = null) + { + return self::unpackLocalFileData($buffer, $entry); + } + + /** + * The actual data to put into local file data - without Header-ID + * or length specifier. + * + * @return string the data + */ + public function packLocalFileData() + { + return pack('vx' . $this->padding, $this->multiple); + } + + /** + * The actual data to put into central directory - without Header-ID or + * length specifier. + * + * @return string the data + */ + public function packCentralDirData() + { + return $this->packLocalFileData(); + } + + /** + * @return string + */ + public function __toString() + { + return sprintf( + '0x%04x APK Alignment: Multiple=%d Padding=%d', + self::HEADER_ID, + $this->multiple, + $this->padding + ); + } +} diff --git a/vendor/nelexa/zip/src/Model/Extra/Fields/AsiExtraField.php b/vendor/nelexa/zip/src/Model/Extra/Fields/AsiExtraField.php new file mode 100644 index 0000000..3bf62b9 --- /dev/null +++ b/vendor/nelexa/zip/src/Model/Extra/Fields/AsiExtraField.php @@ -0,0 +1,302 @@ +mode = $mode; + $this->uid = $uid; + $this->gid = $gid; + $this->link = $link; + } + + /** + * Returns the Header ID (type) of this Extra Field. + * The Header ID is an unsigned short integer (two bytes) + * which must be constant during the life cycle of this object. + * + * @return int + */ + public function getHeaderId() + { + return self::HEADER_ID; + } + + /** + * Populate data from this array as if it was in local file data. + * + * @param string $buffer the buffer to read data from + * @param ZipEntry|null $entry + * + * @throws Crc32Exception + * + * @return static + */ + public static function unpackLocalFileData($buffer, ZipEntry $entry = null) + { + $givenChecksum = unpack('V', $buffer)[1]; + $buffer = substr($buffer, 4); + $realChecksum = crc32($buffer); + + if ($givenChecksum !== $realChecksum) { + throw new Crc32Exception('Asi Unix Extra Filed Data', $givenChecksum, $realChecksum); + } + + $data = unpack('vmode/VlinkSize/vuid/vgid', $buffer); + $link = ''; + + if ($data['linkSize'] > 0) { + $link = substr($buffer, 10); + } + + return new self($data['mode'], $data['uid'], $data['gid'], $link); + } + + /** + * Populate data from this array as if it was in central directory data. + * + * @param string $buffer the buffer to read data from + * @param ZipEntry|null $entry + * + * @throws Crc32Exception + * + * @return AsiExtraField + */ + public static function unpackCentralDirData($buffer, ZipEntry $entry = null) + { + return self::unpackLocalFileData($buffer, $entry); + } + + /** + * The actual data to put into local file data - without Header-ID + * or length specifier. + * + * @return string the data + */ + public function packLocalFileData() + { + $data = pack( + 'vVvv', + $this->mode, + \strlen($this->link), + $this->uid, + $this->gid + ) . $this->link; + + return pack('V', crc32($data)) . $data; + } + + /** + * The actual data to put into central directory - without Header-ID or + * length specifier. + * + * @return string the data + */ + public function packCentralDirData() + { + return $this->packLocalFileData(); + } + + /** + * Name of linked file. + * + * @return string name of the file this entry links to if it is a + * symbolic link, the empty string otherwise + */ + public function getLink() + { + return $this->link; + } + + /** + * Indicate that this entry is a symbolic link to the given filename. + * + * @param string $link name of the file this entry links to, empty + * string if it is not a symbolic link + */ + public function setLink($link) + { + $this->link = (string) $link; + $this->mode = $this->getPermissionsMode($this->mode); + } + + /** + * Is this entry a symbolic link? + * + * @return bool true if this is a symbolic link + */ + public function isLink() + { + return !empty($this->link); + } + + /** + * Get the file mode for given permissions with the correct file type. + * + * @param int $mode the mode + * + * @return int the type with the mode + */ + protected function getPermissionsMode($mode) + { + $type = 0; + + if ($this->isLink()) { + $type = UnixStat::UNX_IFLNK; + } elseif (($mode & UnixStat::UNX_IFREG) !== 0) { + $type = UnixStat::UNX_IFREG; + } elseif (($mode & UnixStat::UNX_IFDIR) !== 0) { + $type = UnixStat::UNX_IFDIR; + } + + return $type | ($mode & self::PERM_MASK); + } + + /** + * Is this entry a directory? + * + * @return bool true if this entry is a directory + */ + public function isDirectory() + { + return ($this->mode & UnixStat::UNX_IFDIR) !== 0 && !$this->isLink(); + } + + /** + * @return int + */ + public function getMode() + { + return $this->mode; + } + + /** + * @param int $mode + */ + public function setMode($mode) + { + $this->mode = $this->getPermissionsMode($mode); + } + + /** + * @return int + */ + public function getUserId() + { + return $this->uid; + } + + /** + * @param int $uid + */ + public function setUserId($uid) + { + $this->uid = (int) $uid; + } + + /** + * @return int + */ + public function getGroupId() + { + return $this->gid; + } + + /** + * @param int $gid + */ + public function setGroupId($gid) + { + $this->gid = (int) $gid; + } + + /** + * @return string + */ + public function __toString() + { + return sprintf( + '0x%04x ASI: Mode=%o UID=%d GID=%d Link="%s', + self::HEADER_ID, + $this->mode, + $this->uid, + $this->gid, + $this->link + ); + } +} diff --git a/vendor/nelexa/zip/src/Model/Extra/Fields/ExtendedTimestampExtraField.php b/vendor/nelexa/zip/src/Model/Extra/Fields/ExtendedTimestampExtraField.php new file mode 100644 index 0000000..909c783 --- /dev/null +++ b/vendor/nelexa/zip/src/Model/Extra/Fields/ExtendedTimestampExtraField.php @@ -0,0 +1,446 @@ +flags = (int) $flags; + $this->modifyTime = $modifyTime; + $this->accessTime = $accessTime; + $this->createTime = $createTime; + } + + /** + * @param int|null $modifyTime + * @param int|null $accessTime + * @param int|null $createTime + * + * @return ExtendedTimestampExtraField + */ + public static function create($modifyTime, $accessTime, $createTime) + { + $flags = 0; + + if ($modifyTime !== null) { + $modifyTime = (int) $modifyTime; + $flags |= self::MODIFY_TIME_BIT; + } + + if ($accessTime !== null) { + $accessTime = (int) $accessTime; + $flags |= self::ACCESS_TIME_BIT; + } + + if ($createTime !== null) { + $createTime = (int) $createTime; + $flags |= self::CREATE_TIME_BIT; + } + + return new self($flags, $modifyTime, $accessTime, $createTime); + } + + /** + * Returns the Header ID (type) of this Extra Field. + * The Header ID is an unsigned short integer (two bytes) + * which must be constant during the life cycle of this object. + * + * @return int + */ + public function getHeaderId() + { + return self::HEADER_ID; + } + + /** + * Populate data from this array as if it was in local file data. + * + * @param string $buffer the buffer to read data from + * @param ZipEntry|null $entry + * + * @return ExtendedTimestampExtraField + */ + public static function unpackLocalFileData($buffer, ZipEntry $entry = null) + { + $length = \strlen($buffer); + $flags = unpack('C', $buffer)[1]; + $offset = 1; + + $modifyTime = null; + $accessTime = null; + $createTime = null; + + if (($flags & self::MODIFY_TIME_BIT) === self::MODIFY_TIME_BIT) { + $modifyTime = unpack('V', substr($buffer, $offset, 4))[1]; + $offset += 4; + } + + // Notice the extra length check in case we are parsing the shorter + // central data field (for both access and create timestamps). + if ((($flags & self::ACCESS_TIME_BIT) === self::ACCESS_TIME_BIT) && $offset + 4 <= $length) { + $accessTime = unpack('V', substr($buffer, $offset, 4))[1]; + $offset += 4; + } + + if ((($flags & self::CREATE_TIME_BIT) === self::CREATE_TIME_BIT) && $offset + 4 <= $length) { + $createTime = unpack('V', substr($buffer, $offset, 4))[1]; + } + + return new self($flags, $modifyTime, $accessTime, $createTime); + } + + /** + * Populate data from this array as if it was in central directory data. + * + * @param string $buffer the buffer to read data from + * @param ZipEntry|null $entry + * + * @return ExtendedTimestampExtraField + */ + public static function unpackCentralDirData($buffer, ZipEntry $entry = null) + { + return self::unpackLocalFileData($buffer, $entry); + } + + /** + * The actual data to put into local file data - without Header-ID + * or length specifier. + * + * @return string the data + */ + public function packLocalFileData() + { + $data = ''; + + if (($this->flags & self::MODIFY_TIME_BIT) === self::MODIFY_TIME_BIT && $this->modifyTime !== null) { + $data .= pack('V', $this->modifyTime); + } + + if (($this->flags & self::ACCESS_TIME_BIT) === self::ACCESS_TIME_BIT && $this->accessTime !== null) { + $data .= pack('V', $this->accessTime); + } + + if (($this->flags & self::CREATE_TIME_BIT) === self::CREATE_TIME_BIT && $this->createTime !== null) { + $data .= pack('V', $this->createTime); + } + + return pack('C', $this->flags) . $data; + } + + /** + * The actual data to put into central directory - without Header-ID or + * length specifier. + * + * Note: even if bit1 and bit2 are set, the Central data will still + * not contain access/create fields: only local data ever holds those! + * + * @return string the data + */ + public function packCentralDirData() + { + $cdLength = 1 + ($this->modifyTime !== null ? 4 : 0); + + return substr($this->packLocalFileData(), 0, $cdLength); + } + + /** + * Gets flags byte. + * + * The flags byte tells us which of the three datestamp fields are + * present in the data: + * bit0 - modify time + * bit1 - access time + * bit2 - create time + * + * Only first 3 bits of flags are used according to the + * latest version of the spec (December 2012). + * + * @return int flags byte indicating which of the + * three datestamp fields are present + */ + public function getFlags() + { + return $this->flags; + } + + /** + * Returns the modify time (seconds since epoch) of this zip entry, + * or null if no such timestamp exists in the zip entry. + * + * @return int|null modify time (seconds since epoch) or null + */ + public function getModifyTime() + { + return $this->modifyTime; + } + + /** + * Returns the access time (seconds since epoch) of this zip entry, + * or null if no such timestamp exists in the zip entry. + * + * @return int|null access time (seconds since epoch) or null + */ + public function getAccessTime() + { + return $this->accessTime; + } + + /** + * Returns the create time (seconds since epoch) of this zip entry, + * or null if no such timestamp exists in the zip entry. + * + * Note: modern linux file systems (e.g., ext2) + * do not appear to store a "create time" value, and so + * it's usually omitted altogether in the zip extra + * field. Perhaps other unix systems track this. + * + * @return int|null create time (seconds since epoch) or null + */ + public function getCreateTime() + { + return $this->createTime; + } + + /** + * Returns the modify time as a \DateTimeInterface + * of this zip entry, or null if no such timestamp exists in the zip entry. + * The milliseconds are always zeroed out, since the underlying data + * offers only per-second precision. + * + * @return \DateTimeInterface|null modify time as \DateTimeInterface or null + */ + public function getModifyDateTime() + { + return self::timestampToDateTime($this->modifyTime); + } + + /** + * Returns the access time as a \DateTimeInterface + * of this zip entry, or null if no such timestamp exists in the zip entry. + * The milliseconds are always zeroed out, since the underlying data + * offers only per-second precision. + * + * @return \DateTimeInterface|null access time as \DateTimeInterface or null + */ + public function getAccessDateTime() + { + return self::timestampToDateTime($this->accessTime); + } + + /** + * Returns the create time as a a \DateTimeInterface + * of this zip entry, or null if no such timestamp exists in the zip entry. + * The milliseconds are always zeroed out, since the underlying data + * offers only per-second precision. + * + * Note: modern linux file systems (e.g., ext2) + * do not appear to store a "create time" value, and so + * it's usually omitted altogether in the zip extra + * field. Perhaps other unix systems track $this->. + * + * @return \DateTimeInterface|null create time as \DateTimeInterface or null + */ + public function getCreateDateTime() + { + return self::timestampToDateTime($this->createTime); + } + + /** + * Sets the modify time (seconds since epoch) of this zip entry + * using a integer. + * + * @param int|null $unixTime unix time of the modify time (seconds per epoch) or null + */ + public function setModifyTime($unixTime) + { + $this->modifyTime = $unixTime; + $this->updateFlags(); + } + + private function updateFlags() + { + $flags = 0; + + if ($this->modifyTime !== null) { + $flags |= self::MODIFY_TIME_BIT; + } + + if ($this->accessTime !== null) { + $flags |= self::ACCESS_TIME_BIT; + } + + if ($this->createTime !== null) { + $flags |= self::CREATE_TIME_BIT; + } + $this->flags = $flags; + } + + /** + * Sets the access time (seconds since epoch) of this zip entry + * using a integer. + * + * @param int|null $unixTime Unix time of the access time (seconds per epoch) or null + */ + public function setAccessTime($unixTime) + { + $this->accessTime = $unixTime; + $this->updateFlags(); + } + + /** + * Sets the create time (seconds since epoch) of this zip entry + * using a integer. + * + * @param int|null $unixTime Unix time of the create time (seconds per epoch) or null + */ + public function setCreateTime($unixTime) + { + $this->createTime = $unixTime; + $this->updateFlags(); + } + + /** + * @param int|null $timestamp + * + * @return \DateTimeInterface|null + */ + private static function timestampToDateTime($timestamp) + { + try { + return $timestamp !== null ? new \DateTimeImmutable('@' . $timestamp) : null; + } catch (\Exception $e) { + return null; + } + } + + /** + * @return string + */ + public function __toString() + { + $args = [self::HEADER_ID]; + $format = '0x%04x ExtendedTimestamp:'; + + if ($this->modifyTime !== null) { + $format .= ' Modify:[%s]'; + $args[] = date(\DATE_W3C, $this->modifyTime); + } + + if ($this->accessTime !== null) { + $format .= ' Access:[%s]'; + $args[] = date(\DATE_W3C, $this->accessTime); + } + + if ($this->createTime !== null) { + $format .= ' Create:[%s]'; + $args[] = date(\DATE_W3C, $this->createTime); + } + + return vsprintf($format, $args); + } +} diff --git a/vendor/nelexa/zip/src/Model/Extra/Fields/JarMarkerExtraField.php b/vendor/nelexa/zip/src/Model/Extra/Fields/JarMarkerExtraField.php new file mode 100644 index 0000000..e1683aa --- /dev/null +++ b/vendor/nelexa/zip/src/Model/Extra/Fields/JarMarkerExtraField.php @@ -0,0 +1,118 @@ +getEntries(); + + if (!empty($zipEntries)) { + foreach ($zipEntries as $zipEntry) { + $zipEntry->removeExtraField(self::HEADER_ID); + } + // set jar execute bit + reset($zipEntries); + $zipEntry = current($zipEntries); + $zipEntry->getCdExtraFields()[] = new self(); + } + } + + /** + * Returns the Header ID (type) of this Extra Field. + * The Header ID is an unsigned short integer (two bytes) + * which must be constant during the life cycle of this object. + * + * @return int + */ + public function getHeaderId() + { + return self::HEADER_ID; + } + + /** + * The actual data to put into local file data - without Header-ID + * or length specifier. + * + * @return string the data + */ + public function packLocalFileData() + { + return ''; + } + + /** + * The actual data to put into central directory - without Header-ID or + * length specifier. + * + * @return string the data + */ + public function packCentralDirData() + { + return ''; + } + + /** + * Populate data from this array as if it was in local file data. + * + * @param string $buffer the buffer to read data from + * @param ZipEntry|null $entry + * + * @throws ZipException on error + * + * @return JarMarkerExtraField + */ + public static function unpackLocalFileData($buffer, ZipEntry $entry = null) + { + if (!empty($buffer)) { + throw new ZipException("JarMarker doesn't expect any data"); + } + + return new self(); + } + + /** + * Populate data from this array as if it was in central directory data. + * + * @param string $buffer the buffer to read data from + * @param ZipEntry|null $entry + * + * @throws ZipException on error + * + * @return JarMarkerExtraField + */ + public static function unpackCentralDirData($buffer, ZipEntry $entry = null) + { + return self::unpackLocalFileData($buffer, $entry); + } + + /** + * @return string + */ + public function __toString() + { + return sprintf('0x%04x Jar Marker', self::HEADER_ID); + } +} diff --git a/vendor/nelexa/zip/src/Model/Extra/Fields/NewUnixExtraField.php b/vendor/nelexa/zip/src/Model/Extra/Fields/NewUnixExtraField.php new file mode 100644 index 0000000..807de72 --- /dev/null +++ b/vendor/nelexa/zip/src/Model/Extra/Fields/NewUnixExtraField.php @@ -0,0 +1,237 @@ +version = (int) $version; + $this->uid = (int) $uid; + $this->gid = (int) $gid; + } + + /** + * Returns the Header ID (type) of this Extra Field. + * The Header ID is an unsigned short integer (two bytes) + * which must be constant during the life cycle of this object. + * + * @return int + */ + public function getHeaderId() + { + return self::HEADER_ID; + } + + /** + * Populate data from this array as if it was in local file data. + * + * @param string $buffer the buffer to read data from + * @param ZipEntry|null $entry + * + * @throws ZipException + * + * @return NewUnixExtraField + */ + public static function unpackLocalFileData($buffer, ZipEntry $entry = null) + { + $length = \strlen($buffer); + + if ($length < 3) { + throw new ZipException(sprintf('X7875_NewUnix length is too short, only %s bytes', $length)); + } + $offset = 0; + $data = unpack('Cversion/CuidSize', $buffer); + $offset += 2; + $uidSize = $data['uidSize']; + $gid = self::readSizeIntegerLE(substr($buffer, $offset, $uidSize), $uidSize); + $offset += $uidSize; + $gidSize = unpack('C', $buffer[$offset])[1]; + $offset++; + $uid = self::readSizeIntegerLE(substr($buffer, $offset, $gidSize), $gidSize); + + return new self($data['version'], $gid, $uid); + } + + /** + * Populate data from this array as if it was in central directory data. + * + * @param string $buffer the buffer to read data from + * @param ZipEntry|null $entry + * + * @throws ZipException + * + * @return NewUnixExtraField + */ + public static function unpackCentralDirData($buffer, ZipEntry $entry = null) + { + return self::unpackLocalFileData($buffer, $entry); + } + + /** + * The actual data to put into local file data - without Header-ID + * or length specifier. + * + * @return string the data + */ + public function packLocalFileData() + { + return pack( + 'CCVCV', + $this->version, + 4, // UIDSize + $this->uid, + 4, // GIDSize + $this->gid + ); + } + + /** + * The actual data to put into central directory - without Header-ID or + * length specifier. + * + * @return string the data + */ + public function packCentralDirData() + { + return $this->packLocalFileData(); + } + + /** + * @param string $data + * @param int $size + * + * @throws ZipException + * + * @return int + */ + private static function readSizeIntegerLE($data, $size) + { + $format = [ + 1 => 'C', // unsigned byte + 2 => 'v', // unsigned short LE + 4 => 'V', // unsigned int LE + ]; + + if (!isset($format[$size])) { + throw new ZipException(sprintf('Invalid size bytes: %d', $size)); + } + + return unpack($format[$size], $data)[1]; + } + + /** + * @return int + */ + public function getUid() + { + return $this->uid; + } + + /** + * @param int $uid + */ + public function setUid($uid) + { + $this->uid = $uid & 0xffffffff; + } + + /** + * @return int + */ + public function getGid() + { + return $this->gid; + } + + /** + * @param int $gid + */ + public function setGid($gid) + { + $this->gid = $gid & 0xffffffff; + } + + /** + * @return int + */ + public function getVersion() + { + return $this->version; + } + + /** + * @return string + */ + public function __toString() + { + return sprintf( + '0x%04x NewUnix: UID=%d GID=%d', + self::HEADER_ID, + $this->uid, + $this->gid + ); + } +} diff --git a/vendor/nelexa/zip/src/Model/Extra/Fields/NtfsExtraField.php b/vendor/nelexa/zip/src/Model/Extra/Fields/NtfsExtraField.php new file mode 100644 index 0000000..213e925 --- /dev/null +++ b/vendor/nelexa/zip/src/Model/Extra/Fields/NtfsExtraField.php @@ -0,0 +1,339 @@ +modifyNtfsTime = (int) $modifyNtfsTime; + $this->accessNtfsTime = (int) $accessNtfsTime; + $this->createNtfsTime = (int) $createNtfsTime; + } + + /** + * @param \DateTimeInterface $modifyDateTime + * @param \DateTimeInterface $accessDateTime + * @param \DateTimeInterface $createNtfsTime + * + * @return NtfsExtraField + */ + public static function create( + \DateTimeInterface $modifyDateTime, + \DateTimeInterface $accessDateTime, + \DateTimeInterface $createNtfsTime + ) { + return new self( + self::dateTimeToNtfsTime($modifyDateTime), + self::dateTimeToNtfsTime($accessDateTime), + self::dateTimeToNtfsTime($createNtfsTime) + ); + } + + /** + * Returns the Header ID (type) of this Extra Field. + * The Header ID is an unsigned short integer (two bytes) + * which must be constant during the life cycle of this object. + * + * @return int + */ + public function getHeaderId() + { + return self::HEADER_ID; + } + + /** + * Populate data from this array as if it was in local file data. + * + * @param string $buffer the buffer to read data from + * @param ZipEntry|null $entry + * + * @throws ZipException + * + * @return NtfsExtraField + */ + public static function unpackLocalFileData($buffer, ZipEntry $entry = null) + { + if (\PHP_INT_SIZE === 4) { + throw new ZipException('not supported for php-32bit'); + } + + $buffer = substr($buffer, 4); + + $modifyTime = 0; + $accessTime = 0; + $createTime = 0; + + while ($buffer || $buffer !== '') { + $unpack = unpack('vtag/vsizeAttr', $buffer); + + if ($unpack['tag'] === self::TIME_ATTR_TAG && $unpack['sizeAttr'] === self::TIME_ATTR_SIZE) { + // refactoring will be needed when php 5.5 support ends + $modifyTime = PackUtil::unpackLongLE(substr($buffer, 4, 8)); + $accessTime = PackUtil::unpackLongLE(substr($buffer, 12, 8)); + $createTime = PackUtil::unpackLongLE(substr($buffer, 20, 8)); + + break; + } + $buffer = substr($buffer, 4 + $unpack['sizeAttr']); + } + + return new self($modifyTime, $accessTime, $createTime); + } + + /** + * Populate data from this array as if it was in central directory data. + * + * @param string $buffer the buffer to read data from + * @param ZipEntry|null $entry + * + * @throws ZipException + * + * @return NtfsExtraField + */ + public static function unpackCentralDirData($buffer, ZipEntry $entry = null) + { + return self::unpackLocalFileData($buffer, $entry); + } + + /** + * The actual data to put into local file data - without Header-ID + * or length specifier. + * + * @return string the data + */ + public function packLocalFileData() + { + $data = pack('Vvv', 0, self::TIME_ATTR_TAG, self::TIME_ATTR_SIZE); + // refactoring will be needed when php 5.5 support ends + $data .= PackUtil::packLongLE($this->modifyNtfsTime); + $data .= PackUtil::packLongLE($this->accessNtfsTime); + $data .= PackUtil::packLongLE($this->createNtfsTime); + + return $data; + } + + /** + * @return int + */ + public function getModifyNtfsTime() + { + return $this->modifyNtfsTime; + } + + /** + * @param int $modifyNtfsTime + */ + public function setModifyNtfsTime($modifyNtfsTime) + { + $this->modifyNtfsTime = (int) $modifyNtfsTime; + } + + /** + * @return int + */ + public function getAccessNtfsTime() + { + return $this->accessNtfsTime; + } + + /** + * @param int $accessNtfsTime + */ + public function setAccessNtfsTime($accessNtfsTime) + { + $this->accessNtfsTime = (int) $accessNtfsTime; + } + + /** + * @return int + */ + public function getCreateNtfsTime() + { + return $this->createNtfsTime; + } + + /** + * @param int $createNtfsTime + */ + public function setCreateNtfsTime($createNtfsTime) + { + $this->createNtfsTime = (int) $createNtfsTime; + } + + /** + * The actual data to put into central directory - without Header-ID or + * length specifier. + * + * @return string the data + */ + public function packCentralDirData() + { + return $this->packLocalFileData(); + } + + /** + * @return \DateTimeInterface + */ + public function getModifyDateTime() + { + return self::ntfsTimeToDateTime($this->modifyNtfsTime); + } + + /** + * @param \DateTimeInterface $modifyTime + */ + public function setModifyDateTime(\DateTimeInterface $modifyTime) + { + $this->modifyNtfsTime = self::dateTimeToNtfsTime($modifyTime); + } + + /** + * @return \DateTimeInterface + */ + public function getAccessDateTime() + { + return self::ntfsTimeToDateTime($this->accessNtfsTime); + } + + /** + * @param \DateTimeInterface $accessTime + */ + public function setAccessDateTime(\DateTimeInterface $accessTime) + { + $this->accessNtfsTime = self::dateTimeToNtfsTime($accessTime); + } + + /** + * @return \DateTimeInterface + */ + public function getCreateDateTime() + { + return self::ntfsTimeToDateTime($this->createNtfsTime); + } + + /** + * @param \DateTimeInterface $createTime + */ + public function setCreateDateTime(\DateTimeInterface $createTime) + { + $this->createNtfsTime = self::dateTimeToNtfsTime($createTime); + } + + /** + * @param float $timestamp Float timestamp + * + * @return int + */ + public static function timestampToNtfsTime($timestamp) + { + return (int) (((float) $timestamp * 10000000) - self::EPOCH_OFFSET); + } + + /** + * @param \DateTimeInterface $dateTime + * + * @return int + */ + public static function dateTimeToNtfsTime(\DateTimeInterface $dateTime) + { + return self::timestampToNtfsTime((float) $dateTime->format('U.u')); + } + + /** + * @param int $ntfsTime + * + * @return float Float unix timestamp + */ + public static function ntfsTimeToTimestamp($ntfsTime) + { + return (float) (($ntfsTime + self::EPOCH_OFFSET) / 10000000); + } + + /** + * @param int $ntfsTime + * + * @return \DateTimeInterface + */ + public static function ntfsTimeToDateTime($ntfsTime) + { + $timestamp = self::ntfsTimeToTimestamp($ntfsTime); + $dateTime = \DateTimeImmutable::createFromFormat('U.u', sprintf('%.6f', $timestamp)); + + if ($dateTime === false) { + throw new InvalidArgumentException('Cannot create date/time object for timestamp ' . $timestamp); + } + + return $dateTime; + } + + /** + * @return string + */ + public function __toString() + { + $args = [self::HEADER_ID]; + $format = '0x%04x NtfsExtra:'; + + if ($this->modifyNtfsTime !== 0) { + $format .= ' Modify:[%s]'; + $args[] = $this->getModifyDateTime()->format(\DATE_ATOM); + } + + if ($this->accessNtfsTime !== 0) { + $format .= ' Access:[%s]'; + $args[] = $this->getAccessDateTime()->format(\DATE_ATOM); + } + + if ($this->createNtfsTime !== 0) { + $format .= ' Create:[%s]'; + $args[] = $this->getCreateDateTime()->format(\DATE_ATOM); + } + + return vsprintf($format, $args); + } +} diff --git a/vendor/nelexa/zip/src/Model/Extra/Fields/OldUnixExtraField.php b/vendor/nelexa/zip/src/Model/Extra/Fields/OldUnixExtraField.php new file mode 100644 index 0000000..1f69487 --- /dev/null +++ b/vendor/nelexa/zip/src/Model/Extra/Fields/OldUnixExtraField.php @@ -0,0 +1,327 @@ +accessTime = $accessTime; + $this->modifyTime = $modifyTime; + $this->uid = $uid; + $this->gid = $gid; + } + + /** + * Returns the Header ID (type) of this Extra Field. + * The Header ID is an unsigned short integer (two bytes) + * which must be constant during the life cycle of this object. + * + * @return int + */ + public function getHeaderId() + { + return self::HEADER_ID; + } + + /** + * Populate data from this array as if it was in local file data. + * + * @param string $buffer the buffer to read data from + * @param ZipEntry|null $entry + * + * @return OldUnixExtraField + */ + public static function unpackLocalFileData($buffer, ZipEntry $entry = null) + { + $length = \strlen($buffer); + + $accessTime = $modifyTime = $uid = $gid = null; + + if ($length >= 4) { + $accessTime = unpack('V', $buffer)[1]; + } + + if ($length >= 8) { + $modifyTime = unpack('V', substr($buffer, 4, 4))[1]; + } + + if ($length >= 10) { + $uid = unpack('v', substr($buffer, 8, 2))[1]; + } + + if ($length >= 12) { + $gid = unpack('v', substr($buffer, 10, 2))[1]; + } + + return new self($accessTime, $modifyTime, $uid, $gid); + } + + /** + * Populate data from this array as if it was in central directory data. + * + * @param string $buffer the buffer to read data from + * @param ZipEntry|null $entry + * + * @return OldUnixExtraField + */ + public static function unpackCentralDirData($buffer, ZipEntry $entry = null) + { + $length = \strlen($buffer); + + $accessTime = $modifyTime = null; + + if ($length >= 4) { + $accessTime = unpack('V', $buffer)[1]; + } + + if ($length >= 8) { + $modifyTime = unpack('V', substr($buffer, 4, 4))[1]; + } + + return new self($accessTime, $modifyTime, null, null); + } + + /** + * The actual data to put into local file data - without Header-ID + * or length specifier. + * + * @return string the data + */ + public function packLocalFileData() + { + $data = ''; + + if ($this->accessTime !== null) { + $data .= pack('V', $this->accessTime); + + if ($this->modifyTime !== null) { + $data .= pack('V', $this->modifyTime); + + if ($this->uid !== null) { + $data .= pack('v', $this->uid); + + if ($this->gid !== null) { + $data .= pack('v', $this->gid); + } + } + } + } + + return $data; + } + + /** + * The actual data to put into central directory - without Header-ID or + * length specifier. + * + * @return string the data + */ + public function packCentralDirData() + { + $data = ''; + + if ($this->accessTime !== null) { + $data .= pack('V', $this->accessTime); + + if ($this->modifyTime !== null) { + $data .= pack('V', $this->modifyTime); + } + } + + return $data; + } + + /** + * @return int|null + */ + public function getAccessTime() + { + return $this->accessTime; + } + + /** + * @param int|null $accessTime + */ + public function setAccessTime($accessTime) + { + $this->accessTime = $accessTime; + } + + /** + * @return \DateTimeInterface|null + */ + public function getAccessDateTime() + { + try { + return $this->accessTime === null ? null : + new \DateTimeImmutable('@' . $this->accessTime); + } catch (\Exception $e) { + return null; + } + } + + /** + * @return int|null + */ + public function getModifyTime() + { + return $this->modifyTime; + } + + /** + * @param int|null $modifyTime + */ + public function setModifyTime($modifyTime) + { + $this->modifyTime = $modifyTime; + } + + /** + * @return \DateTimeInterface|null + */ + public function getModifyDateTime() + { + try { + return $this->modifyTime === null ? null : + new \DateTimeImmutable('@' . $this->modifyTime); + } catch (\Exception $e) { + return null; + } + } + + /** + * @return int|null + */ + public function getUid() + { + return $this->uid; + } + + /** + * @param int|null $uid + */ + public function setUid($uid) + { + $this->uid = $uid; + } + + /** + * @return int|null + */ + public function getGid() + { + return $this->gid; + } + + /** + * @param int|null $gid + */ + public function setGid($gid) + { + $this->gid = $gid; + } + + /** + * @return string + */ + public function __toString() + { + $args = [self::HEADER_ID]; + $format = '0x%04x OldUnix:'; + + if (($modifyTime = $this->getModifyDateTime()) !== null) { + $format .= ' Modify:[%s]'; + $args[] = $modifyTime->format(\DATE_ATOM); + } + + if (($accessTime = $this->getAccessDateTime()) !== null) { + $format .= ' Access:[%s]'; + $args[] = $accessTime->format(\DATE_ATOM); + } + + if ($this->uid !== null) { + $format .= ' UID=%d'; + $args[] = $this->uid; + } + + if ($this->gid !== null) { + $format .= ' GID=%d'; + $args[] = $this->gid; + } + + return vsprintf($format, $args); + } +} diff --git a/vendor/nelexa/zip/src/Model/Extra/Fields/UnicodeCommentExtraField.php b/vendor/nelexa/zip/src/Model/Extra/Fields/UnicodeCommentExtraField.php new file mode 100644 index 0000000..d8280b6 --- /dev/null +++ b/vendor/nelexa/zip/src/Model/Extra/Fields/UnicodeCommentExtraField.php @@ -0,0 +1,76 @@ +getUnicodeValue() + ); + } +} diff --git a/vendor/nelexa/zip/src/Model/Extra/Fields/UnicodePathExtraField.php b/vendor/nelexa/zip/src/Model/Extra/Fields/UnicodePathExtraField.php new file mode 100644 index 0000000..047b2d0 --- /dev/null +++ b/vendor/nelexa/zip/src/Model/Extra/Fields/UnicodePathExtraField.php @@ -0,0 +1,77 @@ +getUnicodeValue() + ); + } +} diff --git a/vendor/nelexa/zip/src/Model/Extra/Fields/UnrecognizedExtraField.php b/vendor/nelexa/zip/src/Model/Extra/Fields/UnrecognizedExtraField.php new file mode 100644 index 0000000..699c193 --- /dev/null +++ b/vendor/nelexa/zip/src/Model/Extra/Fields/UnrecognizedExtraField.php @@ -0,0 +1,116 @@ +headerId = (int) $headerId; + $this->data = (string) $data; + } + + /** + * @param int $headerId + */ + public function setHeaderId($headerId) + { + $this->headerId = $headerId; + } + + /** + * Returns the Header ID (type) of this Extra Field. + * The Header ID is an unsigned short integer (two bytes) + * which must be constant during the life cycle of this object. + * + * @return int + */ + public function getHeaderId() + { + return $this->headerId; + } + + /** + * Populate data from this array as if it was in local file data. + * + * @param string $buffer the buffer to read data from + * @param ZipEntry|null $entry + */ + public static function unpackLocalFileData($buffer, ZipEntry $entry = null) + { + throw new RuntimeException('Unsupport parse'); + } + + /** + * Populate data from this array as if it was in central directory data. + * + * @param string $buffer the buffer to read data from + * @param ZipEntry|null $entry + */ + public static function unpackCentralDirData($buffer, ZipEntry $entry = null) + { + throw new RuntimeException('Unsupport parse'); + } + + /** + * {@inheritdoc} + */ + public function packLocalFileData() + { + return $this->data; + } + + /** + * {@inheritdoc} + */ + public function packCentralDirData() + { + return $this->data; + } + + /** + * @return string + */ + public function getData() + { + return $this->data; + } + + /** + * @param string $data + */ + public function setData($data) + { + $this->data = (string) $data; + } + + /** + * @return string + */ + public function __toString() + { + $args = [$this->headerId, $this->data]; + $format = '0x%04x Unrecognized Extra Field: "%s"'; + + return vsprintf($format, $args); + } +} diff --git a/vendor/nelexa/zip/src/Model/Extra/Fields/WinZipAesExtraField.php b/vendor/nelexa/zip/src/Model/Extra/Fields/WinZipAesExtraField.php new file mode 100644 index 0000000..0a50dca --- /dev/null +++ b/vendor/nelexa/zip/src/Model/Extra/Fields/WinZipAesExtraField.php @@ -0,0 +1,387 @@ + */ + private static $encryptionStrengths = [ + self::KEY_STRENGTH_128BIT => 128, + self::KEY_STRENGTH_192BIT => 192, + self::KEY_STRENGTH_256BIT => 256, + ]; + + /** @var array */ + private static $MAP_KEY_STRENGTH_METHODS = [ + self::KEY_STRENGTH_128BIT => ZipEncryptionMethod::WINZIP_AES_128, + self::KEY_STRENGTH_192BIT => ZipEncryptionMethod::WINZIP_AES_192, + self::KEY_STRENGTH_256BIT => ZipEncryptionMethod::WINZIP_AES_256, + ]; + + /** @var int Integer version number specific to the zip vendor */ + private $vendorVersion = self::VERSION_AE1; + + /** @var int Integer mode value indicating AES encryption strength */ + private $keyStrength = self::KEY_STRENGTH_256BIT; + + /** @var int The actual compression method used to compress the file */ + private $compressionMethod; + + /** + * @param int $vendorVersion Integer version number specific to the zip vendor + * @param int $keyStrength Integer mode value indicating AES encryption strength + * @param int $compressionMethod The actual compression method used to compress the file + * + * @throws ZipUnsupportMethodException + */ + public function __construct($vendorVersion, $keyStrength, $compressionMethod) + { + $this->setVendorVersion($vendorVersion); + $this->setKeyStrength($keyStrength); + $this->setCompressionMethod($compressionMethod); + } + + /** + * @param ZipEntry $entry + * + * @throws ZipUnsupportMethodException + * + * @return WinZipAesExtraField + */ + public static function create(ZipEntry $entry) + { + $keyStrength = array_search($entry->getEncryptionMethod(), self::$MAP_KEY_STRENGTH_METHODS, true); + + if ($keyStrength === false) { + throw new InvalidArgumentException('Not support encryption method ' . $entry->getEncryptionMethod()); + } + + // WinZip 11 will continue to use AE-2, with no CRC, for very small files + // of less than 20 bytes. It will also use AE-2 for files compressed in + // BZIP2 format, because this format has internal integrity checks + // equivalent to a CRC check built in. + // + // https://www.winzip.com/win/en/aes_info.html + $vendorVersion = ( + $entry->getUncompressedSize() < 20 || + $entry->getCompressionMethod() === ZipCompressionMethod::BZIP2 + ) ? + self::VERSION_AE2 : + self::VERSION_AE1; + + $field = new self($vendorVersion, $keyStrength, $entry->getCompressionMethod()); + + $entry->getLocalExtraFields()->add($field); + $entry->getCdExtraFields()->add($field); + + return $field; + } + + /** + * Returns the Header ID (type) of this Extra Field. + * The Header ID is an unsigned short integer (two bytes) + * which must be constant during the life cycle of this object. + * + * @return int + */ + public function getHeaderId() + { + return self::HEADER_ID; + } + + /** + * Populate data from this array as if it was in local file data. + * + * @param string $buffer the buffer to read data from + * @param ZipEntry|null $entry + * + * @throws ZipException on error + * + * @return WinZipAesExtraField + */ + public static function unpackLocalFileData($buffer, ZipEntry $entry = null) + { + $size = \strlen($buffer); + + if ($size !== self::DATA_SIZE) { + throw new ZipException( + sprintf( + 'WinZip AES Extra data invalid size: %d. Must be %d', + $size, + self::DATA_SIZE + ) + ); + } + + $data = unpack('vvendorVersion/vvendorId/ckeyStrength/vcompressionMethod', $buffer); + + if ($data['vendorId'] !== self::VENDOR_ID) { + throw new ZipException( + sprintf( + 'Vendor id invalid: %d. Must be %d', + $data['vendorId'], + self::VENDOR_ID + ) + ); + } + + return new self( + $data['vendorVersion'], + $data['keyStrength'], + $data['compressionMethod'] + ); + } + + /** + * Populate data from this array as if it was in central directory data. + * + * @param string $buffer the buffer to read data from + * @param ZipEntry|null $entry + * + * @throws ZipException + * + * @return WinZipAesExtraField + */ + public static function unpackCentralDirData($buffer, ZipEntry $entry = null) + { + return self::unpackLocalFileData($buffer, $entry); + } + + /** + * The actual data to put into local file data - without Header-ID + * or length specifier. + * + * @return string the data + */ + public function packLocalFileData() + { + return pack( + 'vvcv', + $this->vendorVersion, + self::VENDOR_ID, + $this->keyStrength, + $this->compressionMethod + ); + } + + /** + * The actual data to put into central directory - without Header-ID or + * length specifier. + * + * @return string the data + */ + public function packCentralDirData() + { + return $this->packLocalFileData(); + } + + /** + * Returns the vendor version. + * + * @return int + * + * @see WinZipAesExtraField::VERSION_AE2 + * @see WinZipAesExtraField::VERSION_AE1 + */ + public function getVendorVersion() + { + return $this->vendorVersion; + } + + /** + * Sets the vendor version. + * + * @param int $vendorVersion the vendor version + * + * @see WinZipAesExtraField::VERSION_AE2 + * @see WinZipAesExtraField::VERSION_AE1 + */ + public function setVendorVersion($vendorVersion) + { + $vendorVersion = (int) $vendorVersion; + + if (!\in_array($vendorVersion, self::$allowVendorVersions, true)) { + throw new InvalidArgumentException( + sprintf( + 'Unsupport WinZip AES vendor version: %d', + $vendorVersion + ) + ); + } + $this->vendorVersion = $vendorVersion; + } + + /** + * Returns vendor id. + * + * @return int + */ + public function getVendorId() + { + return self::VENDOR_ID; + } + + /** + * @return int + */ + public function getKeyStrength() + { + return $this->keyStrength; + } + + /** + * Set key strength. + * + * @param int $keyStrength + */ + public function setKeyStrength($keyStrength) + { + $keyStrength = (int) $keyStrength; + + if (!isset(self::$encryptionStrengths[$keyStrength])) { + throw new InvalidArgumentException( + sprintf( + 'Key strength %d not support value. Allow values: %s', + $keyStrength, + implode(', ', array_keys(self::$encryptionStrengths)) + ) + ); + } + $this->keyStrength = $keyStrength; + } + + /** + * @return int + */ + public function getCompressionMethod() + { + return $this->compressionMethod; + } + + /** + * @param int $compressionMethod + * + * @throws ZipUnsupportMethodException + */ + public function setCompressionMethod($compressionMethod) + { + $compressionMethod = (int) $compressionMethod; + ZipCompressionMethod::checkSupport($compressionMethod); + $this->compressionMethod = $compressionMethod; + } + + /** + * @return int + */ + public function getEncryptionStrength() + { + return self::$encryptionStrengths[$this->keyStrength]; + } + + /** + * @return int + */ + public function getEncryptionMethod() + { + $keyStrength = $this->getKeyStrength(); + + if (!isset(self::$MAP_KEY_STRENGTH_METHODS[$keyStrength])) { + throw new InvalidArgumentException('Invalid encryption method'); + } + + return self::$MAP_KEY_STRENGTH_METHODS[$keyStrength]; + } + + /** + * @return bool + */ + public function isV1() + { + return $this->vendorVersion === self::VERSION_AE1; + } + + /** + * @return bool + */ + public function isV2() + { + return $this->vendorVersion === self::VERSION_AE2; + } + + /** + * @return int + */ + public function getSaltSize() + { + return (int) ($this->getEncryptionStrength() / 8 / 2); + } + + /** + * @return string + */ + public function __toString() + { + return sprintf( + '0x%04x WINZIP AES: VendorVersion=%d KeyStrength=0x%02x CompressionMethod=%s', + __CLASS__, + $this->vendorVersion, + $this->keyStrength, + $this->compressionMethod + ); + } +} diff --git a/vendor/nelexa/zip/src/Model/Extra/Fields/Zip64ExtraField.php b/vendor/nelexa/zip/src/Model/Extra/Fields/Zip64ExtraField.php new file mode 100644 index 0000000..4393a9c --- /dev/null +++ b/vendor/nelexa/zip/src/Model/Extra/Fields/Zip64ExtraField.php @@ -0,0 +1,311 @@ +uncompressedSize = $uncompressedSize; + $this->compressedSize = $compressedSize; + $this->localHeaderOffset = $localHeaderOffset; + $this->diskStart = $diskStart; + } + + /** + * Returns the Header ID (type) of this Extra Field. + * The Header ID is an unsigned short integer (two bytes) + * which must be constant during the life cycle of this object. + * + * @return int + */ + public function getHeaderId() + { + return self::HEADER_ID; + } + + /** + * Populate data from this array as if it was in local file data. + * + * @param string $buffer the buffer to read data from + * @param ZipEntry|null $entry + * + * @throws ZipException on error + * + * @return Zip64ExtraField + */ + public static function unpackLocalFileData($buffer, ZipEntry $entry = null) + { + $length = \strlen($buffer); + + if ($length === 0) { + // no local file data at all, may happen if an archive + // only holds a ZIP64 extended information extra field + // inside the central directory but not inside the local + // file header + return new self(); + } + + if ($length < 16) { + throw new ZipException( + 'Zip64 extended information must contain both size values in the local file header.' + ); + } + + $uncompressedSize = PackUtil::unpackLongLE(substr($buffer, 0, 8)); + $compressedSize = PackUtil::unpackLongLE(substr($buffer, 8, 8)); + + return new self($uncompressedSize, $compressedSize); + } + + /** + * Populate data from this array as if it was in central directory data. + * + * @param string $buffer the buffer to read data from + * @param ZipEntry|null $entry + * + * @throws ZipException + * + * @return Zip64ExtraField + */ + public static function unpackCentralDirData($buffer, ZipEntry $entry = null) + { + if ($entry === null) { + throw new RuntimeException('zipEntry is null'); + } + + $length = \strlen($buffer); + $remaining = $length; + + $uncompressedSize = null; + $compressedSize = null; + $localHeaderOffset = null; + $diskStart = null; + + if ($entry->getUncompressedSize() === ZipConstants::ZIP64_MAGIC) { + if ($remaining < 8) { + throw new ZipException('ZIP64 extension corrupt (no uncompressed size).'); + } + $uncompressedSize = PackUtil::unpackLongLE(substr($buffer, $length - $remaining, 8)); + $remaining -= 8; + } + + if ($entry->getCompressedSize() === ZipConstants::ZIP64_MAGIC) { + if ($remaining < 8) { + throw new ZipException('ZIP64 extension corrupt (no compressed size).'); + } + $compressedSize = PackUtil::unpackLongLE(substr($buffer, $length - $remaining, 8)); + $remaining -= 8; + } + + if ($entry->getLocalHeaderOffset() === ZipConstants::ZIP64_MAGIC) { + if ($remaining < 8) { + throw new ZipException('ZIP64 extension corrupt (no relative local header offset).'); + } + $localHeaderOffset = PackUtil::unpackLongLE(substr($buffer, $length - $remaining, 8)); + $remaining -= 8; + } + + if ($remaining === 4) { + $diskStart = unpack('V', substr($buffer, $length - $remaining, 4))[1]; + } + + return new self($uncompressedSize, $compressedSize, $localHeaderOffset, $diskStart); + } + + /** + * The actual data to put into local file data - without Header-ID + * or length specifier. + * + * @return string the data + */ + public function packLocalFileData() + { + if ($this->uncompressedSize !== null || $this->compressedSize !== null) { + if ($this->uncompressedSize === null || $this->compressedSize === null) { + throw new \InvalidArgumentException( + 'Zip64 extended information must contain both size values in the local file header.' + ); + } + + return $this->packSizes(); + } + + return ''; + } + + /** + * @return string + */ + private function packSizes() + { + $data = ''; + + if ($this->uncompressedSize !== null) { + $data .= PackUtil::packLongLE($this->uncompressedSize); + } + + if ($this->compressedSize !== null) { + $data .= PackUtil::packLongLE($this->compressedSize); + } + + return $data; + } + + /** + * The actual data to put into central directory - without Header-ID or + * length specifier. + * + * @return string the data + */ + public function packCentralDirData() + { + $data = $this->packSizes(); + + if ($this->localHeaderOffset !== null) { + $data .= PackUtil::packLongLE($this->localHeaderOffset); + } + + if ($this->diskStart !== null) { + $data .= pack('V', $this->diskStart); + } + + return $data; + } + + /** + * @return int|null + */ + public function getUncompressedSize() + { + return $this->uncompressedSize; + } + + /** + * @param int|null $uncompressedSize + */ + public function setUncompressedSize($uncompressedSize) + { + $this->uncompressedSize = $uncompressedSize; + } + + /** + * @return int|null + */ + public function getCompressedSize() + { + return $this->compressedSize; + } + + /** + * @param int|null $compressedSize + */ + public function setCompressedSize($compressedSize) + { + $this->compressedSize = $compressedSize; + } + + /** + * @return int|null + */ + public function getLocalHeaderOffset() + { + return $this->localHeaderOffset; + } + + /** + * @param int|null $localHeaderOffset + */ + public function setLocalHeaderOffset($localHeaderOffset) + { + $this->localHeaderOffset = $localHeaderOffset; + } + + /** + * @return int|null + */ + public function getDiskStart() + { + return $this->diskStart; + } + + /** + * @param int|null $diskStart + */ + public function setDiskStart($diskStart) + { + $this->diskStart = $diskStart; + } + + /** + * @return string + */ + public function __toString() + { + $args = [self::HEADER_ID]; + $format = '0x%04x ZIP64: '; + $formats = []; + + if ($this->uncompressedSize !== null) { + $formats[] = 'SIZE=%d'; + $args[] = $this->uncompressedSize; + } + + if ($this->compressedSize !== null) { + $formats[] = 'COMP_SIZE=%d'; + $args[] = $this->compressedSize; + } + + if ($this->localHeaderOffset !== null) { + $formats[] = 'OFFSET=%d'; + $args[] = $this->localHeaderOffset; + } + + if ($this->diskStart !== null) { + $formats[] = 'DISK_START=%d'; + $args[] = $this->diskStart; + } + $format .= implode(' ', $formats); + + return vsprintf($format, $args); + } +} diff --git a/vendor/nelexa/zip/src/Model/Extra/ZipExtraDriver.php b/vendor/nelexa/zip/src/Model/Extra/ZipExtraDriver.php new file mode 100644 index 0000000..e1332f0 --- /dev/null +++ b/vendor/nelexa/zip/src/Model/Extra/ZipExtraDriver.php @@ -0,0 +1,107 @@ + + * @psalm-var array> + */ + private static $implementations = [ + ApkAlignmentExtraField::HEADER_ID => ApkAlignmentExtraField::class, + AsiExtraField::HEADER_ID => AsiExtraField::class, + ExtendedTimestampExtraField::HEADER_ID => ExtendedTimestampExtraField::class, + JarMarkerExtraField::HEADER_ID => JarMarkerExtraField::class, + NewUnixExtraField::HEADER_ID => NewUnixExtraField::class, + NtfsExtraField::HEADER_ID => NtfsExtraField::class, + OldUnixExtraField::HEADER_ID => OldUnixExtraField::class, + UnicodeCommentExtraField::HEADER_ID => UnicodeCommentExtraField::class, + UnicodePathExtraField::HEADER_ID => UnicodePathExtraField::class, + WinZipAesExtraField::HEADER_ID => WinZipAesExtraField::class, + Zip64ExtraField::HEADER_ID => Zip64ExtraField::class, + ]; + + private function __construct() + { + } + + /** + * @param string|ZipExtraField $extraField ZipExtraField object or class name + */ + public static function register($extraField) + { + if (!is_a($extraField, ZipExtraField::class, true)) { + throw new InvalidArgumentException( + sprintf( + '$extraField "%s" is not implements interface %s', + (string) $extraField, + ZipExtraField::class + ) + ); + } + self::$implementations[\call_user_func([$extraField, 'getHeaderId'])] = $extraField; + } + + /** + * @param int|string|ZipExtraField $extraType ZipExtraField object or class name or extra header id + * + * @return bool + */ + public static function unregister($extraType) + { + $headerId = null; + + if (\is_int($extraType)) { + $headerId = $extraType; + } elseif (is_a($extraType, ZipExtraField::class, true)) { + $headerId = \call_user_func([$extraType, 'getHeaderId']); + } else { + return false; + } + + if (isset(self::$implementations[$headerId])) { + unset(self::$implementations[$headerId]); + + return true; + } + + return false; + } + + /** + * @param int $headerId + * + * @return string|null + */ + public static function getClassNameOrNull($headerId) + { + $headerId = (int) $headerId; + + if ($headerId < 0 || $headerId > 0xffff) { + throw new \InvalidArgumentException('$headerId out of range: ' . $headerId); + } + + if (isset(self::$implementations[$headerId])) { + return self::$implementations[$headerId]; + } + + return null; + } +} diff --git a/vendor/nelexa/zip/src/Model/Extra/ZipExtraField.php b/vendor/nelexa/zip/src/Model/Extra/ZipExtraField.php new file mode 100644 index 0000000..ce69aaf --- /dev/null +++ b/vendor/nelexa/zip/src/Model/Extra/ZipExtraField.php @@ -0,0 +1,63 @@ +entries = $entries; + $this->archiveComment = $archiveComment; + } + + /** + * @return ZipEntry[] + */ + public function &getEntries() + { + return $this->entries; + } + + /** + * @return string|null + */ + public function getArchiveComment() + { + return $this->archiveComment; + } + + /** + * Count elements of an object. + * + * @see https://php.net/manual/en/countable.count.php + * + * @return int The custom count as an integer. + * The return value is cast to an integer. + */ + public function count() + { + return \count($this->entries); + } + + /** + * When an object is cloned, PHP 5 will perform a shallow copy of all of the object's properties. + * Any properties that are references to other variables, will remain references. + * Once the cloning is complete, if a __clone() method is defined, + * then the newly created object's __clone() method will be called, to allow any necessary properties that need to + * be changed. NOT CALLABLE DIRECTLY. + * + * @see https://php.net/manual/en/language.oop5.cloning.php + */ + public function __clone() + { + foreach ($this->entries as $key => $value) { + $this->entries[$key] = clone $value; + } + } +} diff --git a/vendor/nelexa/zip/src/Model/ZipContainer.php b/vendor/nelexa/zip/src/Model/ZipContainer.php new file mode 100644 index 0000000..6cfe87e --- /dev/null +++ b/vendor/nelexa/zip/src/Model/ZipContainer.php @@ -0,0 +1,386 @@ +getEntries() as $entryName => $entry) { + $entries[$entryName] = clone $entry; + } + $archiveComment = $sourceContainer->getArchiveComment(); + } + parent::__construct($entries, $archiveComment); + $this->sourceContainer = $sourceContainer; + } + + /** + * @return ImmutableZipContainer|null + */ + public function getSourceContainer() + { + return $this->sourceContainer; + } + + /** + * @param ZipEntry $entry + */ + public function addEntry(ZipEntry $entry) + { + $this->entries[$entry->getName()] = $entry; + } + + /** + * @param string|ZipEntry $entry + * + * @return bool + */ + public function deleteEntry($entry) + { + $entry = $entry instanceof ZipEntry ? $entry->getName() : (string) $entry; + + if (isset($this->entries[$entry])) { + unset($this->entries[$entry]); + + return true; + } + + return false; + } + + /** + * @param string|ZipEntry $old + * @param string|ZipEntry $new + * + * @throws ZipException + * + * @return ZipEntry New zip entry + */ + public function renameEntry($old, $new) + { + $old = $old instanceof ZipEntry ? $old->getName() : (string) $old; + $new = $new instanceof ZipEntry ? $new->getName() : (string) $new; + + if (isset($this->entries[$new])) { + throw new InvalidArgumentException('New entry name ' . $new . ' is exists.'); + } + + $entry = $this->getEntry($old); + $newEntry = $entry->rename($new); + + $this->deleteEntry($entry); + $this->addEntry($newEntry); + + return $newEntry; + } + + /** + * @param string|ZipEntry $entryName + * + * @throws ZipEntryNotFoundException + * + * @return ZipEntry + */ + public function getEntry($entryName) + { + $entry = $this->getEntryOrNull($entryName); + + if ($entry !== null) { + return $entry; + } + + throw new ZipEntryNotFoundException($entryName); + } + + /** + * @param string|ZipEntry $entryName + * + * @return ZipEntry|null + */ + public function getEntryOrNull($entryName) + { + $entryName = $entryName instanceof ZipEntry ? $entryName->getName() : (string) $entryName; + + return isset($this->entries[$entryName]) ? $this->entries[$entryName] : null; + } + + /** + * @param string|ZipEntry $entryName + * + * @return bool + */ + public function hasEntry($entryName) + { + $entryName = $entryName instanceof ZipEntry ? $entryName->getName() : (string) $entryName; + + return isset($this->entries[$entryName]); + } + + /** + * Delete all entries. + */ + public function deleteAll() + { + $this->entries = []; + } + + /** + * Delete entries by regex pattern. + * + * @param string $regexPattern Regex pattern + * + * @return ZipEntry[] Deleted entries + */ + public function deleteByRegex($regexPattern) + { + if (empty($regexPattern)) { + throw new InvalidArgumentException('The regex pattern is not specified'); + } + + /** @var ZipEntry[] $found */ + $found = []; + + foreach ($this->entries as $entryName => $entry) { + if (preg_match($regexPattern, $entryName)) { + $found[] = $entry; + } + } + + foreach ($found as $entry) { + $this->deleteEntry($entry); + } + + return $found; + } + + /** + * Undo all changes done in the archive. + */ + public function unchangeAll() + { + $this->entries = []; + + if ($this->sourceContainer !== null) { + foreach ($this->sourceContainer->getEntries() as $entry) { + $this->entries[$entry->getName()] = clone $entry; + } + } + $this->unchangeArchiveComment(); + } + + /** + * Undo change archive comment. + */ + public function unchangeArchiveComment() + { + $this->archiveComment = null; + + if ($this->sourceContainer !== null) { + $this->archiveComment = $this->sourceContainer->archiveComment; + } + } + + /** + * Revert all changes done to an entry with the given name. + * + * @param string|ZipEntry $entry Entry name or ZipEntry + * + * @return bool + */ + public function unchangeEntry($entry) + { + $entry = $entry instanceof ZipEntry ? $entry->getName() : (string) $entry; + + if ( + $this->sourceContainer !== null && + isset($this->entries[$entry], $this->sourceContainer->entries[$entry]) + ) { + $this->entries[$entry] = clone $this->sourceContainer->entries[$entry]; + + return true; + } + + return false; + } + + /** + * Entries sort by name. + * + * Example: + * ```php + * $zipContainer->sortByName(static function (string $nameA, string $nameB): int { + * return strcmp($nameA, $nameB); + * }); + * ``` + * + * @param callable $cmp + */ + public function sortByName(callable $cmp) + { + uksort($this->entries, $cmp); + } + + /** + * Entries sort by entry. + * + * Example: + * ```php + * $zipContainer->sortByEntry(static function (ZipEntry $a, ZipEntry $b): int { + * return strcmp($a->getName(), $b->getName()); + * }); + * ``` + * + * @param callable $cmp + */ + public function sortByEntry(callable $cmp) + { + uasort($this->entries, $cmp); + } + + /** + * @param string|null $archiveComment + */ + public function setArchiveComment($archiveComment) + { + if ($archiveComment !== null && $archiveComment !== '') { + $archiveComment = (string) $archiveComment; + $length = \strlen($archiveComment); + + if ($length > 0xffff) { + throw new InvalidArgumentException('Length comment out of range'); + } + } + $this->archiveComment = $archiveComment; + } + + /** + * @return ZipEntryMatcher + */ + public function matcher() + { + return new ZipEntryMatcher($this); + } + + /** + * Specify a password for extracting files. + * + * @param string|null $password + */ + public function setReadPassword($password) + { + if ($this->sourceContainer !== null) { + foreach ($this->sourceContainer->entries as $entry) { + if ($entry->isEncrypted()) { + $entry->setPassword($password); + } + } + } + } + + /** + * @param string $entryName + * @param string $password + * + * @throws ZipEntryNotFoundException + * @throws ZipException + */ + public function setReadPasswordEntry($entryName, $password) + { + if (!isset($this->sourceContainer->entries[$entryName])) { + throw new ZipEntryNotFoundException($entryName); + } + + if ($this->sourceContainer->entries[$entryName]->isEncrypted()) { + $this->sourceContainer->entries[$entryName]->setPassword($password); + } + } + + /** + * @return int|null + */ + public function getZipAlign() + { + return $this->zipAlign; + } + + /** + * @param int|null $zipAlign + */ + public function setZipAlign($zipAlign) + { + $this->zipAlign = $zipAlign === null ? null : (int) $zipAlign; + } + + /** + * @return bool + */ + public function isZipAlign() + { + return $this->zipAlign !== null; + } + + /** + * @param string|null $writePassword + */ + public function setWritePassword($writePassword) + { + $this->matcher()->all()->setPassword($writePassword); + } + + /** + * Remove password. + */ + public function removePassword() + { + $this->matcher()->all()->setPassword(null); + } + + /** + * @param string|ZipEntry $entryName + */ + public function removePasswordEntry($entryName) + { + $this->matcher()->add($entryName)->setPassword(null); + } + + /** + * @param int $encryptionMethod + */ + public function setEncryptionMethod($encryptionMethod = ZipEncryptionMethod::WINZIP_AES_256) + { + $this->matcher()->all()->setEncryptionMethod($encryptionMethod); + } +} diff --git a/vendor/nelexa/zip/src/Model/ZipData.php b/vendor/nelexa/zip/src/Model/ZipData.php new file mode 100644 index 0000000..30f8289 --- /dev/null +++ b/vendor/nelexa/zip/src/Model/ZipData.php @@ -0,0 +1,28 @@ +setName($name, $charset); + + $this->cdExtraFields = new ExtraFieldsCollection(); + $this->localExtraFields = new ExtraFieldsCollection(); + } + + /** + * This method only internal use. + * + * @param string $name + * @param int $createdOS + * @param int $extractedOS + * @param int $softwareVersion + * @param int $extractVersion + * @param int $compressionMethod + * @param int $gpbf + * @param int $dosTime + * @param int $crc + * @param int $compressedSize + * @param int $uncompressedSize + * @param int $internalAttributes + * @param int $externalAttributes + * @param int $offsetLocalHeader + * @param string|null $comment + * @param string|null $charset + * + * @return ZipEntry + * + * @internal + * + * @noinspection PhpTooManyParametersInspection + */ + public static function create( + $name, + $createdOS, + $extractedOS, + $softwareVersion, + $extractVersion, + $compressionMethod, + $gpbf, + $dosTime, + $crc, + $compressedSize, + $uncompressedSize, + $internalAttributes, + $externalAttributes, + $offsetLocalHeader, + $comment, + $charset + ) { + $entry = new self($name); + $entry->createdOS = (int) $createdOS; + $entry->extractedOS = (int) $extractedOS; + $entry->softwareVersion = (int) $softwareVersion; + $entry->extractVersion = (int) $extractVersion; + $entry->compressionMethod = (int) $compressionMethod; + $entry->generalPurposeBitFlags = (int) $gpbf; + $entry->dosTime = (int) $dosTime; + $entry->crc = (int) $crc; + $entry->compressedSize = (int) $compressedSize; + $entry->uncompressedSize = (int) $uncompressedSize; + $entry->internalAttributes = (int) $internalAttributes; + $entry->externalAttributes = (int) $externalAttributes; + $entry->localHeaderOffset = (int) $offsetLocalHeader; + $entry->setComment($comment); + $entry->setCharset($charset); + $entry->updateCompressionLevel(); + + return $entry; + } + + /** + * Set entry name. + * + * @param string $name New entry name + * @param string|null $charset + * + * @return ZipEntry + */ + private function setName($name, $charset = null) + { + if ($name === null) { + throw new InvalidArgumentException('zip entry name is null'); + } + + $name = ltrim((string) $name, '\\/'); + + if ($name === '') { + throw new InvalidArgumentException('Empty zip entry name'); + } + + $name = (string) $name; + $length = \strlen($name); + + if ($length > 0xffff) { + throw new InvalidArgumentException('Illegal zip entry name parameter'); + } + + $this->setCharset($charset); + + if ($this->charset === null && !StringUtil::isASCII($name)) { + $this->enableUtf8Name(true); + } + $this->name = $name; + $this->isDirectory = ($length = \strlen($name)) >= 1 && $name[$length - 1] === '/'; + $this->externalAttributes = $this->isDirectory ? DosAttrs::DOS_DIRECTORY : DosAttrs::DOS_ARCHIVE; + + if ($this->extractVersion !== self::UNKNOWN) { + $this->extractVersion = max( + $this->extractVersion, + $this->isDirectory ? + ZipVersion::v20_DEFLATED_FOLDER_ZIPCRYPTO : + ZipVersion::v10_DEFAULT_MIN + ); + } + + return $this; + } + + /** + * @param string|null $charset + * + * @return ZipEntry + * + * @see DosCodePage::getCodePages() + */ + public function setCharset($charset = null) + { + if ($charset !== null && $charset === '') { + throw new InvalidArgumentException('Empty charset'); + } + $this->charset = $charset; + + return $this; + } + + /** + * @return string|null + */ + public function getCharset() + { + return $this->charset; + } + + /** + * @param string $newName New entry name + * + * @return ZipEntry new {@see ZipEntry} object with new name + * + * @internal + */ + public function rename($newName) + { + $newEntry = clone $this; + $newEntry->setName($newName); + + $newEntry->removeExtraField(UnicodePathExtraField::HEADER_ID); + + return $newEntry; + } + + /** + * Returns the ZIP entry name. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @return ZipData|null + * + * @internal + */ + public function getData() + { + return $this->data; + } + + /** + * @param ZipData|null $data + * + * @internal + */ + public function setData($data) + { + $this->data = $data; + } + + /** + * @return int Get platform + * + * @deprecated Use {@see ZipEntry::getCreatedOS()} + */ + public function getPlatform() + { + @trigger_error(__METHOD__ . ' is deprecated. Use ' . __CLASS__ . '::getCreatedOS()', \E_USER_DEPRECATED); + + return $this->getCreatedOS(); + } + + /** + * @param int $platform + * + * @return ZipEntry + * + * @deprecated Use {@see ZipEntry::setCreatedOS()} + */ + public function setPlatform($platform) + { + @trigger_error(__METHOD__ . ' is deprecated. Use ' . __CLASS__ . '::setCreatedOS()', \E_USER_DEPRECATED); + + return $this->setCreatedOS($platform); + } + + /** + * @return int platform + */ + public function getCreatedOS() + { + return $this->createdOS; + } + + /** + * Set platform. + * + * @param int $platform + * + * @return ZipEntry + */ + public function setCreatedOS($platform) + { + $platform = (int) $platform; + + if ($platform < 0x00 || $platform > 0xff) { + throw new InvalidArgumentException('Platform out of range'); + } + $this->createdOS = $platform; + + return $this; + } + + /** + * @return int + */ + public function getExtractedOS() + { + return $this->extractedOS; + } + + /** + * Set extracted OS. + * + * @param int $platform + * + * @return ZipEntry + */ + public function setExtractedOS($platform) + { + $platform = (int) $platform; + + if ($platform < 0x00 || $platform > 0xff) { + throw new InvalidArgumentException('Platform out of range'); + } + $this->extractedOS = $platform; + + return $this; + } + + /** + * @return int + */ + public function getSoftwareVersion() + { + if ($this->softwareVersion === self::UNKNOWN) { + return $this->getExtractVersion(); + } + + return $this->softwareVersion; + } + + /** + * @param int $softwareVersion + * + * @return ZipEntry + */ + public function setSoftwareVersion($softwareVersion) + { + $this->softwareVersion = (int) $softwareVersion; + + return $this; + } + + /** + * Version needed to extract. + * + * @return int + * + * @deprecated Use {@see ZipEntry::getExtractVersion()} + */ + public function getVersionNeededToExtract() + { + @trigger_error(__METHOD__ . ' is deprecated. Use ' . __CLASS__ . '::getExtractVersion()', \E_USER_DEPRECATED); + + return $this->getExtractVersion(); + } + + /** + * Version needed to extract. + * + * @return int + */ + public function getExtractVersion() + { + if ($this->extractVersion === self::UNKNOWN) { + if (ZipEncryptionMethod::isWinZipAesMethod($this->encryptionMethod)) { + return ZipVersion::v51_ENCR_AES_RC2_CORRECT; + } + + if ($this->compressionMethod === ZipCompressionMethod::BZIP2) { + return ZipVersion::v46_BZIP2; + } + + if ($this->isZip64ExtensionsRequired()) { + return ZipVersion::v45_ZIP64_EXT; + } + + if ( + $this->compressionMethod === ZipCompressionMethod::DEFLATED || + $this->isDirectory || + $this->encryptionMethod === ZipEncryptionMethod::PKWARE + ) { + return ZipVersion::v20_DEFLATED_FOLDER_ZIPCRYPTO; + } + + return ZipVersion::v10_DEFAULT_MIN; + } + + return $this->extractVersion; + } + + /** + * Set version needed to extract. + * + * @param int $version + * + * @return ZipEntry + * + * @deprecated Use {@see ZipEntry::setExtractVersion()} + */ + public function setVersionNeededToExtract($version) + { + @trigger_error(__METHOD__ . ' is deprecated. Use ' . __CLASS__ . '::setExtractVersion()', \E_USER_DEPRECATED); + + return $this->setExtractVersion($version); + } + + /** + * Set version needed to extract. + * + * @param int $version + * + * @return ZipEntry + */ + public function setExtractVersion($version) + { + $this->extractVersion = max(ZipVersion::v10_DEFAULT_MIN, (int) $version); + + return $this; + } + + /** + * Returns the compressed size of this entry. + * + * @return int + */ + public function getCompressedSize() + { + return $this->compressedSize; + } + + /** + * Sets the compressed size of this entry. + * + * @param int $compressedSize the Compressed Size + * + * @return ZipEntry + * + * @internal + */ + public function setCompressedSize($compressedSize) + { + $compressedSize = (int) $compressedSize; + + if ($compressedSize < self::UNKNOWN) { + throw new InvalidArgumentException('Compressed size < ' . self::UNKNOWN); + } + $this->compressedSize = $compressedSize; + + return $this; + } + + /** + * Returns the uncompressed size of this entry. + * + * @return int + * + * @deprecated Use {@see ZipEntry::getUncompressedSize()} + */ + public function getSize() + { + @trigger_error(__METHOD__ . ' is deprecated. Use ' . __CLASS__ . '::getUncompressedSize()', \E_USER_DEPRECATED); + + return $this->getUncompressedSize(); + } + + /** + * Sets the uncompressed size of this entry. + * + * @param int $size the (Uncompressed) Size + * + * @return ZipEntry + * + * @deprecated Use {@see ZipEntry::setUncompressedSize()} + * + * @internal + */ + public function setSize($size) + { + @trigger_error(__METHOD__ . ' is deprecated. Use ' . __CLASS__ . '::setUncompressedSize()', \E_USER_DEPRECATED); + + return $this->setUncompressedSize($size); + } + + /** + * Returns the uncompressed size of this entry. + * + * @return int + */ + public function getUncompressedSize() + { + return $this->uncompressedSize; + } + + /** + * Sets the uncompressed size of this entry. + * + * @param int $uncompressedSize the (Uncompressed) Size + * + * @return ZipEntry + * + * @internal + */ + public function setUncompressedSize($uncompressedSize) + { + $uncompressedSize = (int) $uncompressedSize; + + if ($uncompressedSize < self::UNKNOWN) { + throw new InvalidArgumentException('Uncompressed size < ' . self::UNKNOWN); + } + $this->uncompressedSize = $uncompressedSize; + + return $this; + } + + /** + * Return relative Offset Of Local File Header. + * + * @return int + */ + public function getLocalHeaderOffset() + { + return $this->localHeaderOffset; + } + + /** + * @param int $localHeaderOffset + * + * @return ZipEntry + * + * @internal + */ + public function setLocalHeaderOffset($localHeaderOffset) + { + $localHeaderOffset = (int) $localHeaderOffset; + + if ($localHeaderOffset < 0) { + throw new InvalidArgumentException('Negative $localHeaderOffset'); + } + $this->localHeaderOffset = $localHeaderOffset; + + return $this; + } + + /** + * Return relative Offset Of Local File Header. + * + * @return int + * + * @deprecated Use {@see ZipEntry::getLocalHeaderOffset()} + */ + public function getOffset() + { + @trigger_error( + __METHOD__ . ' is deprecated. Use ' . __CLASS__ . '::getLocalHeaderOffset()', + \E_USER_DEPRECATED + ); + + return $this->getLocalHeaderOffset(); + } + + /** + * @param int $offset + * + * @return ZipEntry + * + * @deprecated Use {@see ZipEntry::setLocalHeaderOffset()} + * + * @internal + */ + public function setOffset($offset) + { + @trigger_error( + __METHOD__ . ' is deprecated. Use ' . __CLASS__ . '::setLocalHeaderOffset()', + \E_USER_DEPRECATED + ); + + return $this->setLocalHeaderOffset($offset); + } + + /** + * Returns the General Purpose Bit Flags. + * + * @return int + */ + public function getGeneralPurposeBitFlags() + { + return $this->generalPurposeBitFlags; + } + + /** + * Sets the General Purpose Bit Flags. + * + * @param int $gpbf general purpose bit flags + * + * @return ZipEntry + * + * @internal + */ + public function setGeneralPurposeBitFlags($gpbf) + { + $gpbf = (int) $gpbf; + + if ($gpbf < 0x0000 || $gpbf > 0xffff) { + throw new InvalidArgumentException('general purpose bit flags out of range'); + } + $this->generalPurposeBitFlags = $gpbf; + $this->updateCompressionLevel(); + + return $this; + } + + private function updateCompressionLevel() + { + if ($this->compressionMethod === ZipCompressionMethod::DEFLATED) { + $bit1 = $this->isSetGeneralBitFlag(GeneralPurposeBitFlag::COMPRESSION_FLAG1); + $bit2 = $this->isSetGeneralBitFlag(GeneralPurposeBitFlag::COMPRESSION_FLAG2); + + if ($bit1 && !$bit2) { + $this->compressionLevel = ZipCompressionLevel::MAXIMUM; + } elseif (!$bit1 && $bit2) { + $this->compressionLevel = ZipCompressionLevel::FAST; + } elseif ($bit1 && $bit2) { + $this->compressionLevel = ZipCompressionLevel::SUPER_FAST; + } else { + $this->compressionLevel = ZipCompressionLevel::NORMAL; + } + } + } + + /** + * @param int $mask + * @param bool $enable + * + * @return ZipEntry + */ + private function setGeneralBitFlag($mask, $enable) + { + if ($enable) { + $this->generalPurposeBitFlags |= $mask; + } else { + $this->generalPurposeBitFlags &= ~$mask; + } + + return $this; + } + + /** + * @param int $mask + * + * @return bool + */ + private function isSetGeneralBitFlag($mask) + { + return ($this->generalPurposeBitFlags & $mask) === $mask; + } + + /** + * @return bool + */ + public function isDataDescriptorEnabled() + { + return $this->isSetGeneralBitFlag(GeneralPurposeBitFlag::DATA_DESCRIPTOR); + } + + /** + * Enabling or disabling the use of the Data Descriptor block. + * + * @param bool $enabled + */ + public function enableDataDescriptor($enabled = true) + { + $this->setGeneralBitFlag(GeneralPurposeBitFlag::DATA_DESCRIPTOR, (bool) $enabled); + } + + /** + * @param bool $enabled + */ + public function enableUtf8Name($enabled) + { + $this->setGeneralBitFlag(GeneralPurposeBitFlag::UTF8, (bool) $enabled); + } + + /** + * @return bool + */ + public function isUtf8Flag() + { + return $this->isSetGeneralBitFlag(GeneralPurposeBitFlag::UTF8); + } + + /** + * Returns true if and only if this ZIP entry is encrypted. + * + * @return bool + */ + public function isEncrypted() + { + return $this->isSetGeneralBitFlag(GeneralPurposeBitFlag::ENCRYPTION); + } + + /** + * @return bool + */ + public function isStrongEncryption() + { + return $this->isSetGeneralBitFlag(GeneralPurposeBitFlag::STRONG_ENCRYPTION); + } + + /** + * Sets the encryption property to false and removes any other + * encryption artifacts. + * + * @return ZipEntry + */ + public function disableEncryption() + { + $this->setEncrypted(false); + $this->removeExtraField(WinZipAesExtraField::HEADER_ID); + $this->encryptionMethod = ZipEncryptionMethod::NONE; + $this->password = null; + $this->extractVersion = self::UNKNOWN; + + return $this; + } + + /** + * Sets the encryption flag for this ZIP entry. + * + * @param bool $encrypted + * + * @return ZipEntry + */ + private function setEncrypted($encrypted) + { + $encrypted = (bool) $encrypted; + $this->setGeneralBitFlag(GeneralPurposeBitFlag::ENCRYPTION, $encrypted); + + return $this; + } + + /** + * Returns the compression method for this entry. + * + * @return int + * + * @deprecated Use {@see ZipEntry::getCompressionMethod()} + */ + public function getMethod() + { + @trigger_error( + __METHOD__ . ' is deprecated. Use ' . __CLASS__ . '::getCompressionMethod()', + \E_USER_DEPRECATED + ); + + return $this->getCompressionMethod(); + } + + /** + * Returns the compression method for this entry. + * + * @return int + */ + public function getCompressionMethod() + { + return $this->compressionMethod; + } + + /** + * Sets the compression method for this entry. + * + * @param int $method + * + * @throws ZipUnsupportMethodException + * + * @return ZipEntry + * + * @deprecated Use {@see ZipEntry::setCompressionMethod()} + */ + public function setMethod($method) + { + @trigger_error( + __METHOD__ . ' is deprecated. Use ' . __CLASS__ . '::setCompressionMethod()', + \E_USER_DEPRECATED + ); + + return $this->setCompressionMethod($method); + } + + /** + * Sets the compression method for this entry. + * + * @param int $compressionMethod + * + * @throws ZipUnsupportMethodException + * + * @return ZipEntry + * + * @see ZipCompressionMethod::STORED + * @see ZipCompressionMethod::DEFLATED + * @see ZipCompressionMethod::BZIP2 + */ + public function setCompressionMethod($compressionMethod) + { + $compressionMethod = (int) $compressionMethod; + + if ($compressionMethod < 0x0000 || $compressionMethod > 0xffff) { + throw new InvalidArgumentException('method out of range: ' . $compressionMethod); + } + + ZipCompressionMethod::checkSupport($compressionMethod); + + $this->compressionMethod = $compressionMethod; + $this->updateCompressionLevel(); + $this->extractVersion = self::UNKNOWN; + + return $this; + } + + /** + * Get Unix Timestamp. + * + * @return int + */ + public function getTime() + { + if ($this->getDosTime() === self::UNKNOWN) { + return self::UNKNOWN; + } + + return DateTimeConverter::msDosToUnix($this->getDosTime()); + } + + /** + * Get Dos Time. + * + * @return int + */ + public function getDosTime() + { + return $this->dosTime; + } + + /** + * Set Dos Time. + * + * @param int $dosTime + * + * @return ZipEntry + */ + public function setDosTime($dosTime) + { + $dosTime = (int) $dosTime; + + if (\PHP_INT_SIZE === 8) { + if ($dosTime < 0x00000000 || $dosTime > 0xffffffff) { + throw new InvalidArgumentException('DosTime out of range'); + } + } + + $this->dosTime = $dosTime; + + return $this; + } + + /** + * Set time from unix timestamp. + * + * @param int $unixTimestamp + * + * @return ZipEntry + */ + public function setTime($unixTimestamp) + { + if ($unixTimestamp !== self::UNKNOWN) { + $this->setDosTime(DateTimeConverter::unixToMsDos($unixTimestamp)); + } else { + $this->dosTime = 0; + } + + return $this; + } + + /** + * Returns the external file attributes. + * + * @return int the external file attributes + */ + public function getExternalAttributes() + { + return $this->externalAttributes; + } + + /** + * Sets the external file attributes. + * + * @param int $externalAttributes the external file attributes + * + * @return ZipEntry + */ + public function setExternalAttributes($externalAttributes) + { + $this->externalAttributes = (int) $externalAttributes; + + if (\PHP_INT_SIZE === 8) { + if ($externalAttributes < 0x00000000 || $externalAttributes > 0xffffffff) { + throw new InvalidArgumentException('external attributes out of range: ' . $externalAttributes); + } + } + + $this->externalAttributes = $externalAttributes; + + return $this; + } + + /** + * Returns the internal file attributes. + * + * @return int the internal file attributes + */ + public function getInternalAttributes() + { + return $this->internalAttributes; + } + + /** + * Sets the internal file attributes. + * + * @param int $internalAttributes the internal file attributes + * + * @return ZipEntry + */ + public function setInternalAttributes($internalAttributes) + { + $internalAttributes = (int) $internalAttributes; + + if ($internalAttributes < 0x0000 || $internalAttributes > 0xffff) { + throw new InvalidArgumentException('internal attributes out of range'); + } + $this->internalAttributes = $internalAttributes; + + return $this; + } + + /** + * Returns true if and only if this ZIP entry represents a directory entry + * (i.e. end with '/'). + * + * @return bool + */ + final public function isDirectory() + { + return $this->isDirectory; + } + + /** + * @return ExtraFieldsCollection + */ + public function getCdExtraFields() + { + return $this->cdExtraFields; + } + + /** + * @param int $headerId + * + * @return ZipExtraField|null + */ + public function getCdExtraField($headerId) + { + return $this->cdExtraFields->get((int) $headerId); + } + + /** + * @param ExtraFieldsCollection $cdExtraFields + * + * @return ZipEntry + */ + public function setCdExtraFields(ExtraFieldsCollection $cdExtraFields) + { + $this->cdExtraFields = $cdExtraFields; + + return $this; + } + + /** + * @return ExtraFieldsCollection + */ + public function getLocalExtraFields() + { + return $this->localExtraFields; + } + + /** + * @param int $headerId + * + * @return ZipExtraField|null + */ + public function getLocalExtraField($headerId) + { + return $this->localExtraFields[(int) $headerId]; + } + + /** + * @param ExtraFieldsCollection $localExtraFields + * + * @return ZipEntry + */ + public function setLocalExtraFields(ExtraFieldsCollection $localExtraFields) + { + $this->localExtraFields = $localExtraFields; + + return $this; + } + + /** + * @param int $headerId + * + * @return ZipExtraField|null + */ + public function getExtraField($headerId) + { + $headerId = (int) $headerId; + $local = $this->getLocalExtraField($headerId); + + if ($local === null) { + return $this->getCdExtraField($headerId); + } + + return $local; + } + + /** + * @param int $headerId + * + * @return bool + */ + public function hasExtraField($headerId) + { + $headerId = (int) $headerId; + + return + isset($this->localExtraFields[$headerId]) || + isset($this->cdExtraFields[$headerId]); + } + + /** + * @param int $headerId + */ + public function removeExtraField($headerId) + { + $headerId = (int) $headerId; + + $this->cdExtraFields->remove($headerId); + $this->localExtraFields->remove($headerId); + } + + /** + * @param ZipExtraField $zipExtraField + */ + public function addExtraField(ZipExtraField $zipExtraField) + { + $this->addLocalExtraField($zipExtraField); + $this->addCdExtraField($zipExtraField); + } + + /** + * @param ZipExtraField $zipExtraField + */ + public function addLocalExtraField(ZipExtraField $zipExtraField) + { + $this->localExtraFields->add($zipExtraField); + } + + /** + * @param ZipExtraField $zipExtraField + */ + public function addCdExtraField(ZipExtraField $zipExtraField) + { + $this->cdExtraFields->add($zipExtraField); + } + + /** + * Returns comment entry. + * + * @return string + */ + public function getComment() + { + return $this->comment !== null ? $this->comment : ''; + } + + /** + * Set entry comment. + * + * @param string|null $comment + * + * @return ZipEntry + */ + public function setComment($comment) + { + if ($comment !== null) { + $commentLength = \strlen($comment); + + if ($commentLength > 0xffff) { + throw new InvalidArgumentException('Comment too long'); + } + + if ($this->charset === null && !StringUtil::isASCII($comment)) { + $this->enableUtf8Name(true); + } + } + $this->comment = $comment; + + return $this; + } + + /** + * @return bool + */ + public function isDataDescriptorRequired() + { + return ($this->getCrc() | $this->getCompressedSize() | $this->getUncompressedSize()) === self::UNKNOWN; + } + + /** + * Return crc32 content or 0 for WinZip AES v2. + * + * @return int + */ + public function getCrc() + { + return $this->crc; + } + + /** + * Set crc32 content. + * + * @param int $crc + * + * @return ZipEntry + * + * @internal + */ + public function setCrc($crc) + { + $this->crc = (int) $crc; + + return $this; + } + + /** + * @return string|null + */ + public function getPassword() + { + return $this->password; + } + + /** + * Set password and encryption method from entry. + * + * @param string|null $password + * @param int|null $encryptionMethod + * + * @return ZipEntry + */ + public function setPassword($password, $encryptionMethod = null) + { + if (!$this->isDirectory) { + if ($password === null || $password === '') { + $this->password = null; + $this->disableEncryption(); + } else { + $this->password = (string) $password; + + if ($encryptionMethod === null && $this->encryptionMethod === ZipEncryptionMethod::NONE) { + $encryptionMethod = ZipEncryptionMethod::WINZIP_AES_256; + } + + if ($encryptionMethod !== null) { + $this->setEncryptionMethod($encryptionMethod); + } + $this->setEncrypted(true); + } + } + + return $this; + } + + /** + * @return int + */ + public function getEncryptionMethod() + { + return $this->encryptionMethod; + } + + /** + * Set encryption method. + * + * @param int|null $encryptionMethod + * + * @return ZipEntry + * + * @see ZipEncryptionMethod::NONE + * @see ZipEncryptionMethod::PKWARE + * @see ZipEncryptionMethod::WINZIP_AES_256 + * @see ZipEncryptionMethod::WINZIP_AES_192 + * @see ZipEncryptionMethod::WINZIP_AES_128 + */ + public function setEncryptionMethod($encryptionMethod) + { + if ($encryptionMethod === null) { + $encryptionMethod = ZipEncryptionMethod::NONE; + } + + $encryptionMethod = (int) $encryptionMethod; + ZipEncryptionMethod::checkSupport($encryptionMethod); + $this->encryptionMethod = $encryptionMethod; + + $this->setEncrypted($this->encryptionMethod !== ZipEncryptionMethod::NONE); + $this->extractVersion = self::UNKNOWN; + + return $this; + } + + /** + * @return int + */ + public function getCompressionLevel() + { + return $this->compressionLevel; + } + + /** + * @param int $compressionLevel + * + * @return ZipEntry + */ + public function setCompressionLevel($compressionLevel) + { + $compressionLevel = (int) $compressionLevel; + + if ($compressionLevel === self::UNKNOWN) { + $compressionLevel = ZipCompressionLevel::NORMAL; + } + + if ( + $compressionLevel < ZipCompressionLevel::LEVEL_MIN || + $compressionLevel > ZipCompressionLevel::LEVEL_MAX + ) { + throw new InvalidArgumentException( + 'Invalid compression level. Minimum level ' . + ZipCompressionLevel::LEVEL_MIN . '. Maximum level ' . ZipCompressionLevel::LEVEL_MAX + ); + } + $this->compressionLevel = $compressionLevel; + + $this->updateGbpfCompLevel(); + + return $this; + } + + /** + * Update general purpose bit flogs. + */ + private function updateGbpfCompLevel() + { + if ($this->compressionMethod === ZipCompressionMethod::DEFLATED) { + $bit1 = false; + $bit2 = false; + + switch ($this->compressionLevel) { + case ZipCompressionLevel::MAXIMUM: + $bit1 = true; + break; + + case ZipCompressionLevel::FAST: + $bit2 = true; + break; + + case ZipCompressionLevel::SUPER_FAST: + $bit1 = true; + $bit2 = true; + break; + // default is ZipCompressionLevel::NORMAL + } + + $this->generalPurposeBitFlags |= ($bit1 ? GeneralPurposeBitFlag::COMPRESSION_FLAG1 : 0); + $this->generalPurposeBitFlags |= ($bit2 ? GeneralPurposeBitFlag::COMPRESSION_FLAG2 : 0); + } + } + + /** + * Sets Unix permissions in a way that is understood by Info-Zip's + * unzip command. + * + * @param int $mode mode an int value + * + * @return ZipEntry + */ + public function setUnixMode($mode) + { + $mode = (int) $mode; + $this->setExternalAttributes( + ($mode << 16) + // MS-DOS read-only attribute + | (($mode & UnixStat::UNX_IWUSR) === 0 ? DosAttrs::DOS_HIDDEN : 0) + // MS-DOS directory flag + | ($this->isDirectory() ? DosAttrs::DOS_DIRECTORY : DosAttrs::DOS_ARCHIVE) + ); + $this->createdOS = ZipPlatform::OS_UNIX; + + return $this; + } + + /** + * Unix permission. + * + * @return int the unix permissions + */ + public function getUnixMode() + { + $mode = 0; + + if ($this->createdOS === ZipPlatform::OS_UNIX) { + $mode = ($this->externalAttributes >> 16) & 0xFFFF; + } elseif ($this->hasExtraField(AsiExtraField::HEADER_ID)) { + /** @var AsiExtraField $asiExtraField */ + $asiExtraField = $this->getExtraField(AsiExtraField::HEADER_ID); + $mode = $asiExtraField->getMode(); + } + + if ($mode > 0) { + return $mode; + } + + return $this->isDirectory ? 040755 : 0100644; + } + + /** + * Offset MUST be considered in decision about ZIP64 format - see + * description of Data Descriptor in ZIP File Format Specification. + * + * @return bool + */ + public function isZip64ExtensionsRequired() + { + return $this->compressedSize > ZipConstants::ZIP64_MAGIC + || $this->uncompressedSize > ZipConstants::ZIP64_MAGIC; + } + + /** + * Returns true if this entry represents a unix symlink, + * in which case the entry's content contains the target path + * for the symlink. + * + * @return bool true if the entry represents a unix symlink, + * false otherwise + */ + public function isUnixSymlink() + { + return ($this->getUnixMode() & UnixStat::UNX_IFMT) === UnixStat::UNX_IFLNK; + } + + /** + * @return \DateTimeInterface + */ + public function getMTime() + { + /** @var NtfsExtraField|null $ntfsExtra */ + $ntfsExtra = $this->getExtraField(NtfsExtraField::HEADER_ID); + + if ($ntfsExtra !== null) { + return $ntfsExtra->getModifyDateTime(); + } + + /** @var ExtendedTimestampExtraField|null $extendedExtra */ + $extendedExtra = $this->getExtraField(ExtendedTimestampExtraField::HEADER_ID); + + if ($extendedExtra !== null && ($mtime = $extendedExtra->getModifyDateTime()) !== null) { + return $mtime; + } + + /** @var OldUnixExtraField|null $oldUnixExtra */ + $oldUnixExtra = $this->getExtraField(OldUnixExtraField::HEADER_ID); + + if ($oldUnixExtra !== null && ($mtime = $oldUnixExtra->getModifyDateTime()) !== null) { + return $mtime; + } + + $timestamp = $this->getTime(); + + try { + return new \DateTimeImmutable('@' . $timestamp); + } catch (\Exception $e) { + throw new RuntimeException('Error create DateTime object with timestamp ' . $timestamp, 1, $e); + } + } + + /** + * @return \DateTimeInterface|null + */ + public function getATime() + { + /** @var NtfsExtraField|null $ntfsExtra */ + $ntfsExtra = $this->getExtraField(NtfsExtraField::HEADER_ID); + + if ($ntfsExtra !== null) { + return $ntfsExtra->getAccessDateTime(); + } + + /** @var ExtendedTimestampExtraField|null $extendedExtra */ + $extendedExtra = $this->getExtraField(ExtendedTimestampExtraField::HEADER_ID); + + if ($extendedExtra !== null && ($atime = $extendedExtra->getAccessDateTime()) !== null) { + return $atime; + } + + /** @var OldUnixExtraField|null $oldUnixExtra */ + $oldUnixExtra = $this->getExtraField(OldUnixExtraField::HEADER_ID); + + if ($oldUnixExtra !== null) { + return $oldUnixExtra->getAccessDateTime(); + } + + return null; + } + + /** + * @return \DateTimeInterface|null + */ + public function getCTime() + { + /** @var NtfsExtraField|null $ntfsExtra */ + $ntfsExtra = $this->getExtraField(NtfsExtraField::HEADER_ID); + + if ($ntfsExtra !== null) { + return $ntfsExtra->getCreateDateTime(); + } + + /** @var ExtendedTimestampExtraField|null $extendedExtra */ + $extendedExtra = $this->getExtraField(ExtendedTimestampExtraField::HEADER_ID); + + if ($extendedExtra !== null) { + return $extendedExtra->getCreateDateTime(); + } + + return null; + } + + public function __clone() + { + $this->cdExtraFields = clone $this->cdExtraFields; + $this->localExtraFields = clone $this->localExtraFields; + + if ($this->data !== null) { + $this->data = clone $this->data; + } + } +} diff --git a/vendor/nelexa/zip/src/Model/ZipEntryMatcher.php b/vendor/nelexa/zip/src/Model/ZipEntryMatcher.php new file mode 100644 index 0000000..9b91ba7 --- /dev/null +++ b/vendor/nelexa/zip/src/Model/ZipEntryMatcher.php @@ -0,0 +1,206 @@ +zipContainer = $zipContainer; + } + + /** + * @param string|ZipEntry|string[]|ZipEntry[] $entries + * + * @return ZipEntryMatcher + */ + public function add($entries) + { + $entries = (array) $entries; + $entries = array_map( + static function ($entry) { + return $entry instanceof ZipEntry ? $entry->getName() : (string) $entry; + }, + $entries + ); + $this->matches = array_values( + array_map( + 'strval', + array_unique( + array_merge( + $this->matches, + array_keys( + array_intersect_key( + $this->zipContainer->getEntries(), + array_flip($entries) + ) + ) + ) + ) + ) + ); + + return $this; + } + + /** + * @param string $regexp + * + * @return ZipEntryMatcher + * + * @noinspection PhpUnusedParameterInspection + */ + public function match($regexp) + { + array_walk( + $this->zipContainer->getEntries(), + /** + * @param ZipEntry $entry + * @param string $entryName + */ + function (ZipEntry $entry, $entryName) use ($regexp) { + if (preg_match($regexp, $entryName)) { + $this->matches[] = (string) $entryName; + } + } + ); + $this->matches = array_unique($this->matches); + + return $this; + } + + /** + * @return ZipEntryMatcher + */ + public function all() + { + $this->matches = array_map( + 'strval', + array_keys($this->zipContainer->getEntries()) + ); + + return $this; + } + + /** + * Callable function for all select entries. + * + * Callable function signature: + * function(string $entryName){} + * + * @param callable $callable + */ + public function invoke(callable $callable) + { + if (!empty($this->matches)) { + array_walk( + $this->matches, + /** @param string $entryName */ + static function ($entryName) use ($callable) { + $callable($entryName); + } + ); + } + } + + /** + * @return array + */ + public function getMatches() + { + return $this->matches; + } + + public function delete() + { + array_walk( + $this->matches, + /** @param string $entryName */ + function ($entryName) { + $this->zipContainer->deleteEntry($entryName); + } + ); + $this->matches = []; + } + + /** + * @param string|null $password + * @param int|null $encryptionMethod + */ + public function setPassword($password, $encryptionMethod = null) + { + array_walk( + $this->matches, + /** @param string $entryName */ + function ($entryName) use ($password, $encryptionMethod) { + $entry = $this->zipContainer->getEntry($entryName); + + if (!$entry->isDirectory()) { + $entry->setPassword($password, $encryptionMethod); + } + } + ); + } + + /** + * @param int $encryptionMethod + */ + public function setEncryptionMethod($encryptionMethod) + { + array_walk( + $this->matches, + /** @param string $entryName */ + function ($entryName) use ($encryptionMethod) { + $entry = $this->zipContainer->getEntry($entryName); + + if (!$entry->isDirectory()) { + $entry->setEncryptionMethod($encryptionMethod); + } + } + ); + } + + public function disableEncryption() + { + array_walk( + $this->matches, + /** @param string $entryName */ + function ($entryName) { + $entry = $this->zipContainer->getEntry($entryName); + + if (!$entry->isDirectory()) { + $entry->disableEncryption(); + } + } + ); + } + + /** + * Count elements of an object. + * + * @see http://php.net/manual/en/countable.count.php + * + * @return int the custom count as an integer + * + * @since 5.1.0 + */ + public function count() + { + return \count($this->matches); + } +} diff --git a/vendor/nelexa/zip/src/Model/ZipInfo.php b/vendor/nelexa/zip/src/Model/ZipInfo.php new file mode 100644 index 0000000..42eebbf --- /dev/null +++ b/vendor/nelexa/zip/src/Model/ZipInfo.php @@ -0,0 +1,266 @@ +entry = $entry; + } + + /** + * @param ZipEntry $entry + * + * @return string + * + * @deprecated Use {@see ZipPlatform::getPlatformName()} + */ + public static function getPlatformName(ZipEntry $entry) + { + return ZipPlatform::getPlatformName($entry->getExtractedOS()); + } + + /** + * @return string + */ + public function getName() + { + return $this->entry->getName(); + } + + /** + * @return bool + */ + public function isFolder() + { + return $this->entry->isDirectory(); + } + + /** + * @return int + */ + public function getSize() + { + return $this->entry->getUncompressedSize(); + } + + /** + * @return int + */ + public function getCompressedSize() + { + return $this->entry->getCompressedSize(); + } + + /** + * @return int + */ + public function getMtime() + { + return $this->entry->getMTime()->getTimestamp(); + } + + /** + * @return int|null + */ + public function getCtime() + { + $ctime = $this->entry->getCTime(); + + return $ctime === null ? null : $ctime->getTimestamp(); + } + + /** + * @return int|null + */ + public function getAtime() + { + $atime = $this->entry->getATime(); + + return $atime === null ? null : $atime->getTimestamp(); + } + + /** + * @return string + */ + public function getAttributes() + { + $externalAttributes = $this->entry->getExternalAttributes(); + + if ($this->entry->getCreatedOS() === ZipPlatform::OS_UNIX) { + $permission = (($externalAttributes >> 16) & 0xFFFF); + + return FileAttribUtil::getUnixMode($permission); + } + + return FileAttribUtil::getDosMode($externalAttributes); + } + + /** + * @return bool + */ + public function isEncrypted() + { + return $this->entry->isEncrypted(); + } + + /** + * @return string|null + */ + public function getComment() + { + return $this->entry->getComment(); + } + + /** + * @return int + */ + public function getCrc() + { + return $this->entry->getCrc(); + } + + /** + * @return string + * + * @deprecated use \PhpZip\Model\ZipInfo::getMethodName() + */ + public function getMethod() + { + return $this->getMethodName(); + } + + /** + * @return string + */ + public function getMethodName() + { + return ZipCompressionMethod::getCompressionMethodName($this->entry->getCompressionMethod()); + } + + /** + * @return string + */ + public function getEncryptionMethodName() + { + return ZipEncryptionMethod::getEncryptionMethodName($this->entry->getEncryptionMethod()); + } + + /** + * @return string + */ + public function getPlatform() + { + return ZipPlatform::getPlatformName($this->entry->getExtractedOS()); + } + + /** + * @return int + */ + public function getVersion() + { + return $this->entry->getExtractVersion(); + } + + /** + * @return int|null + */ + public function getEncryptionMethod() + { + $encryptionMethod = $this->entry->getEncryptionMethod(); + + return $encryptionMethod === ZipEncryptionMethod::NONE ? null : $encryptionMethod; + } + + /** + * @return int|null + */ + public function getCompressionLevel() + { + return $this->entry->getCompressionLevel(); + } + + /** + * @return int + */ + public function getCompressionMethod() + { + return $this->entry->getCompressionMethod(); + } + + /** + * @return array + */ + public function toArray() + { + return [ + 'name' => $this->getName(), + 'folder' => $this->isFolder(), + 'size' => $this->getSize(), + 'compressed_size' => $this->getCompressedSize(), + 'modified' => $this->getMtime(), + 'created' => $this->getCtime(), + 'accessed' => $this->getAtime(), + 'attributes' => $this->getAttributes(), + 'encrypted' => $this->isEncrypted(), + 'encryption_method' => $this->getEncryptionMethod(), + 'encryption_method_name' => $this->getEncryptionMethodName(), + 'comment' => $this->getComment(), + 'crc' => $this->getCrc(), + 'method_name' => $this->getMethodName(), + 'compression_method' => $this->getCompressionMethod(), + 'platform' => $this->getPlatform(), + 'version' => $this->getVersion(), + ]; + } + + /** + * @return string + */ + public function __toString() + { + $ctime = $this->entry->getCTime(); + $atime = $this->entry->getATime(); + $comment = $this->getComment(); + + return __CLASS__ . ' {' + . 'Name="' . $this->getName() . '", ' + . ($this->isFolder() ? 'Folder, ' : '') + . 'Size="' . FilesUtil::humanSize($this->getSize()) . '"' + . ', Compressed size="' . FilesUtil::humanSize($this->getCompressedSize()) . '"' + . ', Modified time="' . $this->entry->getMTime()->format(\DATE_W3C) . '", ' + . ($ctime !== null ? 'Created time="' . $ctime->format(\DATE_W3C) . '", ' : '') + . ($atime !== null ? 'Accessed time="' . $atime->format(\DATE_W3C) . '", ' : '') + . ($this->isEncrypted() ? 'Encrypted, ' : '') + . ($comment !== null ? 'Comment="' . $comment . '", ' : '') + . (!empty($this->crc) ? 'Crc=0x' . dechex($this->crc) . ', ' : '') + . 'Method name="' . $this->getMethodName() . '", ' + . 'Attributes="' . $this->getAttributes() . '", ' + . 'Platform="' . $this->getPlatform() . '", ' + . 'Version=' . $this->getVersion() + . '}'; + } +} diff --git a/vendor/nelexa/zip/src/Util/CryptoUtil.php b/vendor/nelexa/zip/src/Util/CryptoUtil.php new file mode 100644 index 0000000..852a5e1 --- /dev/null +++ b/vendor/nelexa/zip/src/Util/CryptoUtil.php @@ -0,0 +1,77 @@ +> 1); + + /** + * Convert a 32 bit integer DOS date/time value to a UNIX timestamp value. + * + * @param int $dosTime Dos date/time + * + * @return int Unix timestamp + */ + public static function msDosToUnix($dosTime) + { + if ($dosTime <= self::MIN_DOS_TIME) { + $dosTime = 0; + } elseif ($dosTime > self::MAX_DOS_TIME) { + $dosTime = self::MAX_DOS_TIME; + } +// date_default_timezone_set('UTC'); + return mktime( + (($dosTime >> 11) & 0x1f), // hours + (($dosTime >> 5) & 0x3f), // minutes + (($dosTime << 1) & 0x3e), // seconds + (($dosTime >> 21) & 0x0f), // month + (($dosTime >> 16) & 0x1f), // day + ((($dosTime >> 25) & 0x7f) + 1980) // year + ); + } + + /** + * Converts a UNIX timestamp value to a DOS date/time value. + * + * @param int $unixTimestamp the number of seconds since midnight, January 1st, + * 1970 AD UTC + * + * @return int a DOS date/time value reflecting the local time zone and + * rounded down to even seconds + * and is in between DateTimeConverter::MIN_DOS_TIME and DateTimeConverter::MAX_DOS_TIME + */ + public static function unixToMsDos($unixTimestamp) + { + if ($unixTimestamp < 0) { + throw new \InvalidArgumentException('Negative unix timestamp: ' . $unixTimestamp); + } + + $date = getdate($unixTimestamp); + $dosTime = ( + (($date['year'] - 1980) << 25) | + ($date['mon'] << 21) | + ($date['mday'] << 16) | + ($date['hours'] << 11) | + ($date['minutes'] << 5) | + ($date['seconds'] >> 1) + ); + + if ($dosTime <= self::MIN_DOS_TIME) { + $dosTime = 0; + } + + return $dosTime; + } +} diff --git a/vendor/nelexa/zip/src/Util/FileAttribUtil.php b/vendor/nelexa/zip/src/Util/FileAttribUtil.php new file mode 100644 index 0000000..06247ae --- /dev/null +++ b/vendor/nelexa/zip/src/Util/FileAttribUtil.php @@ -0,0 +1,108 @@ +isDir() ? 'rmdir' : 'unlink'); + $function($fileInfo->getPathname()); + } + @rmdir($dir); + } + + /** + * Convert glob pattern to regex pattern. + * + * @param string $globPattern + * + * @return string + */ + public static function convertGlobToRegEx($globPattern) + { + // Remove beginning and ending * globs because they're useless + $globPattern = trim($globPattern, '*'); + $escaping = false; + $inCurrent = 0; + $chars = str_split($globPattern); + $regexPattern = ''; + + foreach ($chars as $currentChar) { + switch ($currentChar) { + case '*': + $regexPattern .= ($escaping ? '\\*' : '.*'); + $escaping = false; + break; + + case '?': + $regexPattern .= ($escaping ? '\\?' : '.'); + $escaping = false; + break; + + case '.': + case '(': + case ')': + case '+': + case '|': + case '^': + case '$': + case '@': + case '%': + $regexPattern .= '\\' . $currentChar; + $escaping = false; + break; + + case '\\': + if ($escaping) { + $regexPattern .= '\\\\'; + $escaping = false; + } else { + $escaping = true; + } + break; + + case '{': + if ($escaping) { + $regexPattern .= '\\{'; + } else { + $regexPattern = '('; + $inCurrent++; + } + $escaping = false; + break; + + case '}': + if ($inCurrent > 0 && !$escaping) { + $regexPattern .= ')'; + $inCurrent--; + } elseif ($escaping) { + $regexPattern = '\\}'; + } else { + $regexPattern = '}'; + } + $escaping = false; + break; + + case ',': + if ($inCurrent > 0 && !$escaping) { + $regexPattern .= '|'; + } elseif ($escaping) { + $regexPattern .= '\\,'; + } else { + $regexPattern = ','; + } + break; + default: + $escaping = false; + $regexPattern .= $currentChar; + } + } + + return $regexPattern; + } + + /** + * Search files. + * + * @param string $inputDir + * @param bool $recursive + * @param array $ignoreFiles + * + * @return array Searched file list + */ + public static function fileSearchWithIgnore($inputDir, $recursive = true, array $ignoreFiles = []) + { + if ($recursive) { + $directoryIterator = new \RecursiveDirectoryIterator($inputDir); + + if (!empty($ignoreFiles)) { + $directoryIterator = new IgnoreFilesRecursiveFilterIterator($directoryIterator, $ignoreFiles); + } + $iterator = new \RecursiveIteratorIterator($directoryIterator); + } else { + $directoryIterator = new \DirectoryIterator($inputDir); + + if (!empty($ignoreFiles)) { + $directoryIterator = new IgnoreFilesFilterIterator($directoryIterator, $ignoreFiles); + } + $iterator = new \IteratorIterator($directoryIterator); + } + + $fileList = []; + + foreach ($iterator as $file) { + if ($file instanceof \SplFileInfo) { + $fileList[] = $file->getPathname(); + } + } + + return $fileList; + } + + /** + * Search files from glob pattern. + * + * @param string $globPattern + * @param int $flags + * @param bool $recursive + * + * @return array Searched file list + */ + public static function globFileSearch($globPattern, $flags = 0, $recursive = true) + { + $flags = (int) $flags; + $recursive = (bool) $recursive; + $files = glob($globPattern, $flags); + + if (!$recursive) { + return $files; + } + + foreach (glob(\dirname($globPattern) . \DIRECTORY_SEPARATOR . '*', \GLOB_ONLYDIR | \GLOB_NOSORT) as $dir) { + // Unpacking the argument via ... is supported starting from php 5.6 only + /** @noinspection SlowArrayOperationsInLoopInspection */ + $files = array_merge($files, self::globFileSearch($dir . \DIRECTORY_SEPARATOR . basename($globPattern), $flags, $recursive)); + } + + return $files; + } + + /** + * Search files from regex pattern. + * + * @param string $folder + * @param string $pattern + * @param bool $recursive + * + * @return array Searched file list + */ + public static function regexFileSearch($folder, $pattern, $recursive = true) + { + if ($recursive) { + $directoryIterator = new \RecursiveDirectoryIterator($folder); + $iterator = new \RecursiveIteratorIterator($directoryIterator); + } else { + $directoryIterator = new \DirectoryIterator($folder); + $iterator = new \IteratorIterator($directoryIterator); + } + + $regexIterator = new \RegexIterator($iterator, $pattern, \RegexIterator::MATCH); + $fileList = []; + + foreach ($regexIterator as $file) { + if ($file instanceof \SplFileInfo) { + $fileList[] = $file->getPathname(); + } + } + + return $fileList; + } + + /** + * Convert bytes to human size. + * + * @param int $size Size bytes + * @param string|null $unit Unit support 'GB', 'MB', 'KB' + * + * @return string + */ + public static function humanSize($size, $unit = null) + { + if (($unit === null && $size >= 1 << 30) || $unit === 'GB') { + return number_format($size / (1 << 30), 2) . 'GB'; + } + + if (($unit === null && $size >= 1 << 20) || $unit === 'MB') { + return number_format($size / (1 << 20), 2) . 'MB'; + } + + if (($unit === null && $size >= 1 << 10) || $unit === 'KB') { + return number_format($size / (1 << 10), 2) . 'KB'; + } + + return number_format($size) . ' bytes'; + } + + /** + * Normalizes zip path. + * + * @param string $path Zip path + * + * @return string + */ + public static function normalizeZipPath($path) + { + return implode( + \DIRECTORY_SEPARATOR, + array_filter( + explode('/', (string) $path), + static function ($part) { + return $part !== '.' && $part !== '..'; + } + ) + ); + } + + /** + * Returns whether the file path is an absolute path. + * + * @param string $file A file path + * + * @return bool + * + * @see source symfony filesystem component + */ + public static function isAbsolutePath($file) + { + return strspn($file, '/\\', 0, 1) + || ( + \strlen($file) > 3 && ctype_alpha($file[0]) + && $file[1] === ':' + && strspn($file, '/\\', 2, 1) + ) + || parse_url($file, \PHP_URL_SCHEME) !== null; + } + + /** + * @param string $target + * @param string $path + * @param bool $allowSymlink + * + * @return bool + */ + public static function symlink($target, $path, $allowSymlink) + { + if (\DIRECTORY_SEPARATOR === '\\' || !$allowSymlink) { + return file_put_contents($path, $target) !== false; + } + + return symlink($target, $path); + } + + /** + * @param string $file + * + * @return bool + */ + public static function isBadCompressionFile($file) + { + $badCompressFileExt = [ + 'dic', + 'dng', + 'f4v', + 'flipchart', + 'h264', + 'lrf', + 'mobi', + 'mts', + 'nef', + 'pspimage', + ]; + + $ext = strtolower(pathinfo($file, \PATHINFO_EXTENSION)); + + if (\in_array($ext, $badCompressFileExt, true)) { + return true; + } + + $mimeType = self::getMimeTypeFromFile($file); + + return self::isBadCompressionMimeType($mimeType); + } + + /** + * @param string $mimeType + * + * @return bool + */ + public static function isBadCompressionMimeType($mimeType) + { + static $badDeflateCompMimeTypes = [ + 'application/epub+zip', + 'application/gzip', + 'application/vnd.debian.binary-package', + 'application/vnd.oasis.opendocument.graphics', + 'application/vnd.oasis.opendocument.presentation', + 'application/vnd.oasis.opendocument.text', + 'application/vnd.oasis.opendocument.text-master', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'application/vnd.rn-realmedia', + 'application/x-7z-compressed', + 'application/x-arj', + 'application/x-bzip2', + 'application/x-hwp', + 'application/x-lzip', + 'application/x-lzma', + 'application/x-ms-reader', + 'application/x-rar', + 'application/x-rpm', + 'application/x-stuffit', + 'application/x-tar', + 'application/x-xz', + 'application/zip', + 'application/zlib', + 'audio/flac', + 'audio/mpeg', + 'audio/ogg', + 'audio/vnd.dolby.dd-raw', + 'audio/webm', + 'audio/x-ape', + 'audio/x-hx-aac-adts', + 'audio/x-m4a', + 'audio/x-m4a', + 'audio/x-wav', + 'image/gif', + 'image/heic', + 'image/jp2', + 'image/jpeg', + 'image/png', + 'image/vnd.djvu', + 'image/webp', + 'image/x-canon-cr2', + 'video/ogg', + 'video/webm', + 'video/x-matroska', + 'video/x-ms-asf', + 'x-epoc/x-sisx-app', + ]; + + if (\in_array($mimeType, $badDeflateCompMimeTypes, true)) { + return true; + } + + return false; + } + + /** + * @param string $file + * + * @return string + * + * @noinspection PhpComposerExtensionStubsInspection + */ + public static function getMimeTypeFromFile($file) + { + if (\function_exists('mime_content_type')) { + return mime_content_type($file); + } + + return 'application/octet-stream'; + } + + /** + * @param string $contents + * + * @return string + * @noinspection PhpComposerExtensionStubsInspection + */ + public static function getMimeTypeFromString($contents) + { + $contents = (string) $contents; + $finfo = new \finfo(\FILEINFO_MIME); + $mimeType = $finfo->buffer($contents); + + if ($mimeType === false) { + $mimeType = 'application/octet-stream'; + } + + return explode(';', $mimeType)[0]; + } +} diff --git a/vendor/nelexa/zip/src/Util/Iterator/IgnoreFilesFilterIterator.php b/vendor/nelexa/zip/src/Util/Iterator/IgnoreFilesFilterIterator.php new file mode 100644 index 0000000..c13734e --- /dev/null +++ b/vendor/nelexa/zip/src/Util/Iterator/IgnoreFilesFilterIterator.php @@ -0,0 +1,66 @@ +ignoreFiles = array_merge($this->ignoreFiles, $ignoreFiles); + } + + /** + * Check whether the current element of the iterator is acceptable. + * + * @see http://php.net/manual/en/filteriterator.accept.php + * + * @return bool true if the current element is acceptable, otherwise false + * + * @since 5.1.0 + */ + public function accept() + { + /** + * @var \SplFileInfo $fileInfo + */ + $fileInfo = $this->current(); + $pathname = str_replace('\\', '/', $fileInfo->getPathname()); + + foreach ($this->ignoreFiles as $ignoreFile) { + // handler dir and sub dir + if ($fileInfo->isDir() + && StringUtil::endsWith($ignoreFile, '/') + && StringUtil::endsWith($pathname, substr($ignoreFile, 0, -1)) + ) { + return false; + } + + // handler filename + if (StringUtil::endsWith($pathname, $ignoreFile)) { + return false; + } + } + + return true; + } +} diff --git a/vendor/nelexa/zip/src/Util/Iterator/IgnoreFilesRecursiveFilterIterator.php b/vendor/nelexa/zip/src/Util/Iterator/IgnoreFilesRecursiveFilterIterator.php new file mode 100644 index 0000000..8935127 --- /dev/null +++ b/vendor/nelexa/zip/src/Util/Iterator/IgnoreFilesRecursiveFilterIterator.php @@ -0,0 +1,74 @@ +ignoreFiles = array_merge($this->ignoreFiles, $ignoreFiles); + } + + /** + * Check whether the current element of the iterator is acceptable. + * + * @see http://php.net/manual/en/filteriterator.accept.php + * + * @return bool true if the current element is acceptable, otherwise false + * + * @since 5.1.0 + */ + public function accept() + { + /** + * @var \SplFileInfo $fileInfo + */ + $fileInfo = $this->current(); + $pathname = str_replace('\\', '/', $fileInfo->getPathname()); + + foreach ($this->ignoreFiles as $ignoreFile) { + // handler dir and sub dir + if ($fileInfo->isDir() + && $ignoreFile[\strlen($ignoreFile) - 1] === '/' + && StringUtil::endsWith($pathname, substr($ignoreFile, 0, -1)) + ) { + return false; + } + + // handler filename + if (StringUtil::endsWith($pathname, $ignoreFile)) { + return false; + } + } + + return true; + } + + /** + * @return IgnoreFilesRecursiveFilterIterator + */ + public function getChildren() + { + return new self($this->getInnerIterator()->getChildren(), $this->ignoreFiles); + } +} diff --git a/vendor/nelexa/zip/src/Util/PackUtil.php b/vendor/nelexa/zip/src/Util/PackUtil.php new file mode 100644 index 0000000..653fab7 --- /dev/null +++ b/vendor/nelexa/zip/src/Util/PackUtil.php @@ -0,0 +1,69 @@ += 506030) { + return pack('P', $longValue); + } + + $left = 0xffffffff00000000; + $right = 0x00000000ffffffff; + + $r = ($longValue & $left) >> 32; + $l = $longValue & $right; + + return pack('VV', $l, $r); + } + + /** + * @param string $value + * + * @return int + */ + public static function unpackLongLE($value) + { + if (\PHP_VERSION_ID >= 506030) { + return unpack('P', $value)[1]; + } + $unpack = unpack('Va/Vb', $value); + + return $unpack['a'] + ($unpack['b'] << 32); + } + + /** + * Cast to signed int 32-bit. + * + * @param int $int + * + * @return int + */ + public static function toSignedInt32($int) + { + if (\PHP_INT_SIZE === 8) { + $int &= 0xffffffff; + + if ($int & 0x80000000) { + return $int - 0x100000000; + } + } + + return $int; + } +} diff --git a/vendor/nelexa/zip/src/Util/StringUtil.php b/vendor/nelexa/zip/src/Util/StringUtil.php new file mode 100644 index 0000000..bce2a17 --- /dev/null +++ b/vendor/nelexa/zip/src/Util/StringUtil.php @@ -0,0 +1,48 @@ + 'application/zip', + 'apk' => 'application/vnd.android.package-archive', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'epub' => 'application/epub+zip', + 'jar' => 'application/java-archive', + 'odt' => 'application/vnd.oasis.opendocument.text', + 'pptx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xpi' => 'application/x-xpinstall', + ]; + + /** @var ZipContainer */ + protected $zipContainer; + + /** @var ZipReader|null */ + private $reader; + + /** + * ZipFile constructor. + */ + public function __construct() + { + $this->zipContainer = $this->createZipContainer(null); + } + + /** + * @param resource $inputStream + * @param array $options + * + * @return ZipReader + */ + protected function createZipReader($inputStream, array $options = []) + { + return new ZipReader($inputStream, $options); + } + + /** + * @return ZipWriter + */ + protected function createZipWriter() + { + return new ZipWriter($this->zipContainer); + } + + /** + * @param ImmutableZipContainer|null $sourceContainer + * + * @return ZipContainer + */ + protected function createZipContainer(ImmutableZipContainer $sourceContainer = null) + { + return new ZipContainer($sourceContainer); + } + + /** + * Open zip archive from file. + * + * @param string $filename + * @param array $options + * + * @throws ZipException if can't open file + * + * @return ZipFile + */ + public function openFile($filename, array $options = []) + { + if (!file_exists($filename)) { + throw new ZipException("File {$filename} does not exist."); + } + + if (!($handle = @fopen($filename, 'rb'))) { + throw new ZipException("File {$filename} can't open."); + } + + return $this->openFromStream($handle, $options); + } + + /** + * Open zip archive from raw string data. + * + * @param string $data + * @param array $options + * + * @throws ZipException if can't open temp stream + * + * @return ZipFile + */ + public function openFromString($data, array $options = []) + { + if ($data === null || $data === '') { + throw new InvalidArgumentException('Empty string passed'); + } + + if (!($handle = fopen('php://temp', 'r+b'))) { + // @codeCoverageIgnoreStart + throw new ZipException('A temporary resource cannot be opened for writing.'); + // @codeCoverageIgnoreEnd + } + fwrite($handle, $data); + rewind($handle); + + return $this->openFromStream($handle, $options); + } + + /** + * Open zip archive from stream resource. + * + * @param resource $handle + * @param array $options + * + * @throws ZipException + * + * @return ZipFile + */ + public function openFromStream($handle, array $options = []) + { + $this->reader = $this->createZipReader($handle, $options); + $this->zipContainer = $this->createZipContainer($this->reader->read()); + + return $this; + } + + /** + * @return string[] returns the list files + */ + public function getListFiles() + { + // strval is needed to cast entry names to string type + return array_map('strval', array_keys($this->zipContainer->getEntries())); + } + + /** + * @return int returns the number of entries in this ZIP file + */ + public function count() + { + return $this->zipContainer->count(); + } + + /** + * Returns the file comment. + * + * @return string|null the file comment + */ + public function getArchiveComment() + { + return $this->zipContainer->getArchiveComment(); + } + + /** + * Set archive comment. + * + * @param string|null $comment + * + * @return ZipFile + */ + public function setArchiveComment($comment = null) + { + $this->zipContainer->setArchiveComment($comment); + + return $this; + } + + /** + * Checks if there is an entry in the archive. + * + * @param string $entryName + * + * @return bool + */ + public function hasEntry($entryName) + { + return $this->zipContainer->hasEntry($entryName); + } + + /** + * Returns ZipEntry object. + * + * @param string $entryName + * + * @throws ZipEntryNotFoundException + * + * @return ZipEntry + */ + public function getEntry($entryName) + { + return $this->zipContainer->getEntry($entryName); + } + + /** + * Checks that the entry in the archive is a directory. + * Returns true if and only if this ZIP entry represents a directory entry + * (i.e. end with '/'). + * + * @param string $entryName + * + * @throws ZipEntryNotFoundException + * + * @return bool + */ + public function isDirectory($entryName) + { + return $this->getEntry($entryName)->isDirectory(); + } + + /** + * Returns entry comment. + * + * @param string $entryName + * + * @throws ZipEntryNotFoundException + * @throws ZipException + * + * @return string + */ + public function getEntryComment($entryName) + { + return $this->getEntry($entryName)->getComment(); + } + + /** + * Set entry comment. + * + * @param string $entryName + * @param string|null $comment + * + * @throws ZipException + * @throws ZipEntryNotFoundException + * + * @return ZipFile + */ + public function setEntryComment($entryName, $comment = null) + { + $this->getEntry($entryName)->setComment($comment); + + return $this; + } + + /** + * Returns the entry contents. + * + * @param string $entryName + * + * @throws ZipException + * @throws ZipEntryNotFoundException + * + * @return string + */ + public function getEntryContents($entryName) + { + $zipData = $this->zipContainer->getEntry($entryName)->getData(); + + if ($zipData === null) { + throw new ZipException(sprintf('No data for zip entry %s', $entryName)); + } + + return $zipData->getDataAsString(); + } + + /** + * @param string $entryName + * + * @throws ZipException + * @throws ZipEntryNotFoundException + * + * @return resource + */ + public function getEntryStream($entryName) + { + $resource = ZipEntryStreamWrapper::wrap($this->zipContainer->getEntry($entryName)); + rewind($resource); + + return $resource; + } + + /** + * Get info by entry. + * + * @param string|ZipEntry $entryName + * + * @throws ZipEntryNotFoundException + * @throws ZipException + * + * @return ZipInfo + */ + public function getEntryInfo($entryName) + { + return new ZipInfo($this->zipContainer->getEntry($entryName)); + } + + /** + * Get info by all entries. + * + * @return ZipInfo[] + */ + public function getAllInfo() + { + $infoMap = []; + + foreach ($this->zipContainer->getEntries() as $name => $entry) { + $infoMap[$name] = new ZipInfo($entry); + } + + return $infoMap; + } + + /** + * @return ZipEntryMatcher + */ + public function matcher() + { + return $this->zipContainer->matcher(); + } + + /** + * Returns an array of zip records (ex. for modify time). + * + * @return ZipEntry[] array of raw zip entries + */ + public function getEntries() + { + return $this->zipContainer->getEntries(); + } + + /** + * Extract the archive contents (unzip). + * + * Extract the complete archive or the given files to the specified destination. + * + * @param string $destDir location where to extract the files + * @param array|string|null $entries entries to extract + * @param array $options extract options + * @param array $extractedEntries if the extractedEntries argument + * is present, then the specified + * array will be filled with + * information about the + * extracted entries + * + * @throws ZipException + * + * @return ZipFile + */ + public function extractTo($destDir, $entries = null, array $options = [], &$extractedEntries = []) + { + if (!file_exists($destDir)) { + throw new ZipException(sprintf('Destination %s not found', $destDir)); + } + + if (!is_dir($destDir)) { + throw new ZipException('Destination is not directory'); + } + + if (!is_writable($destDir)) { + throw new ZipException('Destination is not writable directory'); + } + + if ($extractedEntries === null) { + $extractedEntries = []; + } + + $defaultOptions = [ + ZipOptions::EXTRACT_SYMLINKS => false, + ]; + /** @noinspection AdditionOperationOnArraysInspection */ + $options += $defaultOptions; + + $zipEntries = $this->zipContainer->getEntries(); + + if (!empty($entries)) { + if (\is_string($entries)) { + $entries = (array) $entries; + } + + if (\is_array($entries)) { + $entries = array_unique($entries); + $zipEntries = array_intersect_key($zipEntries, array_flip($entries)); + } + } + + if (empty($zipEntries)) { + return $this; + } + + /** @var int[] $lastModDirs */ + $lastModDirs = []; + + krsort($zipEntries, \SORT_NATURAL); + + $symlinks = []; + $destDir = rtrim($destDir, '/\\'); + + foreach ($zipEntries as $entryName => $entry) { + $unixMode = $entry->getUnixMode(); + $entryName = FilesUtil::normalizeZipPath($entryName); + $file = $destDir . \DIRECTORY_SEPARATOR . $entryName; + + $extractedEntries[$file] = $entry; + $modifyTimestamp = $entry->getMTime()->getTimestamp(); + $atime = $entry->getATime(); + $accessTimestamp = $atime === null ? null : $atime->getTimestamp(); + + $dir = $entry->isDirectory() ? $file : \dirname($file); + + if (!is_dir($dir)) { + $dirMode = $entry->isDirectory() ? $unixMode : 0755; + + if ($dirMode === 0) { + $dirMode = 0755; + } + + if (!mkdir($dir, $dirMode, true) && !is_dir($dir)) { + // @codeCoverageIgnoreStart + throw new \RuntimeException(sprintf('Directory "%s" was not created', $dir)); + // @codeCoverageIgnoreEnd + } + chmod($dir, $dirMode); + } + + $parts = explode('/', rtrim($entryName, '/')); + $path = $destDir . \DIRECTORY_SEPARATOR; + + foreach ($parts as $part) { + if (!isset($lastModDirs[$path]) || $lastModDirs[$path] > $modifyTimestamp) { + $lastModDirs[$path] = $modifyTimestamp; + } + + $path .= $part . \DIRECTORY_SEPARATOR; + } + + if ($entry->isDirectory()) { + $lastModDirs[$dir] = $modifyTimestamp; + + continue; + } + + $zipData = $entry->getData(); + + if ($zipData === null) { + continue; + } + + if ($entry->isUnixSymlink()) { + $symlinks[$file] = $zipData->getDataAsString(); + + continue; + } + + /** @noinspection PhpUsageOfSilenceOperatorInspection */ + if (!($handle = @fopen($file, 'w+b'))) { + // @codeCoverageIgnoreStart + throw new ZipException( + sprintf( + 'Cannot extract zip entry %s. File %s cannot open for write.', + $entry->getName(), + $file + ) + ); + // @codeCoverageIgnoreEnd + } + + try { + $zipData->copyDataToStream($handle); + } catch (ZipException $e) { + unlink($file); + + throw $e; + } + fclose($handle); + + if ($unixMode === 0) { + $unixMode = 0644; + } + chmod($file, $unixMode); + + if ($accessTimestamp !== null) { + /** @noinspection PotentialMalwareInspection */ + touch($file, $modifyTimestamp, $accessTimestamp); + } else { + touch($file, $modifyTimestamp); + } + } + + $allowSymlink = (bool) $options[ZipOptions::EXTRACT_SYMLINKS]; + + foreach ($symlinks as $linkPath => $target) { + if (!FilesUtil::symlink($target, $linkPath, $allowSymlink)) { + unset($extractedEntries[$linkPath]); + } + } + + krsort($lastModDirs, \SORT_NATURAL); + + foreach ($lastModDirs as $dir => $lastMod) { + touch($dir, $lastMod); + } + + ksort($extractedEntries); + + return $this; + } + + /** + * Add entry from the string. + * + * @param string $entryName zip entry name + * @param string $contents string contents + * @param int|null $compressionMethod Compression method. + * Use {@see ZipCompressionMethod::STORED}, + * {@see ZipCompressionMethod::DEFLATED} or + * {@see ZipCompressionMethod::BZIP2}. + * If null, then auto choosing method. + * + * @throws ZipException + * + * @return ZipFile + */ + public function addFromString($entryName, $contents, $compressionMethod = null) + { + $entryName = $this->normalizeEntryName($entryName); + + if ($contents === null) { + throw new InvalidArgumentException('Contents is null'); + } + + $contents = (string) $contents; + $length = \strlen($contents); + + if ($compressionMethod === null || $compressionMethod === ZipEntry::UNKNOWN) { + if ($length < 512) { + $compressionMethod = ZipCompressionMethod::STORED; + } else { + $mimeType = FilesUtil::getMimeTypeFromString($contents); + $compressionMethod = FilesUtil::isBadCompressionMimeType($mimeType) ? + ZipCompressionMethod::STORED : + ZipCompressionMethod::DEFLATED; + } + } + + $zipEntry = new ZipEntry($entryName); + $zipEntry->setData(new ZipNewData($zipEntry, $contents)); + $zipEntry->setUncompressedSize($length); + $zipEntry->setCompressionMethod($compressionMethod); + $zipEntry->setCreatedOS(ZipPlatform::OS_UNIX); + $zipEntry->setExtractedOS(ZipPlatform::OS_UNIX); + $zipEntry->setUnixMode(0100644); + $zipEntry->setTime(time()); + + $this->addZipEntry($zipEntry); + + return $this; + } + + /** + * @param string $entryName + * + * @return string + */ + protected function normalizeEntryName($entryName) + { + if ($entryName === null) { + throw new InvalidArgumentException('Entry name is null'); + } + + $entryName = ltrim((string) $entryName, '\\/'); + + if (\DIRECTORY_SEPARATOR === '\\') { + $entryName = str_replace('\\', '/', $entryName); + } + + if ($entryName === '') { + throw new InvalidArgumentException('Empty entry name'); + } + + return $entryName; + } + + /** + * @param Finder $finder + * @param array $options + * + * @throws ZipException + * + * @return ZipEntry[] + */ + public function addFromFinder(Finder $finder, array $options = []) + { + $defaultOptions = [ + ZipOptions::STORE_ONLY_FILES => false, + ZipOptions::COMPRESSION_METHOD => null, + ZipOptions::MODIFIED_TIME => null, + ]; + /** @noinspection AdditionOperationOnArraysInspection */ + $options += $defaultOptions; + + if ($options[ZipOptions::STORE_ONLY_FILES]) { + $finder->files(); + } + + $entries = []; + + foreach ($finder as $fileInfo) { + if ($fileInfo->isReadable()) { + $entry = $this->addSplFile($fileInfo, null, $options); + $entries[$entry->getName()] = $entry; + } + } + + return $entries; + } + + /** + * @param \SplFileInfo $file + * @param string|null $entryName + * @param array $options + * + * @throws ZipException + * + * @return ZipEntry + */ + public function addSplFile(\SplFileInfo $file, $entryName = null, array $options = []) + { + if ($file instanceof \DirectoryIterator) { + throw new InvalidArgumentException('File should not be \DirectoryIterator.'); + } + $defaultOptions = [ + ZipOptions::COMPRESSION_METHOD => null, + ZipOptions::MODIFIED_TIME => null, + ]; + /** @noinspection AdditionOperationOnArraysInspection */ + $options += $defaultOptions; + + if (!$file->isReadable()) { + throw new InvalidArgumentException(sprintf('File %s is not readable', $file->getPathname())); + } + + if ($entryName === null) { + if ($file instanceof SymfonySplFileInfo) { + $entryName = $file->getRelativePathname(); + } else { + $entryName = $file->getBasename(); + } + } + + $entryName = $this->normalizeEntryName($entryName); + $entryName = $file->isDir() ? rtrim($entryName, '/\\') . '/' : $entryName; + + $zipEntry = new ZipEntry($entryName); + $zipEntry->setCreatedOS(ZipPlatform::OS_UNIX); + $zipEntry->setExtractedOS(ZipPlatform::OS_UNIX); + + $zipData = null; + $filePerms = $file->getPerms(); + + if ($file->isLink()) { + $linkTarget = $file->getLinkTarget(); + $lengthLinkTarget = \strlen($linkTarget); + + $zipEntry->setCompressionMethod(ZipCompressionMethod::STORED); + $zipEntry->setUncompressedSize($lengthLinkTarget); + $zipEntry->setCompressedSize($lengthLinkTarget); + $zipEntry->setCrc(crc32($linkTarget)); + $filePerms |= UnixStat::UNX_IFLNK; + + $zipData = new ZipNewData($zipEntry, $linkTarget); + } elseif ($file->isFile()) { + if (isset($options[ZipOptions::COMPRESSION_METHOD])) { + $compressionMethod = $options[ZipOptions::COMPRESSION_METHOD]; + } elseif ($file->getSize() < 512) { + $compressionMethod = ZipCompressionMethod::STORED; + } else { + $compressionMethod = FilesUtil::isBadCompressionFile($file->getPathname()) ? + ZipCompressionMethod::STORED : + ZipCompressionMethod::DEFLATED; + } + + $zipEntry->setCompressionMethod($compressionMethod); + + $zipData = new ZipFileData($zipEntry, $file); + } elseif ($file->isDir()) { + $zipEntry->setCompressionMethod(ZipCompressionMethod::STORED); + $zipEntry->setUncompressedSize(0); + $zipEntry->setCompressedSize(0); + $zipEntry->setCrc(0); + } + + $zipEntry->setUnixMode($filePerms); + + $timestamp = null; + + if (isset($options[ZipOptions::MODIFIED_TIME])) { + $mtime = $options[ZipOptions::MODIFIED_TIME]; + + if ($mtime instanceof \DateTimeInterface) { + $timestamp = $mtime->getTimestamp(); + } elseif (is_numeric($mtime)) { + $timestamp = (int) $mtime; + } elseif (\is_string($mtime)) { + $timestamp = strtotime($mtime); + + if ($timestamp === false) { + $timestamp = null; + } + } + } + + if ($timestamp === null) { + $timestamp = $file->getMTime(); + } + + $zipEntry->setTime($timestamp); + $zipEntry->setData($zipData); + + $this->addZipEntry($zipEntry); + + return $zipEntry; + } + + /** + * @param ZipEntry $zipEntry + */ + protected function addZipEntry(ZipEntry $zipEntry) + { + $this->zipContainer->addEntry($zipEntry); + } + + /** + * Add entry from the file. + * + * @param string $filename destination file + * @param string|null $entryName zip Entry name + * @param int|null $compressionMethod Compression method. + * Use {@see ZipCompressionMethod::STORED}, + * {@see ZipCompressionMethod::DEFLATED} or + * {@see ZipCompressionMethod::BZIP2}. + * If null, then auto choosing method. + * + * @throws ZipException + * + * @return ZipFile + */ + public function addFile($filename, $entryName = null, $compressionMethod = null) + { + if ($filename === null) { + throw new InvalidArgumentException('Filename is null'); + } + + $this->addSplFile( + new \SplFileInfo($filename), + $entryName, + [ + ZipOptions::COMPRESSION_METHOD => $compressionMethod, + ] + ); + + return $this; + } + + /** + * Add entry from the stream. + * + * @param resource $stream stream resource + * @param string $entryName zip Entry name + * @param int|null $compressionMethod Compression method. + * Use {@see ZipCompressionMethod::STORED}, + * {@see ZipCompressionMethod::DEFLATED} or + * {@see ZipCompressionMethod::BZIP2}. + * If null, then auto choosing method. + * + * @throws ZipException + * + * @return ZipFile + */ + public function addFromStream($stream, $entryName, $compressionMethod = null) + { + if (!\is_resource($stream)) { + throw new InvalidArgumentException('Stream is not resource'); + } + + $entryName = $this->normalizeEntryName($entryName); + $zipEntry = new ZipEntry($entryName); + $fstat = fstat($stream); + + if ($fstat !== false) { + $unixMode = $fstat['mode']; + $length = $fstat['size']; + + if ($compressionMethod === null || $compressionMethod === ZipEntry::UNKNOWN) { + if ($length < 512) { + $compressionMethod = ZipCompressionMethod::STORED; + } else { + rewind($stream); + $bufferContents = stream_get_contents($stream, min(1024, $length)); + rewind($stream); + $mimeType = FilesUtil::getMimeTypeFromString($bufferContents); + $compressionMethod = FilesUtil::isBadCompressionMimeType($mimeType) ? + ZipCompressionMethod::STORED : + ZipCompressionMethod::DEFLATED; + } + $zipEntry->setUncompressedSize($length); + } + } else { + $unixMode = 0100644; + + if ($compressionMethod === null || $compressionMethod === ZipEntry::UNKNOWN) { + $compressionMethod = ZipCompressionMethod::DEFLATED; + } + } + + $zipEntry->setCreatedOS(ZipPlatform::OS_UNIX); + $zipEntry->setExtractedOS(ZipPlatform::OS_UNIX); + $zipEntry->setUnixMode($unixMode); + $zipEntry->setCompressionMethod($compressionMethod); + $zipEntry->setTime(time()); + $zipEntry->setData(new ZipNewData($zipEntry, $stream)); + + $this->addZipEntry($zipEntry); + + return $this; + } + + /** + * Add an empty directory in the zip archive. + * + * @param string $dirName + * + * @throws ZipException + * + * @return ZipFile + */ + public function addEmptyDir($dirName) + { + $dirName = $this->normalizeEntryName($dirName); + $dirName = rtrim($dirName, '\\/') . '/'; + + $zipEntry = new ZipEntry($dirName); + $zipEntry->setCompressionMethod(ZipCompressionMethod::STORED); + $zipEntry->setUncompressedSize(0); + $zipEntry->setCompressedSize(0); + $zipEntry->setCrc(0); + $zipEntry->setCreatedOS(ZipPlatform::OS_UNIX); + $zipEntry->setExtractedOS(ZipPlatform::OS_UNIX); + $zipEntry->setUnixMode(040755); + $zipEntry->setTime(time()); + + $this->addZipEntry($zipEntry); + + return $this; + } + + /** + * Add directory not recursively to the zip archive. + * + * @param string $inputDir Input directory + * @param string $localPath add files to this directory, or the root + * @param int|null $compressionMethod Compression method. + * + * Use {@see ZipCompressionMethod::STORED}, {@see + * ZipCompressionMethod::DEFLATED} or + * {@see ZipCompressionMethod::BZIP2}. If null, then auto choosing method. + * + * @throws ZipException + * + * @return ZipFile + */ + public function addDir($inputDir, $localPath = '/', $compressionMethod = null) + { + if ($inputDir === null) { + throw new InvalidArgumentException('Input dir is null'); + } + $inputDir = (string) $inputDir; + + if ($inputDir === '') { + throw new InvalidArgumentException('The input directory is not specified'); + } + + if (!is_dir($inputDir)) { + throw new InvalidArgumentException(sprintf('The "%s" directory does not exist.', $inputDir)); + } + $inputDir = rtrim($inputDir, '/\\') . \DIRECTORY_SEPARATOR; + + $directoryIterator = new \DirectoryIterator($inputDir); + + return $this->addFilesFromIterator($directoryIterator, $localPath, $compressionMethod); + } + + /** + * Add recursive directory to the zip archive. + * + * @param string $inputDir Input directory + * @param string $localPath add files to this directory, or the root + * @param int|null $compressionMethod Compression method. + * Use {@see ZipCompressionMethod::STORED}, {@see + * ZipCompressionMethod::DEFLATED} or + * {@see ZipCompressionMethod::BZIP2}. If null, then auto choosing method. + * + * @throws ZipException + * + * @return ZipFile + * + * @see ZipCompressionMethod::STORED + * @see ZipCompressionMethod::DEFLATED + * @see ZipCompressionMethod::BZIP2 + */ + public function addDirRecursive($inputDir, $localPath = '/', $compressionMethod = null) + { + if ($inputDir === null) { + throw new InvalidArgumentException('Input dir is null'); + } + $inputDir = (string) $inputDir; + + if ($inputDir === '') { + throw new InvalidArgumentException('The input directory is not specified'); + } + + if (!is_dir($inputDir)) { + throw new InvalidArgumentException(sprintf('The "%s" directory does not exist.', $inputDir)); + } + $inputDir = rtrim($inputDir, '/\\') . \DIRECTORY_SEPARATOR; + + $directoryIterator = new \RecursiveDirectoryIterator($inputDir); + + return $this->addFilesFromIterator($directoryIterator, $localPath, $compressionMethod); + } + + /** + * Add directories from directory iterator. + * + * @param \Iterator $iterator directory iterator + * @param string $localPath add files to this directory, or the root + * @param int|null $compressionMethod Compression method. + * Use {@see ZipCompressionMethod::STORED}, {@see + * ZipCompressionMethod::DEFLATED} or + * {@see ZipCompressionMethod::BZIP2}. If null, then auto choosing method. + * + * @throws ZipException + * + * @return ZipFile + * + * @see ZipCompressionMethod::STORED + * @see ZipCompressionMethod::DEFLATED + * @see ZipCompressionMethod::BZIP2 + */ + public function addFilesFromIterator( + \Iterator $iterator, + $localPath = '/', + $compressionMethod = null + ) { + $localPath = (string) $localPath; + + if ($localPath !== '') { + $localPath = trim($localPath, '\\/'); + } else { + $localPath = ''; + } + + $iterator = $iterator instanceof \RecursiveIterator ? + new \RecursiveIteratorIterator($iterator) : + new \IteratorIterator($iterator); + /** + * @var string[] $files + * @var string $path + */ + $files = []; + + foreach ($iterator as $file) { + if ($file instanceof \SplFileInfo) { + if ($file->getBasename() === '..') { + continue; + } + + if ($file->getBasename() === '.') { + $files[] = \dirname($file->getPathname()); + } else { + $files[] = $file->getPathname(); + } + } + } + + if (empty($files)) { + return $this; + } + + natcasesort($files); + $path = array_shift($files); + + $this->doAddFiles($path, $files, $localPath, $compressionMethod); + + return $this; + } + + /** + * Add files from glob pattern. + * + * @param string $inputDir Input directory + * @param string $globPattern glob pattern + * @param string $localPath add files to this directory, or the root + * @param int|null $compressionMethod Compression method. + * Use {@see ZipCompressionMethod::STORED}, + * {@see ZipCompressionMethod::DEFLATED} or + * {@see ZipCompressionMethod::BZIP2}. If null, then auto choosing method. + * + * @throws ZipException + * + * @return ZipFile + * @sse https://en.wikipedia.org/wiki/Glob_(programming) Glob pattern syntax + */ + public function addFilesFromGlob($inputDir, $globPattern, $localPath = '/', $compressionMethod = null) + { + return $this->addGlob($inputDir, $globPattern, $localPath, false, $compressionMethod); + } + + /** + * Add files from glob pattern. + * + * @param string $inputDir Input directory + * @param string $globPattern glob pattern + * @param string $localPath add files to this directory, or the root + * @param bool $recursive recursive search + * @param int|null $compressionMethod Compression method. + * Use {@see ZipCompressionMethod::STORED}, + * {@see ZipCompressionMethod::DEFLATED} or + * {@see ZipCompressionMethod::BZIP2}. If null, then auto choosing method. + * + * @throws ZipException + * + * @return ZipFile + * + * @sse https://en.wikipedia.org/wiki/Glob_(programming) Glob pattern syntax + */ + private function addGlob( + $inputDir, + $globPattern, + $localPath = '/', + $recursive = true, + $compressionMethod = null + ) { + if ($inputDir === null) { + throw new InvalidArgumentException('Input dir is null'); + } + $inputDir = (string) $inputDir; + + if ($inputDir === '') { + throw new InvalidArgumentException('The input directory is not specified'); + } + + if (!is_dir($inputDir)) { + throw new InvalidArgumentException(sprintf('The "%s" directory does not exist.', $inputDir)); + } + $globPattern = (string) $globPattern; + + if (empty($globPattern)) { + throw new InvalidArgumentException('The glob pattern is not specified'); + } + + $inputDir = rtrim($inputDir, '/\\') . \DIRECTORY_SEPARATOR; + $globPattern = $inputDir . $globPattern; + + $filesFound = FilesUtil::globFileSearch($globPattern, \GLOB_BRACE, $recursive); + + if ($filesFound === false || empty($filesFound)) { + return $this; + } + + $this->doAddFiles($inputDir, $filesFound, $localPath, $compressionMethod); + + return $this; + } + + /** + * Add files recursively from glob pattern. + * + * @param string $inputDir Input directory + * @param string $globPattern glob pattern + * @param string $localPath add files to this directory, or the root + * @param int|null $compressionMethod Compression method. + * Use {@see ZipCompressionMethod::STORED}, + * {@see ZipCompressionMethod::DEFLATED} or + * {@see ZipCompressionMethod::BZIP2}. If null, then auto choosing method. + * + * @throws ZipException + * + * @return ZipFile + * @sse https://en.wikipedia.org/wiki/Glob_(programming) Glob pattern syntax + */ + public function addFilesFromGlobRecursive($inputDir, $globPattern, $localPath = '/', $compressionMethod = null) + { + return $this->addGlob($inputDir, $globPattern, $localPath, true, $compressionMethod); + } + + /** + * Add files from regex pattern. + * + * @param string $inputDir search files in this directory + * @param string $regexPattern regex pattern + * @param string $localPath add files to this directory, or the root + * @param int|null $compressionMethod Compression method. + * Use {@see ZipCompressionMethod::STORED}, + * {@see ZipCompressionMethod::DEFLATED} or + * {@see ZipCompressionMethod::BZIP2}. If null, then auto choosing method. + * + * @throws ZipException + * + * @return ZipFile + * + * @internal param bool $recursive Recursive search + */ + public function addFilesFromRegex($inputDir, $regexPattern, $localPath = '/', $compressionMethod = null) + { + return $this->addRegex($inputDir, $regexPattern, $localPath, false, $compressionMethod); + } + + /** + * Add files from regex pattern. + * + * @param string $inputDir search files in this directory + * @param string $regexPattern regex pattern + * @param string $localPath add files to this directory, or the root + * @param bool $recursive recursive search + * @param int|null $compressionMethod Compression method. + * Use {@see ZipCompressionMethod::STORED}, + * {@see ZipCompressionMethod::DEFLATED} or + * {@see ZipCompressionMethod::BZIP2}. + * If null, then auto choosing method. + * + * @throws ZipException + * + * @return ZipFile + */ + private function addRegex( + $inputDir, + $regexPattern, + $localPath = '/', + $recursive = true, + $compressionMethod = null + ) { + $regexPattern = (string) $regexPattern; + + if (empty($regexPattern)) { + throw new InvalidArgumentException('The regex pattern is not specified'); + } + $inputDir = (string) $inputDir; + + if ($inputDir === '') { + throw new InvalidArgumentException('The input directory is not specified'); + } + + if (!is_dir($inputDir)) { + throw new InvalidArgumentException(sprintf('The "%s" directory does not exist.', $inputDir)); + } + $inputDir = rtrim($inputDir, '/\\') . \DIRECTORY_SEPARATOR; + + $files = FilesUtil::regexFileSearch($inputDir, $regexPattern, $recursive); + + if (empty($files)) { + return $this; + } + + $this->doAddFiles($inputDir, $files, $localPath, $compressionMethod); + + return $this; + } + + /** + * @param string $fileSystemDir + * @param array $files + * @param string $zipPath + * @param int|null $compressionMethod + * + * @throws ZipException + */ + private function doAddFiles($fileSystemDir, array $files, $zipPath, $compressionMethod = null) + { + $fileSystemDir = rtrim($fileSystemDir, '/\\') . \DIRECTORY_SEPARATOR; + + if (!empty($zipPath) && \is_string($zipPath)) { + $zipPath = trim($zipPath, '\\/') . '/'; + } else { + $zipPath = '/'; + } + + /** + * @var string $file + */ + foreach ($files as $file) { + $filename = str_replace($fileSystemDir, $zipPath, $file); + $filename = ltrim($filename, '\\/'); + + if (is_dir($file) && FilesUtil::isEmptyDir($file)) { + $this->addEmptyDir($filename); + } elseif (is_file($file)) { + $this->addFile($file, $filename, $compressionMethod); + } + } + } + + /** + * Add files recursively from regex pattern. + * + * @param string $inputDir search files in this directory + * @param string $regexPattern regex pattern + * @param string $localPath add files to this directory, or the root + * @param int|null $compressionMethod Compression method. + * Use {@see ZipCompressionMethod::STORED}, + * {@see ZipCompressionMethod::DEFLATED} or + * {@see ZipCompressionMethod::BZIP2}. If null, then auto choosing method. + * + * @throws ZipException + * + * @return ZipFile + * + * @internal param bool $recursive Recursive search + */ + public function addFilesFromRegexRecursive($inputDir, $regexPattern, $localPath = '/', $compressionMethod = null) + { + return $this->addRegex($inputDir, $regexPattern, $localPath, true, $compressionMethod); + } + + /** + * Add array data to archive. + * Keys is local names. + * Values is contents. + * + * @param array $mapData associative array for added to zip + */ + public function addAll(array $mapData) + { + foreach ($mapData as $localName => $content) { + $this[$localName] = $content; + } + } + + /** + * Rename the entry. + * + * @param string $oldName old entry name + * @param string $newName new entry name + * + * @throws ZipException + * + * @return ZipFile + */ + public function rename($oldName, $newName) + { + if ($oldName === null || $newName === null) { + throw new InvalidArgumentException('name is null'); + } + $oldName = ltrim((string) $oldName, '\\/'); + $newName = ltrim((string) $newName, '\\/'); + + if ($oldName !== $newName) { + $this->zipContainer->renameEntry($oldName, $newName); + } + + return $this; + } + + /** + * Delete entry by name. + * + * @param string $entryName zip Entry name + * + * @throws ZipEntryNotFoundException if entry not found + * + * @return ZipFile + */ + public function deleteFromName($entryName) + { + $entryName = ltrim((string) $entryName, '\\/'); + + if (!$this->zipContainer->deleteEntry($entryName)) { + throw new ZipEntryNotFoundException($entryName); + } + + return $this; + } + + /** + * Delete entries by glob pattern. + * + * @param string $globPattern Glob pattern + * + * @return ZipFile + * @sse https://en.wikipedia.org/wiki/Glob_(programming) Glob pattern syntax + */ + public function deleteFromGlob($globPattern) + { + if ($globPattern === null || !\is_string($globPattern) || empty($globPattern)) { + throw new InvalidArgumentException('The glob pattern is not specified'); + } + $globPattern = '~' . FilesUtil::convertGlobToRegEx($globPattern) . '~si'; + $this->deleteFromRegex($globPattern); + + return $this; + } + + /** + * Delete entries by regex pattern. + * + * @param string $regexPattern Regex pattern + * + * @return ZipFile + */ + public function deleteFromRegex($regexPattern) + { + if ($regexPattern === null || !\is_string($regexPattern) || empty($regexPattern)) { + throw new InvalidArgumentException('The regex pattern is not specified'); + } + $this->matcher()->match($regexPattern)->delete(); + + return $this; + } + + /** + * Delete all entries. + * + * @return ZipFile + */ + public function deleteAll() + { + $this->zipContainer->deleteAll(); + + return $this; + } + + /** + * Set compression level for new entries. + * + * @param int $compressionLevel + * + * @return ZipFile + * + * @see ZipCompressionLevel::NORMAL + * @see ZipCompressionLevel::SUPER_FAST + * @see ZipCompressionLevel::FAST + * @see ZipCompressionLevel::MAXIMUM + */ + public function setCompressionLevel($compressionLevel = ZipCompressionLevel::NORMAL) + { + $compressionLevel = (int) $compressionLevel; + + foreach ($this->zipContainer->getEntries() as $entry) { + $entry->setCompressionLevel($compressionLevel); + } + + return $this; + } + + /** + * @param string $entryName + * @param int $compressionLevel + * + * @throws ZipException + * + * @return ZipFile + * + * @see ZipCompressionLevel::NORMAL + * @see ZipCompressionLevel::SUPER_FAST + * @see ZipCompressionLevel::FAST + * @see ZipCompressionLevel::MAXIMUM + */ + public function setCompressionLevelEntry($entryName, $compressionLevel) + { + $compressionLevel = (int) $compressionLevel; + $this->getEntry($entryName)->setCompressionLevel($compressionLevel); + + return $this; + } + + /** + * @param string $entryName + * @param int $compressionMethod Compression method. + * Use {@see ZipCompressionMethod::STORED}, {@see ZipCompressionMethod::DEFLATED} + * or + * {@see ZipCompressionMethod::BZIP2}. If null, then auto choosing method. + * + * @throws ZipException + * + * @return ZipFile + * + * @see ZipCompressionMethod::STORED + * @see ZipCompressionMethod::DEFLATED + * @see ZipCompressionMethod::BZIP2 + */ + public function setCompressionMethodEntry($entryName, $compressionMethod) + { + $this->zipContainer + ->getEntry($entryName) + ->setCompressionMethod($compressionMethod) + ; + + return $this; + } + + /** + * zipalign is optimization to Android application (APK) files. + * + * @param int|null $align + * + * @return ZipFile + * + * @see https://developer.android.com/studio/command-line/zipalign.html + */ + public function setZipAlign($align = null) + { + $this->zipContainer->setZipAlign($align); + + return $this; + } + + /** + * Set password to all input encrypted entries. + * + * @param string $password Password + * + * @return ZipFile + */ + public function setReadPassword($password) + { + $this->zipContainer->setReadPassword($password); + + return $this; + } + + /** + * Set password to concrete input entry. + * + * @param string $entryName + * @param string $password Password + * + * @throws ZipException + * + * @return ZipFile + */ + public function setReadPasswordEntry($entryName, $password) + { + $this->zipContainer->setReadPasswordEntry($entryName, $password); + + return $this; + } + + /** + * Sets a new password for all files in the archive. + * + * @param string $password Password + * @param int|null $encryptionMethod Encryption method + * + * @return ZipFile + */ + public function setPassword($password, $encryptionMethod = ZipEncryptionMethod::WINZIP_AES_256) + { + $this->zipContainer->setWritePassword($password); + + if ($encryptionMethod !== null) { + $this->zipContainer->setEncryptionMethod($encryptionMethod); + } + + return $this; + } + + /** + * Sets a new password of an entry defined by its name. + * + * @param string $entryName + * @param string $password + * @param int|null $encryptionMethod + * + * @throws ZipException + * + * @return ZipFile + */ + public function setPasswordEntry($entryName, $password, $encryptionMethod = null) + { + $this->getEntry($entryName)->setPassword($password, $encryptionMethod); + + return $this; + } + + /** + * Disable encryption for all entries that are already in the archive. + * + * @return ZipFile + */ + public function disableEncryption() + { + $this->zipContainer->removePassword(); + + return $this; + } + + /** + * Disable encryption of an entry defined by its name. + * + * @param string $entryName + * + * @return ZipFile + */ + public function disableEncryptionEntry($entryName) + { + $this->zipContainer->removePasswordEntry($entryName); + + return $this; + } + + /** + * Undo all changes done in the archive. + * + * @return ZipFile + */ + public function unchangeAll() + { + $this->zipContainer->unchangeAll(); + + return $this; + } + + /** + * Undo change archive comment. + * + * @return ZipFile + */ + public function unchangeArchiveComment() + { + $this->zipContainer->unchangeArchiveComment(); + + return $this; + } + + /** + * Revert all changes done to an entry with the given name. + * + * @param string|ZipEntry $entry Entry name or ZipEntry + * + * @return ZipFile + */ + public function unchangeEntry($entry) + { + $this->zipContainer->unchangeEntry($entry); + + return $this; + } + + /** + * Save as file. + * + * @param string $filename Output filename + * + * @throws ZipException + * + * @return ZipFile + */ + public function saveAsFile($filename) + { + $filename = (string) $filename; + + $tempFilename = $filename . '.temp' . uniqid('', false); + + if (!($handle = @fopen($tempFilename, 'w+b'))) { + throw new InvalidArgumentException(sprintf('Cannot open "%s" for writing.', $tempFilename)); + } + $this->saveAsStream($handle); + + $reopen = false; + + if ($this->reader !== null) { + $meta = $this->reader->getStreamMetaData(); + + if ($meta['wrapper_type'] === 'plainfile' && isset($meta['uri'])) { + $readFilePath = realpath($meta['uri']); + $writeFilePath = realpath($filename); + + if ($readFilePath !== false && $writeFilePath !== false && $readFilePath === $writeFilePath) { + $this->reader->close(); + $reopen = true; + } + } + } + + if (!@rename($tempFilename, $filename)) { + if (is_file($tempFilename)) { + unlink($tempFilename); + } + + throw new ZipException(sprintf('Cannot move %s to %s', $tempFilename, $filename)); + } + + if ($reopen) { + return $this->openFile($filename); + } + + return $this; + } + + /** + * Save as stream. + * + * @param resource $handle Output stream resource + * + * @throws ZipException + * + * @return ZipFile + */ + public function saveAsStream($handle) + { + if (!\is_resource($handle)) { + throw new InvalidArgumentException('handle is not resource'); + } + ftruncate($handle, 0); + $this->writeZipToStream($handle); + fclose($handle); + + return $this; + } + + /** + * Output .ZIP archive as attachment. + * Die after output. + * + * @param string $outputFilename Output filename + * @param string|null $mimeType Mime-Type + * @param bool $attachment Http Header 'Content-Disposition' if true then attachment otherwise inline + * + * @throws ZipException + */ + public function outputAsAttachment($outputFilename, $mimeType = null, $attachment = true) + { + $outputFilename = (string) $outputFilename; + + if ($mimeType === null) { + $mimeType = $this->getMimeTypeByFilename($outputFilename); + } + + if (!($handle = fopen('php://temp', 'w+b'))) { + throw new InvalidArgumentException('php://temp cannot open for write.'); + } + $this->writeZipToStream($handle); + $this->close(); + + $size = fstat($handle)['size']; + + $headerContentDisposition = 'Content-Disposition: ' . ($attachment ? 'attachment' : 'inline'); + + if (!empty($outputFilename)) { + $headerContentDisposition .= '; filename="' . basename($outputFilename) . '"'; + } + + header($headerContentDisposition); + header('Content-Type: ' . $mimeType); + header('Content-Length: ' . $size); + + rewind($handle); + + try { + echo stream_get_contents($handle, -1, 0); + } finally { + fclose($handle); + } + } + + /** + * @param string $outputFilename + * + * @return string + */ + protected function getMimeTypeByFilename($outputFilename) + { + $outputFilename = (string) $outputFilename; + $ext = strtolower(pathinfo($outputFilename, \PATHINFO_EXTENSION)); + + if (!empty($ext) && isset(self::$defaultMimeTypes[$ext])) { + return self::$defaultMimeTypes[$ext]; + } + + return self::$defaultMimeTypes['zip']; + } + + /** + * Output .ZIP archive as PSR-7 Response. + * + * @param ResponseInterface $response Instance PSR-7 Response + * @param string $outputFilename Output filename + * @param string|null $mimeType Mime-Type + * @param bool $attachment Http Header 'Content-Disposition' if true then attachment otherwise inline + * + * @throws ZipException + * + * @return ResponseInterface + */ + public function outputAsResponse(ResponseInterface $response, $outputFilename, $mimeType = null, $attachment = true) + { + $outputFilename = (string) $outputFilename; + + if ($mimeType === null) { + $mimeType = $this->getMimeTypeByFilename($outputFilename); + } + + if (!($handle = fopen('php://temp', 'w+b'))) { + throw new InvalidArgumentException('php://temp cannot open for write.'); + } + $this->writeZipToStream($handle); + $this->close(); + rewind($handle); + + $contentDispositionValue = ($attachment ? 'attachment' : 'inline'); + + if (!empty($outputFilename)) { + $contentDispositionValue .= '; filename="' . basename($outputFilename) . '"'; + } + + $stream = new ResponseStream($handle); + $size = $stream->getSize(); + + if ($size !== null) { + /** @noinspection CallableParameterUseCaseInTypeContextInspection */ + $response = $response->withHeader('Content-Length', (string) $size); + } + + return $response + ->withHeader('Content-Type', $mimeType) + ->withHeader('Content-Disposition', $contentDispositionValue) + ->withBody($stream) + ; + } + + /** + * @param resource $handle + * + * @throws ZipException + */ + protected function writeZipToStream($handle) + { + $this->onBeforeSave(); + + $this->createZipWriter()->write($handle); + } + + /** + * Returns the zip archive as a string. + * + * @throws ZipException + * + * @return string + */ + public function outputAsString() + { + if (!($handle = fopen('php://temp', 'w+b'))) { + throw new InvalidArgumentException('php://temp cannot open for write.'); + } + $this->writeZipToStream($handle); + rewind($handle); + + try { + return stream_get_contents($handle); + } finally { + fclose($handle); + } + } + + /** + * Event before save or output. + */ + protected function onBeforeSave() + { + } + + /** + * Close zip archive and release input stream. + */ + public function close() + { + if ($this->reader !== null) { + $this->reader->close(); + $this->reader = null; + } + $this->zipContainer = $this->createZipContainer(null); + gc_collect_cycles(); + } + + /** + * Save and reopen zip archive. + * + * @throws ZipException + * + * @return ZipFile + */ + public function rewrite() + { + if ($this->reader === null) { + throw new ZipException('input stream is null'); + } + + $meta = $this->reader->getStreamMetaData(); + + if ($meta['wrapper_type'] !== 'plainfile' || !isset($meta['uri'])) { + throw new ZipException('Overwrite is only supported for open local files.'); + } + + return $this->saveAsFile($meta['uri']); + } + + /** + * Release all resources. + */ + public function __destruct() + { + $this->close(); + } + + /** + * Offset to set. + * + * @see http://php.net/manual/en/arrayaccess.offsetset.php + * + * @param string $entryName the offset to assign the value to + * @param string|\DirectoryIterator|\SplFileInfo|resource $contents the value to set + * + * @throws ZipException + * + * @see ZipFile::addFromString + * @see ZipFile::addEmptyDir + * @see ZipFile::addFile + * @see ZipFile::addFilesFromIterator + */ + public function offsetSet($entryName, $contents) + { + if ($entryName === null) { + throw new InvalidArgumentException('Key must not be null, but must contain the name of the zip entry.'); + } + $entryName = ltrim((string) $entryName, '\\/'); + + if ($entryName === '') { + throw new InvalidArgumentException('Key is empty, but must contain the name of the zip entry.'); + } + + if ($contents instanceof \DirectoryIterator) { + $this->addFilesFromIterator($contents, $entryName); + } elseif ($contents instanceof \SplFileInfo) { + $this->addSplFile($contents, $entryName); + } elseif (StringUtil::endsWith($entryName, '/')) { + $this->addEmptyDir($entryName); + } elseif (\is_resource($contents)) { + $this->addFromStream($contents, $entryName); + } else { + $this->addFromString($entryName, (string) $contents); + } + } + + /** + * Offset to unset. + * + * @see http://php.net/manual/en/arrayaccess.offsetunset.php + * + * @param string $entryName the offset to unset + * + * @throws ZipEntryNotFoundException + */ + public function offsetUnset($entryName) + { + $this->deleteFromName($entryName); + } + + /** + * Return the current element. + * + * @see http://php.net/manual/en/iterator.current.php + * + * @throws ZipException + * + * @return mixed can return any type + * + * @since 5.0.0 + */ + public function current() + { + return $this->offsetGet($this->key()); + } + + /** + * Offset to retrieve. + * + * @see http://php.net/manual/en/arrayaccess.offsetget.php + * + * @param string $entryName the offset to retrieve + * + * @throws ZipException + * + * @return string|null + */ + public function offsetGet($entryName) + { + return $this->getEntryContents($entryName); + } + + /** + * Return the key of the current element. + * + * @see http://php.net/manual/en/iterator.key.php + * + * @return mixed scalar on success, or null on failure + * + * @since 5.0.0 + */ + public function key() + { + return key($this->zipContainer->getEntries()); + } + + /** + * Move forward to next element. + * + * @see http://php.net/manual/en/iterator.next.php + * @since 5.0.0 + */ + public function next() + { + next($this->zipContainer->getEntries()); + } + + /** + * Checks if current position is valid. + * + * @see http://php.net/manual/en/iterator.valid.php + * + * @return bool The return value will be casted to boolean and then evaluated. + * Returns true on success or false on failure. + * + * @since 5.0.0 + */ + public function valid() + { + return $this->offsetExists($this->key()); + } + + /** + * Whether a offset exists. + * + * @see http://php.net/manual/en/arrayaccess.offsetexists.php + * + * @param string $entryName an offset to check for + * + * @return bool true on success or false on failure. + * The return value will be casted to boolean if non-boolean was returned. + */ + public function offsetExists($entryName) + { + return $this->hasEntry($entryName); + } + + /** + * Rewind the Iterator to the first element. + * + * @see http://php.net/manual/en/iterator.rewind.php + * @since 5.0.0 + */ + public function rewind() + { + reset($this->zipContainer->getEntries()); + } +} diff --git a/vendor/nelexa/zip/src/ZipFileInterface.php b/vendor/nelexa/zip/src/ZipFileInterface.php new file mode 100644 index 0000000..07108d1 --- /dev/null +++ b/vendor/nelexa/zip/src/ZipFileInterface.php @@ -0,0 +1,902 @@ + +
    +  创造不息,交付不止 +
    + + + +

    + +:cn: 基于 [CC-CEDICT](http://cc-cedict.org/wiki/) 词典的中文转拼音工具,更准确的支持多音字的汉字转拼音解决方案。 + + +## 安装 + +使用 Composer 安装: + +``` +composer require "overtrue/pinyin:~3.0" +``` + +## 使用 + +可选转换方案: + + - 内存型,适用于服务器内存空间较富余,优点:转换快 + - 小内存型(默认),适用于内存比较紧张的环境,优点:占用内存小,转换不如内存型快 + - I/O型,适用于虚拟机,内存限制比较严格环境。优点:非常微小内存消耗。缺点:转换慢,不如内存型转换快,php >= 5.5 + +### 拼音数组 + +```php +use Overtrue\Pinyin\Pinyin; + +// 小内存型 +$pinyin = new Pinyin(); // 默认 +// 内存型 +// $pinyin = new Pinyin('Overtrue\Pinyin\MemoryFileDictLoader'); +// I/O型 +// $pinyin = new Pinyin('Overtrue\Pinyin\GeneratorFileDictLoader'); + +$pinyin->convert('带着希望去旅行,比到达终点更美好'); +// ["dai", "zhe", "xi", "wang", "qu", "lv", "xing", "bi", "dao", "da", "zhong", "dian", "geng", "mei", "hao"] + +$pinyin->convert('带着希望去旅行,比到达终点更美好', PINYIN_UNICODE); +// ["dài","zhe","xī","wàng","qù","lǚ","xíng","bǐ","dào","dá","zhōng","diǎn","gèng","měi","hǎo"] + +$pinyin->convert('带着希望去旅行,比到达终点更美好', PINYIN_ASCII); +//["dai4","zhe","xi1","wang4","qu4","lv3","xing2","bi3","dao4","da2","zhong1","dian3","geng4","mei3","hao3"] +``` + +- 小内存型: 将字典分片载入内存 +- 内存型: 将所有字典预先载入内存 +- I/O型: 不载入内存,将字典使用文件流打开逐行遍历并运用php5.5生成器(yield)特性分配单行内存 + + +选项: + +| 选项 | 描述 | +| ------------- | ---------------------------------------------------| +| `PINYIN_NONE` | 不带音调输出: `mei hao` | +| `PINYIN_ASCII` | 带数字式音调: `mei3 hao3` | +| `PINYIN_UNICODE` | UNICODE 式音调:`měi hǎo` | + +### 生成用于链接的拼音字符串 + +```php +$pinyin->permalink('带着希望去旅行'); // dai-zhe-xi-wang-qu-lv-xing +$pinyin->permalink('带着希望去旅行', '.'); // dai.zhe.xi.wang.qu.lv.xing +``` + +### 获取首字符字符串 + +```php +$pinyin->abbr('带着希望去旅行'); // dzxwqlx +$pinyin->abbr('带着希望去旅行', '-'); // d-z-x-w-q-l-x +``` + +### 翻译整段文字为拼音 + +将会保留中文字符:`,。 ! ? : “ ” ‘ ’` 并替换为对应的英文符号。 + +```php +$pinyin->sentence('带着希望去旅行,比到达终点更美好!'); +// dai zhe xi wang qu lv xing, bi dao da zhong dian geng mei hao! + +$pinyin->sentence('带着希望去旅行,比到达终点更美好!', true); +// dài zhe xī wàng qù lǚ xíng, bǐ dào dá zhōng diǎn gèng měi hǎo! +``` + +### 翻译姓名 + +姓名的姓的读音有些与普通字不一样,比如 ‘单’ 常见的音为 `dan`,而作为姓的时候读 `shan`。 + +```php +$pinyin->name('单某某'); // ['shan', 'mou', 'mou'] +$pinyin->name('单某某', PINYIN_UNICODE); // ["shàn","mǒu","mǒu"] +``` + +## 在 Laravel 中使用 + +独立的包在这里:[overtrue/laravel-pinyin](https://github.com/overtrue/laravel-pinyin) + +## Contribution +欢迎提意见及完善补充词库 [`overtrue/pinyin-dictionary-maker`](https://github.com/overtrue/pinyin-dictionary-maker/tree/master/patches) :kiss: + +## 参考 + +- [详细参考资料](https://github.com/overtrue/pinyin-resources) + +# License + +MIT diff --git a/vendor/overtrue/pinyin/composer.json b/vendor/overtrue/pinyin/composer.json new file mode 100644 index 0000000..4b73677 --- /dev/null +++ b/vendor/overtrue/pinyin/composer.json @@ -0,0 +1,33 @@ +{ + "name": "overtrue/pinyin", + "description": "Chinese to pinyin translator.", + "keywords": [ + "chinese", + "pinyin", + "cn2pinyin" + ], + "homepage": "https://github.com/overtrue/pinyin", + "license": "MIT", + "authors": [ + { + "name": "Carlos", + "homepage": "http://github.com/overtrue" + } + ], + "autoload": { + "psr-4": { + "Overtrue\\Pinyin\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Overtrue\\Pinyin\\Test\\": "tests/" + } + }, + "require": { + "php":">=5.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + } +} diff --git a/vendor/overtrue/pinyin/data/surnames b/vendor/overtrue/pinyin/data/surnames new file mode 100644 index 0000000..9f6072a --- /dev/null +++ b/vendor/overtrue/pinyin/data/surnames @@ -0,0 +1,84 @@ + ' mò qí', + '尉迟' => ' yù chí', + '单于' => ' chán yú', + '不' => ' fǒu', + '沈' => ' shěn', + '称' => ' chēng', + '车' => ' chē', + '万' => ' wàn', + '汤' => ' tāng', + '阿' => ' ā', + '丁' => ' dīng', + '强' => ' qiáng', + '仇' => ' qiú', + '叶' => ' yè', + '阚' => ' kàn', + '乐' => ' yuè', + '乜' => ' niè', + '陆' => ' lù', + '殷' => ' yīn', + '牟' => ' móu', + '区' => ' ōu', + '宿' => ' sù', + '俞' => ' yú', + '余' => ' yú', + '齐' => ' qí', + '许' => ' xǔ', + '信' => ' xìn', + '无' => ' wú', + '浣' => ' wǎn', + '艾' => ' ài', + '浅' => ' qiǎn', + '烟' => ' yān', + '蓝' => ' lán', + '於' => ' yú', + '寻' => ' xún', + '殳' => ' shū', + '思' => ' sī', + '鸟' => ' niǎo', + '卜' => ' bǔ', + '单' => ' shàn', + '南' => ' nán', + '柏' => ' bǎi', + '朴' => ' piáo', + '繁' => ' pó', + '曾' => ' zēng', + '瞿' => ' qú', + '缪' => ' miào', + '石' => ' shí', + '冯' => ' féng', + '覃' => ' qín', + '幺' => ' yāo', + '种' => ' chóng', + '折' => ' shè', + '燕' => ' yān', + '纪' => ' jǐ', + '过' => ' guō', + '华' => ' huà', + '冼' => ' xiǎn', + '秘' => ' bì', + '重' => ' chóng', + '解' => ' xiè', + '那' => ' nā', + '和' => ' hé', + '贾' => ' jiǎ', + '塔' => ' tǎ', + '盛' => ' shèng', + '查' => ' zhā', + '盖' => ' gě', + '居' => ' jū', + '哈' => ' hǎ', + '的' => ' dē', + '薄' => ' bó', + '佴' => ' nài', + '六' => ' lù', + '都' => ' dū', + '翟' => ' zhái', + '扎' => ' zā', + '藏' => ' zàng', + '粘' => ' niàn', + '难' => ' nàn', + '若' => ' ruò', +); \ No newline at end of file diff --git a/vendor/overtrue/pinyin/data/words_0 b/vendor/overtrue/pinyin/data/words_0 new file mode 100644 index 0000000..c5ec409 --- /dev/null +++ b/vendor/overtrue/pinyin/data/words_0 @@ -0,0 +1,8003 @@ + ' liàng liàng qiàng qiàng', + '可逆反应' => ' kě nì fǎn yìng', + '口腹之累' => ' kǒu fù zhī lěi', + '质疑问难' => ' zhì yí wèn nàn', + '收入差距' => ' shōu rù chā jù', + '直言贾祸' => ' zhí yán gǔ huò', + '刻鹄成鹜' => ' kè hú chéng wù', + '克尽厥职' => ' kè jìn jué zhí', + '收支差额' => ' shōu zhī chā é', + '首足异处' => ' shǒu zú yì chǔ', + '置之高阁' => ' zhì zhī gāo gé', + '还珠合浦' => ' huán zhū hé pǔ', + '认贼作子' => ' rèn zéi zuò zǐ', + '患难夫妻' => ' huàn nàn fū qī', + '课嘴撩牙' => ' kè zuǐ liáo yá', + '认贼为子' => ' rèn zéi wéi zǐ', + '还朴反古' => ' huán pǔ fǎn gǔ', + '长安棋局' => ' cháng ān qí jú', + '看不习惯' => ' kān bù xí guàn', + '只鸡樽酒' => ' zhī jī zūn jiǔ', + '华亭鹤唳' => ' huà tíng hè lì', + '芝麻开花' => ' zhī ma kāi huā', + '破涕为笑' => ' pò tì wéi xiào', + '陟罚臧否' => ' zhì fá zāng pǐ', + '知识分子' => ' zhī shí fèn zǐ', + '知识宝库' => ' zhī shi bǎo kù', + '认贼为父' => ' rèn zéi wéi fù', + '苦苦相逼' => ' kǔ kǔ xiàng bī', + '至当不易' => ' zhì dàng bù yì', + '数米而炊' => ' shǔ mǐ ér chuī', + '稚齿婑媠' => ' zhì chǐ wǒ tuǒ', + '差之毫厘' => ' chā zhī háo lí', + '科学知识' => ' kē xué zhī shi', + '差三错四' => ' chā sān cuò sì', + '画龙刻鹄' => ' huà lóng kè hú', + '会计分录' => ' kuài jì fēn lù', + '化害为利' => ' huà hài wéi lì', + '辙乱旗靡' => ' zhé luàn qí mǐ', + '画荻和丸' => ' huà dí huò wán', + '测量记录' => ' cè liáng jì lù', + '会计信息' => ' kuài jì xìn xī', + '看在眼里' => ' kàn zài yǎn li', + '只轮无反' => ' zhī lún wú fǎn', + '数米量柴' => ' shǔ mǐ ér chái', + '尽其所有' => ' jìn qí suǒ yǒu', + '耻与哙伍' => ' chǐ yú kuài wǔ', + '子宫内膜' => ' zǐ gōng nèi mó', + '温情脉脉' => ' wēn qíng mò mò', + '简丝数米' => ' jiǎn sī shǔ mǐ', + '救苦救难' => ' jiù kǔ jiù nàn', + '三灾八难' => ' sān zāi bā nàn', + '救黥医劓' => ' jiù qíng yī yì', + '身单力薄' => ' shēn dān lì bó', + '竭尽心力' => ' jié jìn xīn lì', + '九行八业' => ' jiǔ háng bā yè', + '泊松分布' => ' bó sōng fēn bù', + '称体裁衣' => ' chèn tǐ cái yī', + '解衣磅礴' => ' jiě yī páng bó', + '设计要求' => ' shè jì yāo qiú', + '解衣卸甲' => ' jiě yī xiè jiǎ', + '竭智尽力' => ' jié zhì jìn lì', + '尽瘁至死' => ' jìn cuì zhì sǐ', + '山雨欲来' => ' shān yǔ yù lái', + '齿牙为祸' => ' chǐ yá wéi huò', + '齿牙为猾' => ' chǐ yá wéi huá', + '只轮不反' => ' zhī lún bù fǎn', + '混水摸鱼' => ' hún shuǐ mō yú', + '朝云暮雨' => ' zhāo yún mù yǔ', + '逐物不还' => ' zhú wù bù huán', + '独生子女' => ' dú shēng zǐ nǚ', + '深信不疑' => ' shēn xìn bù yí', + '租赁合同' => ' zū lìn hé tong', + '甚而至于' => ' shèn ér zhì yú', + '杀鸡为黍' => ' shā jī wéi shǔ', + '经史子集' => ' jīng shǐ zǐ jí', + '煞费心机' => ' shà fèi xīn jī', + '见义必为' => ' jiàn yì bì wéi', + '混为一谈' => ' hùn wéi yī tán', + '子曰诗云' => ' zǐ yuè shī yún', + '扫眉才子' => ' sǎo méi cái zǐ', + '九垓八埏' => ' jiǔ gāi bā yán', + '自我调节' => ' zì wǒ tiáo jié', + '金鳷擘海' => ' jīn zhī bò hǎi', + '子孙万代' => ' zǐ sūn wàn dài', + '子孙后代' => ' zǐ sūn hòu dài', + '指腹为婚' => ' zhǐ fù wéi hūn', + '尽职尽责' => ' jìn zhí jìn zé', + '尽态极妍' => ' jìn tài jí yán', + '安身为乐' => ' ān shēn wéi lè', + '见素抱朴' => ' xiàn sù bào pǔ', + '阿房宫赋' => ' ē páng gōng fù', + '屙金溺银' => ' ē jīn niào yín', + '遐迩著闻' => ' xiá ěr zhù wén', + '阿党比周' => ' ē dǎng bǐ zhōu', + '朵颐大嚼' => ' duǒ yī dà jiáo', + '误作非为' => ' wù zuò fēi wéi', + '系统设计' => ' xì tǒng shè jì', + '倒峡泻河' => ' dǎo xiá xiè hé', + '阿谀逢迎' => ' ē yú féng yíng', + '捣虚批吭' => ' dǎo xū pī háng', + '攀藤附葛' => ' pān téng fù gě', + '晰毛辨发' => ' xī máo biàn fà', + '多文为富' => ' duō wén wéi fù', + '垂头搨翼' => ' chuí tóu dá yì', + '画荻教子' => ' huà dí jiào zǐ', + '倡而不和' => ' chàng ér bù hè', + '攫为己有' => ' jué wéi jǐ yǒu', + '细高挑儿' => ' xì gāo tiǎo ér', + '披发文身' => ' pī fà wén shēn', + '当回事儿' => ' dàng huí shì r', + '捧心西子' => ' pěng xīn xī zǐ', + '捱三顶五' => ' āi sān dǐng wǔ', + '捱三顶四' => ' āi sān dǐng sì', + '爱人好士' => ' ài rén hào shì', + '旁指曲谕' => ' páng zhǐ qǔ yù', + '无下箸处' => ' wú xià zhù chǔ', + '跑马卖解' => ' pǎo mǎ mài xiè', + '挨冻受饿' => ' ái dòng shòu è', + '顿开茅塞' => ' dùn kāi máo sè', + '睹著知微' => ' dǔ zhù zhī wēi', + '睹微知著' => ' dǔ wēi zhī zhù', + '开锣喝道' => ' kāi luó hè dào', + '倒载干戈' => ' dào zài gān gē', + '阿平绝倒' => ' ā píng jué dǎo', + '无为之治' => ' wú wéi zhī zhì', + '同心一意' => ' tóng xīn yī yì', + '批风抹月' => ' pī fēng mò yuè', + '鼠疫杆菌' => ' shǔ yì gǎn jūn', + '污水处理' => ' wū shuǐ chǔ lǐ', + '货而不售' => ' huo er bu shou', + '绝子绝孙' => ' jué zǐ jué sūn', + '捐躯赴难' => ' juān qū fù nàn', + '日新月著' => ' rì xīn yuè zhù', + '积谗糜骨' => ' jī chán méi gǔ', + '琴瑟不调' => ' qín sè bù tiáo', + '竹篱茅舍' => ' zhú lí máo shè', + '绝对误差' => ' jué duì wù chā', + '豁然开悟' => ' huò rán kāi wù', + '几不欲生' => ' jī bù yù shēng', + '基本要求' => ' jī běn yāo qiú', + '基本电荷' => ' jī běn diàn hè', + '什伍东西' => ' shí wǔ dōng xī', + '十魔九难' => ' shí mó jiǔ nàn', + '铢积寸累' => ' zhū jī cùn lěi', + '用得其所' => ' yòng de qí suǒ', + '什袭而藏' => ' shí xī ér cáng', + '珠还合浦' => ' zhū huán hé pǔ', + '什袭以藏' => ' shí xí yǐ cáng', + '男子篮球' => ' nán zǐ lán qíu', + '豁然确斯' => ' huò rán què sī', + '日省月课' => ' rì xǐng yuè kè', + '积德累善' => ' jī dé lěi shàn', + '玩儿花招' => ' wán r huā zhāo', + '昼度夜思' => ' zhòu duó yè sī', + '事与心违' => ' shì yù xīn wéi', + '均匀分布' => ' jūn yún fēn bù', + '发奋有为' => ' fā fèn yǒu wéi', + '日进斗金' => ' rì jìn dǒu jīn', + '画地为牢' => ' huà dì wéi láo', + '白马王子' => ' bái mǎ wáng zǐ', + '魂不着体' => ' hún bù zhuó tǐ', + '君子好逑' => ' jūn zǐ hǎo qiú', + '君子三戒' => ' jūn zǐ sān jiè', + '益觉困难' => ' yì jué kùn nan', + '玩儿得转' => ' wán r dé zhuàn', + '诗云子曰' => ' shī yún zǐ yuè', + '尽心尽力' => ' jìn xīn jìn lì', + '晦盲否塞' => ' huì máng pǐ sè', + '百度知道' => ' bǎi dù zhī dao', + '尽心竭力' => ' jìn xīn jié lì', + '豁然顿悟' => ' huò rán dùn wù', + '急功好利' => ' jí gōng hào lì', + '朱盘玉敦' => ' zhū pán yù duì', + '活跃分子' => ' huó yuè fèn zǐ', + '设心处虑' => ' shè xīn chǔ lǜ', + '爱生恶死' => ' ài shēng wù sǐ', + '桂子兰孙' => ' guì zǐ lán sūn', + '鱼书雁帖' => ' yú shū yàn tiě', + '与世沉浮' => ' yú shì chén fú', + '思所逐之' => ' si shuo zhu zi', + '不当人子' => ' bù dāng rén zǐ', + '过甚其辞' => ' guò shèn qí cí', + '诱掖后进' => ' yòu yè hòu jìn', + '燎发摧枯' => ' liǎo fà cuī kū', + '过都历块' => ' guò dū lì kuài', + '舌下含服' => ' shé xià hán fù', + '不足为怪' => ' bù zú wéi guài', + '不差累黍' => ' bù chā lěi shǔ', + '舞台音乐' => ' wǔ tái yīn yuè', + '有人家儿' => ' yǒu rén jiā ér', + '撩云拨雨' => ' liáo yún bō yǔ', + '波属云委' => ' bō zhǔ yún wěi', + '桂折一枝' => ' guì shé yī zhī', + '苦尽甘来' => ' kǔ jìn gān lái', + '拨云撩雨' => ' bō yún liáo yǔ', + '财殚力尽' => ' cái dān lì jìn', + '麇至沓来' => ' qún zhì tà lái', + '茅塞顿开' => ' máo sè dùn kāi', + '哺糟啜醨' => ' bǔ zāo chuò lí', + '蠡测管窥' => ' lǐ cè guǎn kuī', + '贼臣逆子' => ' zéi chén nì zǐ', + '不足为凭' => ' bù zú wéi píng', + '凿龟数策' => ' záo guī shǔ cè', + '总理衙门' => ' zǒng lǐ yá men', + '臧否人物' => ' zāng pǐ rén wù', + '河清难俟' => ' hé qīng nán sì', + '载驰载驱' => ' zǎi chí zǎi qū', + '云朝雨暮' => ' yún zhāo yǔ mù', + '河落海干' => ' hé luò hǎi gān', + '罐头起子' => ' guàn tou qǐ zi', + '合浦珠还' => ' hé pǔ zhū huán', + '置之不问' => ' zhì zhī bù wèn', + '置之度外' => ' zhì zhī dù wài', + '合浦还珠' => ' hé pǔ huán zhū', + '罗锅儿桥' => ' luó guō r qiáo', + '云泥之差' => ' yún ní zhī chā', + '河南坠子' => ' hé nán zhuì zǐ', + '圭角不露' => ' guī jiǎo bù lù', + '拨雨撩云' => ' bō yǔ liáo yún', + '绿林好汉' => ' lù lín hǎo hàn', + '积累毒性' => ' jī lěi dú xìng', + '水尽鹅飞' => ' shuǐ jìn é fēi', + '气势磅礴' => ' qì shì páng bó', + '立地成佛' => ' lì dì chéng fó', + '财务会计' => ' cái wù kuài jì', + '朝发暮至' => ' zhāo fā mù zhì', + '积极反应' => ' jī jí fǎn yìng', + '粪金龟子' => ' fèn jīn guī zǐ', + '朝不保暮' => ' zhāo bù bǎo mù', + '材优干济' => ' cái yōu gàn jǐ', + '优游自若' => ' yōu yóu zì ruò', + '热闹非凡' => ' rè nao fēi fán', + '重足一迹' => ' chóng zú yī jì', + '朝闻夕死' => ' zhāo wén xī sǐ', + '鹄形菜色' => ' hú xíng cài sè', + '朝三暮二' => ' zhāo sān mù èr', + '劳务合同' => ' láo wù hé tong', + '笃信好学' => ' dǔ xìn hào xué', + '乐尽悲来' => ' lè jìn bēi lái', + '去甚去泰' => ' qù shèn qù tài', + '优游卒岁' => ' yōu yóu zú suì', + '拨嘴撩牙' => ' bō zuǐ liáo yá', + '于今为烈' => ' yú jīn wéi liè', + '力有未逮' => ' lì yǒu wèi dài', + '曲尽其妙' => ' qū jìn qí miào', + '俟河之清' => ' sì hé zhī qīng', + '离子反应' => ' lí zǐ fǎn yìng', + '舆论哗然' => ' yú lùn huá rán', + '自找麻烦' => ' zì zhǎo má fan', + '自由散漫' => ' zì yóu sǎn màn', + '好丹非素' => ' hào dān fēi sù', + '去太去甚' => ' qù tai qù shèn', + '不见舆薪' => ' bú jiàn yú xīn', + '兴都库什' => ' xīng dū kù shí', + '连鬓胡子' => ' lián bìn hú zǐ', + '与时消息' => ' yǔ shí xiāo xi', + '与此同时' => ' yú cǐ tóng shí', + '兴利除弊' => ' xīng lì chú bì', + '于家为国' => ' yú jiā wéi guó', + '兴安运河' => ' xīng ān yùn hé', + '鬻鸡为凤' => ' yù jī wéi fèng', + '没齿无怨' => ' mò chǐ wú yuàn', + '翘足而待' => ' qiáo zú ér dài', + '金尽裘敝' => ' jīn jìn qiú bì', + '狼子野心' => ' láng zǐ yě xīn', + '积岁累月' => ' jī suì lěi yuè', + '技术要求' => ' jì shù yāo qiú', + '肌肉萎缩' => ' jī ròu wěi suō', + '物尽其用' => ' wù jìn qí yòng', + '肉薄骨并' => ' ròu bó gǔ bìng', + '日月入怀' => ' rì yuè rù huái', + '主要部分' => ' zhǔ yào bù fen', + '奇偶校验' => ' jī ǒu jiào yàn', + '爆冷门儿' => ' bào lěng mén r', + '燕太子丹' => ' yān tài zǐ dān', + '漠然置之' => ' mò rán zhì zhī', + '无的放矢' => ' wú dì fàng shǐ', + '橘化为枳' => ' jú huà wéi zhǐ', + '擢发莫数' => ' zhuó fà mò shǔ', + '三邻四舍' => ' sān lín sì shè', + '剪发杜门' => ' jiǎn fà dù mén', + '三大差别' => ' sān dà chā bié', + '居不重席' => ' jū bù chóng xí', + '鞠为茂草' => ' jū wéi mào cǎo', + '聚米为山' => ' jù mǐ wéi shān', + '家无担石' => ' jiā wú dàn shí', + '独立钻石' => ' dú lì zuàn shí', + '浮云朝露' => ' fú yún zhāo lù', + '罪恶昭著' => ' zuì è zhāo zhù', + '蛇心佛口' => ' shé xīn fó kǒu', + '钻心刺骨' => ' zuàn xīn cì gǔ', + '折腰五斗' => ' shé yāo wǔ dòu', + '淋漓尽致' => ' lín lí jìn zhì', + '淡泊名利' => ' dàn bó míng lì', + '淡泊寡味' => ' dàn bó guǎ wèi', + '肩摩毂接' => ' jiān mó gū jiē', + '如登春台' => ' rú dé chūn tái', + '汉藏语系' => ' hàn zàng yǔ xì', + '无能为力' => ' wú néng wéi lì', + '骥子龙文' => ' jì zǐ lóng wén', + '积铢累寸' => ' jī zhū lěi cùn', + '煞费苦心' => ' shà fèi kǔ xīn', + '入理切情' => ' rù lǐ qiē qíng', + '拽耙扶犁' => ' zhuāi pá fú lí', + '拽布披麻' => ' zhuài bù pī má', + '灰不溜秋' => ' huī bu līu qīu', + '炭疽杆菌' => ' tàn jū gǎn jūn', + '赫赫之功' => ' hè hè zhì gōng', + '鱼游燋釜' => ' yú yóu zhuó fǔ', + '不肖子孙' => ' bù xiào zǐ sūn', + '群居穴处' => ' qún jū xué chǔ', + '好问决疑' => ' hào wèn jué yí', + '理论知识' => ' lǐ lùn zhī shi', + '解甲倒戈' => ' jiě jiǎ dǎo gē', + '好事之徒' => ' hào shì zhī tú', + '玉卮无当' => ' yù zhī wú dàng', + '处理方法' => ' chǔ lǐ fāng fǎ', + '听而不闻' => ' tīng ér bú wén', + '老鼠尾巴' => ' lǎo shǔ wěi ba', + '肉毒杆菌' => ' ròu dú gǎn jūn', + '胡作非为' => ' hú zuò fēi wéi', + '脉轮理论' => ' mài lún lǐ lùn', + '处理系统' => ' chǔ lǐ xì tǒng', + '不露圭角' => ' bù lù guī jiǎo', + '好骑者堕' => ' hào qí zhě duò', + '聚酯树脂' => ' jù zhǐ shù zhī', + '脊索动物' => ' jǐ suǒ dòng wù', + '考波什堡' => ' kǎo bō shí bǎo', + '呵壁问天' => ' hē bì wèn tiān', + '无业闲散' => ' wú yè xián sǎn', + '塞翁得马' => ' sài wēng dé mǎ', + '举措不当' => ' jǔ cuò bù dàng', + '三差五错' => ' sān chā wǔ cuò', + '三不拗六' => ' sān bù niù liù', + '为所欲为' => ' wéi suǒ yù wéi', + '为时不晚' => ' wéi shí bù wǎn', + '为时已晚' => ' wéi shí yǐ wǎn', + '假模假式' => ' jiǎ mú jiǎ shì', + '架肩击毂' => ' jià jiān jī gū', + '鞠躬君子' => ' jū gōng jūn zǐ', + '老少无欺' => ' lǎo shào wú qī', + '无所作为' => ' wú suǒ zuò wéi', + '聚合反应' => ' jù hé fǎn yìng', + '假日经济' => ' jià rì jīng jì', + '朝夷暮跖' => ' zhāo yí mù zhí', + '口似悬河' => ' kǒu sì xuán hé', + '鹤发童颜' => ' hè fà tóng yán', + '离鸾别鹄' => ' lí luán bié hú', + '河伯为患' => ' hé bó wéi huàn', + '挨山塞海' => ' āi shān sè hǎi', + '阿世盗名' => ' ē shì dào míng', + '率马以骥' => ' shuài mǎ yǐ jì', + '临深履薄' => ' lín shēn lǚ bó', + '冰解的破' => ' bīng jiě dì pò', + '狗颠屁股' => ' gǒu diān pì gu', + '姑射神人' => ' gū yè shén rén', + '因树为屋' => ' yīn shù wéi wū', + '伯乐相马' => ' bó lè xiàng mǎ', + '广譬曲谕' => ' guǎng pì qǔ yù', + '萎靡不振' => ' wěi mǐ bù zhèn', + '薄技在身' => ' bó jì zài shēn', + '观者如堵' => ' guān zhě rú dǔ', + '绿林豪杰' => ' lù lín háo jié', + '肆意妄为' => ' sì yì wàng wéi', + '波骇云属' => ' bō hài yún zhǔ', + '裘弊金尽' => ' qiú bì jīn jìn', + '应答如流' => ' yìng dá rú liú', + '鼓乐齐鸣' => ' gǔ yuè qí míng', + '引以为戒' => ' yǐn yǐ wéi jiè', + '蒙古大夫' => ' měng gǔ dài fu', + '引以为豪' => ' yǐn yǐ wéi háo', + '狗屁倒灶' => ' gǒu pì dǎo zào', + '一之已甚' => ' yī zhī yǐ shèn', + '流行歌曲' => ' liú xíng gē qǔ', + '虚心好学' => ' xū xīn hào xué', + '故态复还' => ' gù tài fù huán', + '苏打饼干' => ' sū dá bǐng gān', + '故事片儿' => ' gù shì piān er', + '七十二行' => ' qī shí èr háng', + '孤鸾寡鹄' => ' gū luán guǎ hú', + '因祸为福' => ' yīn huò wéi fú', + '孤鸿寡鹄' => ' gū hóng guǎ hú', + '处理能力' => ' chǔ lǐ néng lì', + '古调不弹' => ' gǔ diào bù tán', + '以珠弹雀' => ' yǐ zhū tán què', + '夙兴夜处' => ' sù xīng yè chǔ', + '孤臣孽子' => ' gū chén niè zǐ', + '露红烟绿' => ' lù hóng yān lǜ', + '随风而靡' => ' suí fēng ér mǐ', + '啜食吐哺' => ' chuò shí tǔ bǔ', + '融为一体' => ' róng wéi yī tǐ', + '衣租食税' => ' yì zū shí shuì', + '蛮横无理' => ' mán hèng wú lǐ', + '引以为耻' => ' yǐn yǐ wéi chǐ', + '吹弹得破' => ' chuī tán dé pò', + '背曲腰弯' => ' bèi qǔ yāo wān', + '一日三省' => ' yī rì sān xǐng', + '一目数行' => ' yī mù shù háng', + '岗头泽底' => ' gāng tóu zé dǐ', + '衣马轻肥' => ' yì mǎ qīng féi', + '弥缝其阙' => ' mí féng qí què', + '蜜里调油' => ' mì lǐ tiáo yóu', + '以利累形' => ' yǐ lì lěi xíng', + '调三窝四' => ' tiáo sān wō sì', + '遗老遗少' => ' yí lǎo yí shào', + '绰有余裕' => ' chuò yǒu yú yù', + '没头没尾' => ' méi tóu mò wěi', + '亲力亲为' => ' qīn lì qīn wéi', + '视为畏途' => ' shì wéi wèi tú', + '躬自菲薄' => ' gōng zì fěi bó', + '此唱彼和' => ' cǐ chàng bǐ hè', + '蹙蹙靡骋' => ' cù cù mǐ chěng', + '亿兆一心' => ' yì zhào yī xīn', + '掠美市恩' => ' lüě měi shì ēn', + '沓来麕至' => ' tà lái yǒu zhì', + '门殚户尽' => ' mén dān hù jìn', + '奔走呼号' => ' bēn zǒu hū háo', + '薄利多销' => ' bó lì duō xiāo', + '一头儿沉' => ' yī tou er chén', + '吹弹歌舞' => ' chuī tán gē wǔ', + '屏气慑息' => ' bǐng qì shè xī', + '寡鹄孤鸾' => ' guǎ hú gū luán', + '挂席为门' => ' guà xí wéi mén', + '调和分析' => ' tiáo hé fēn xī', + '情凄意切' => ' qíng qī yì qiē', + '绿林豪士' => ' lù lín háo shì', + '亲密无间' => ' qīn mì wú jiàn', + '寸积铢累' => ' cùn jī zhū lěi', + '依头缕当' => ' yī tóu lǚ dàng', + '观衅伺隙' => ' guān xìn sì xì', + '贪惏无餍' => ' tān lín wú yàn', + '避军三舍' => ' bì jūn sān shè', + '以是为恨' => ' yǐ shì wéi hèn', + '各自为战' => ' gè zì wéi zhàn', + '碧波万顷' => ' bì bō wàn qǐng', + '毛发倒竖' => ' máo fā dǎo shù', + '弹丝品竹' => ' tán sī pǐn zhú', + '以苦为荣' => ' yǐ kǔ wéi róng', + '谋为不轨' => ' móu wéi bù guǐ', + '衣裳钩儿' => ' yī shang gōu r', + '起重葫芦' => ' qǐ chóng hú lu', + '大难临头' => ' dà nàn lín tóu', + '费尽心思' => ' fèi jìn xīn si', + '大处着墨' => ' dà chù zhuó mò', + '质子轰击' => ' zhì zǐ hōng jī', + '大行大市' => ' dà háng dà shì', + '赤子之心' => ' chì zǐ zhī xīn', + '赤字累累' => ' chì zì lěi lěi', + '绵力薄材' => ' mián lì bó cái', + '起飞弹射' => ' qǐ fēi tán shè', + '以古为镜' => ' yǐ gǔ wéi jìng', + '一干人犯' => ' yī gān rén fàn', + '大辂椎轮' => ' dà lù zhuī lún', + '费尽心机' => ' fèi jìn xīn jī', + '一蹶不兴' => ' yī juě bù xīng', + '叨在知己' => ' tāo zài zhī jǐ', + '逃灾避难' => ' táo zāi bì nàn', + '哑子做梦' => ' yǎ zǐ zuò mèng', + '哑子寻梦' => ' yǎ zǐ xún mèng', + '以古为鉴' => ' yǐ gǔ wéi jiàn', + '霓裳羽衣' => ' ní cháng yǔ yī', + '芽孢杆菌' => ' yá bāo gǎn jūn', + '父严子孝' => ' fù yán zǐ xiào', + '福为祸始' => ' fú wéi huò shǐ', + '浮收勒索' => ' fú shōu lè suǒ', + '浮收勒折' => ' fú shōu lè shé', + '浮皮潦草' => ' fú pí liáo cǎo', + '名流巨子' => ' míng liú jù zǐ', + '仰事俯畜' => ' yǎng shì fǔ xù', + '迄今为止' => ' qì jīn wéi zhǐ', + '伏而咶天' => ' fú ér shì tiān', + '近似等级' => ' jìn sì děng jí', + '豪气干云' => ' háo qì gān yún', + '身体部分' => ' shēn tǐ bù fèn', + '切理厌心' => ' qiē lǐ yàn xīn', + '衣锦夜行' => ' yì jǐn yè xíng', + '诸子十家' => ' zhū zǐ shí jiā', + '诸子百家' => ' zhū zǐ bǎi jiā', + '一还一报' => ' yī huán yī bào', + '绵薄之力' => ' mián bó zhī lì', + '撮科打哄' => ' cuō kē dǎ hòng', + '哑子托梦' => ' yǎ zǐ tuō mèng', + '没食子酸' => ' méi sì zǐ suān', + '岩栖穴处' => ' yán qī xué chǔ', + '凤靡鸾吪' => ' fèng mǐ luán é', + '舍身饲虎' => ' shè shēn sì hǔ', + '莫此为甚' => ' mò cǐ wéi shèn', + '抹粉施脂' => ' mò fěn shī zhī', + '提高认识' => ' tí gāo rèn shi', + '燕市悲歌' => ' yān shì bēi gē', + '燕雀处屋' => ' yàn què chǔ wū', + '没世不忘' => ' mò shì bù wàng', + '道德认识' => ' dào dé rèn shi', + '言气卑弱' => ' yan qi bei ruo', + '佛头加秽' => ' fó tóu jiā huì', + '摸门不着' => ' mō mén bù zháo', + '脉脉含情' => ' mò mò hán qíng', + '风禾尽起' => ' fēng hé jìn qǐ', + '岩居穴处' => ' yán jū xué chǔ', + '燕金募秀' => ' yān jīn mù xiù', + '燕驾越毂' => ' yān jià yuè gū', + '风号雨泣' => ' fēng háo yǔ qì', + '燕歌赵舞' => ' yān gē zhào wǔ', + '眼饧耳热' => ' yǎn xíng ěr rè', + '特殊要求' => ' tè shū yāo qiú', + '天覆地载' => ' tiān fù dì zǎi', + '匪伊朝夕' => ' fěi yí zhāo xī', + '分液漏斗' => ' fēn yè lòu dǒu', + '炫玉贾石' => ' xuàn yù gǔ shí', + '分毫不差' => ' fēn háo bù chā', + '玄酒瓠脯' => ' xuán jiǔ hù fǔ', + '恬不为意' => ' tián bù wéi yì', + '摸头不着' => ' mō tóu bù zháo', + '奋发有为' => ' fèn fā yǒu wéi', + '虚应故事' => ' xū yìng gù shì', + '莫为已甚' => ' mò wéi yǐ shèn', + '佛心蛇口' => ' fó xīn shé kǒu', + '飞檐走脊' => ' fēi yán zǒu jǐ', + '莫知所为' => ' mò zhī suǒ wéi', + '重足而立' => ' chóng zú ér lì', + '墨汁未干' => ' mò zhī wèi gān', + '重眼皮儿' => ' chóng yǎn pí r', + '退避三舍' => ' tuì bì sān shè', + '弹丸黑子' => ' dàn wán hēi zǐ', + '奉公不阿' => ' fèng gōng bù ē', + '落后分子' => ' luò hòu fēn zǐ', + '攒三集五' => ' cuán sān jí wǔ', + '拔本塞源' => ' bá běn sè yuán', + '为鬼为蜮' => ' wéi guǐ wéi yù', + '头半天儿' => ' tóu bàn tiān r', + '为裘为箕' => ' wéi qiú wéi jī', + '唯邻是卜' => ' wéi lín shì bǔ', + '托物寓兴' => ' tuō wù yù xīng', + '为虺弗摧' => ' wéi huǐ fú cuī', + '高高手儿' => ' gāo gāo shǒu r', + '高能粒子' => ' gāo néng lì zǐ', + '瓦查尿溺' => ' wǎ chá niào nì', + '外合里应' => ' wài hé lǐ yìng', + '鞠躬尽力' => ' jū gōng jìn lì', + '帏薄不修' => ' wéi báo bù xiū', + '预备知识' => ' yù bèi zhī shi', + '为期不远' => ' wéi qī bù yuǎn', + '关门弟子' => ' guān mén dì zǐ', + '调剂余缺' => ' tiáo jì yú quē', + '钻木取火' => ' zuàn mù qǔ huǒ', + '犯而不校' => ' fàn ér bù jiào', + '脑脊髓炎' => ' nǎo jǐ suǐ yán', + '高丽棒子' => ' gāo lí bàng zi', + '非都会郡' => ' fēi dū huì jùn', + '叨陪末座' => ' tāo péi mò zuò', + '鼎折餗覆' => ' dǐng shé sù fù', + '万夫不当' => ' wàn fū bù dāng', + '颠仆流离' => ' diān pú liú lí', + '七行俱下' => ' qī háng jù xià', + '奇技淫巧' => ' qí jì yín qiǎo', + '饿莩载道' => ' è piǎo zài dào', + '饿莩遍野' => ' è piǎo biàn yě', + '讫情尽意' => ' qì qíng jìn yì', + '起偃为竖' => ' qǐ yǎn wéi shù', + '铺胸纳地' => ' pū xiōng nà dì', + '非异人任' => ' fēi yì rén rèn', + '电子杂志' => ' diàn zǐ zá zhì', + '鼎折覆餗' => ' dǐng shé fù sù', + '涤故更新' => ' dí gù gēng xīn', + '为蛇画足' => ' wéi shé huà zú', + '味如嚼蜡' => ' wèi rú jiáo là', + '铺锦列绣' => ' pū jǐn liè xiù', + '蒲柳之质' => ' pú liǔ zhī zhì', + '惟日为岁' => ' wéi rì wéi suì', + '挠直为曲' => ' náo zhí wéi qū', + '八难三灾' => ' bā nàn sān zāi', + '万夫莫当' => ' wàn fū mò dāng', + '白发丹心' => ' bái fà dān xīn', + '千载一日' => ' qiān zǎi yī rì', + '南贩北贾' => ' nán fàn běi gǔ', + '反应速度' => ' fǎn yìng sù dù', + '百辞莫辩' => ' bǎi cí mò biàn', + '银行业务' => ' yín háng yè wù', + '铺天盖地' => ' pū tiān gài dì', + '道尽途殚' => ' dào jìn tú dān', + '铺盖卷儿' => ' pū gài juǎn ér', + '蹈厉之志' => ' dǎo lì zhī zhì', + '暮雨朝云' => ' mù yǔ zhāo yún', + '兴家立业' => ' xīng jiā lì yè', + '行号卧泣' => ' xíng háo wò qì', + '反劳为逸' => ' fǎn láo wéi yì', + '铁板茄子' => ' tiě bǎn qié zi', + '铁杆粉丝' => ' tiě gǎn fěn sī', + '番窠倒臼' => ' fān kē dǎo jiù', + '难割难舍' => ' nán gē nán shè', + '都头异姓' => ' dū tóu yì xìng', + '拿糖作醋' => ' ná táng zuò cù', + '犯罪分子' => ' fàn zuì fēn zǐ', + '逆臣贼子' => ' nì chén zéi zǐ', + '儿女亲家' => ' ér nǚ qìng jiā', + '孝子慈孙' => ' xiào zǐ cí sūn', + '小眼薄皮' => ' xiǎo yǎn bó pí', + '难进易退' => ' nan jin yi tui', + '前徒倒戈' => ' qián tú dǎo gē', + '八斗之才' => ' bā dǒu zhī cái', + '小姑独处' => ' xiǎo gū dú chǔ', + '法定假日' => ' fǎ dìng jià rì', + '二竖为虐' => ' èr shù wéi nüè', + '调三斡四' => ' tiáo sān wò sì', + '白发偕老' => ' bái fà xié lǎo', + '逆子贼臣' => ' nì zǐ zéi chén', + '孽子孤臣' => ' niè zǐ gū chén', + '繁花似锦' => ' fán huā sì jǐn', + '千载奇遇' => ' qiān zǎi qí yù', + '暮虢朝虞' => ' mù guó zhāo yú', + '千载一遇' => ' qiān zǎi yī yù', + '沐猴衣冠' => ' mù hóu yī guàn', + '白发朱颜' => ' bái fà zhū yán', + '气吞牛斗' => ' qì tūn niú dǒu', + '欺行霸市' => ' qī háng bà shì', + '视为知己' => ' shì wéi zhī jǐ', + '东猎西渔' => ' dōng liè xī yú', + '等离子态' => ' děng lí zǐ tài', + '电子学系' => ' diàn zǐ xué xì', + '丽池卡登' => ' lí chí kǎ dēng', + '咸津津儿' => ' xián jīn jīn r', + '屠门大嚼' => ' tú mén dà jiáo', + '头会箕赋' => ' tóu kuài jī fù', + '撇呆打堕' => ' piě dāi dǎ duò', + '鸠摩罗什' => ' jīu mó luó shí', + '电子货币' => ' diàn zǐ huò bì', + '文子文孙' => ' wén zǐ wén sūn', + '电子游戏' => ' diàn zǐ yóu xì', + '平铺直叙' => ' píng pū zhí xù', + '东扶西倒' => ' dōng fú xī dǎo', + '吞言咽理' => ' tūn yán yān lǐ', + '破觚为圜' => ' pò gū wéi yuán', + '破坏荷载' => ' pò huài hè zài', + '破家为国' => ' pò jiā wéi guó', + '品竹弹丝' => ' pǐn zhú tán sī', + '麦盖提县' => ' mài gě tí xiàn', + '前仆后踣' => ' qián pú hòu bó', + '难以应付' => ' nán yǐ yìng fù', + '以微知著' => ' yǐ wēi zhī zhù', + '攒三聚五' => ' cuán sān jù wǔ', + '以言为讳' => ' yǐ yán wéi huì', + '贪财好色' => ' tān cái hào sè', + '里应外合' => ' lǐ yìng wài hé', + '西塞山区' => ' xī sài shān qū', + '一无长物' => ' yī wú cháng wù', + '西子捧心' => ' xī zǐ pěng xīn', + '履薄临深' => ' lǚ bó lín shēn', + '变贪厉薄' => ' biǎn tān lì bó', + '片词只句' => ' piàn cí zhī jù', + '以退为进' => ' yǐ tuì wéi jìn', + '琴瑟调和' => ' qín sè tiáo hé', + '伺瑕导蠙' => ' sì xiá dǎo pín', + '倒持干戈' => ' dǎo chí gān gē', + '倒持戈矛' => ' dǎo chí gē máo', + '倒置干戈' => ' dào zhì gān gē', + '斗南一人' => ' dǒu nán yī rén', + '涡轮增压' => ' wō lún zēng yà', + '破矩为圆' => ' pò jǔ wéi yuán', + '杂和面儿' => ' zá huo miàn er', + '风靡一时' => ' fēng mǐ yī shí', + '递兴递废' => ' dì xīng dì fèi', + '默诵佛号' => ' mò sòng fó hào', + '被山带河' => ' pī shān dài hé', + '屋舍俨然' => ' wū shè yǎn rán', + '破崖绝角' => ' pò yá jué jiǎo', + '霍林郭勒' => ' huò lín guō lè', + '惊叹不已' => ' jīng tàn bù yǐ', + '电子信息' => ' diàn zǐ xìn xī', + '七颠八倒' => ' qī diān bā dǎo', + '莞尔而笑' => ' wǎn ěr ér xiào', + '只鸡斗酒' => ' zhī jī dǒu jiǔ', + '莞尔一笑' => ' wǎn ěr yī xiào', + '颠覆分子' => ' diān fù fèn zǐ', + '忘恩失义' => ' wàng ēn shī yì', + '王八羔子' => ' wáng bā gāo zǐ', + '栖风宿雨' => ' qī fēng xiǔ yǔ', + '起根由头' => ' qǐ gēn yóu tou', + '七病八倒' => ' qī bìng bā dǎo', + '风雨欲来' => ' fēng yǔ yù lái', + '五尺童子' => ' wǔ chǐ tóng zǐ', + '隐身草儿' => ' yǐn shēn cǎo r', + '阿拉善盟' => ' a lā shàn méng', + '批吭捣虚' => ' pī háng dǎo xū', + '阿松森岛' => ' a sōng sēn dǎo', + '阿森松岛' => ' a sēn sōng dǎo', + '读书得间' => ' dú shū dé jiàn', + '无能为役' => ' wú néng wéi yì', + '阿空加瓜' => ' a kōng jiā guā', + '笃实好学' => ' dǔ shí hào xué', + '杜门自守' => ' dù mén zì shòu', + '杜门面壁' => ' dù mén mian bì', + '被褐怀珠' => ' pī hè huái zhū', + '无颠无倒' => ' wú diān wú dǎo', + '批砉导窾' => ' pī huā dǎo táo', + '头出头没' => ' tóu chū tóu mò', + '否极阳回' => ' pǐ jí yáng huí', + '五方杂处' => ' wǔ fāng zá chǔ', + '童颜鹤发' => ' tóng yán hè fà', + '斗挹箕扬' => ' dǒu yì jī yáng', + '五斗折腰' => ' wǔ dǒu zhé yāo', + '无动为大' => ' wú dòng wéi dà', + '窥间伺隙' => ' kuī jiàn sì xì', + '朝不谋夕' => ' zhāo bù móu xī', + '敷衍搪塞' => ' fū yǎn táng sè', + '枫桥夜泊' => ' fēng qiáo yè bó', + '孟什维克' => ' mèng shí wéi kè', + '啦啦队长' => ' lā lā duì zhǎng', + '名我固当' => ' ming wo gu dang', + '佛头着粪' => ' fó tóu zhuó fèn', + '摸不着边' => ' mō bù zhuó biān', + '奉为楷模' => ' fèng wéi kǎi mó', + '蜂扇蚁聚' => ' fēng shān yǐ jù', + '风流才子' => ' fēng liú cái zǐ', + '分风劈流' => ' fēn fēng pǐ liú', + '鸣锣喝道' => ' míng luó hè dào', + '丰富知识' => ' fēng fù zhī shi', + '蜂攒蚁聚' => ' fēng cuán yǐ jù', + '蜂攒蚁集' => ' fēng cuán yǐ jí', + '凤雏麟子' => ' fèng chú lín zǐ', + '风驰草靡' => ' fēng chí cǎo mǐ', + '没世难忘' => ' mò shì nán wàng', + '唾面自干' => ' tuò miàn zì gān', + '膏唇岐舌' => ' gào chún qí shé', + '工作态度' => ' gōng zuò tài du', + '分外妖娆' => ' fèn wài yāo ráo', + '狗续侯冠' => ' gǒu xù hòu guàn', + '功薄蝉翼' => ' gōng bó chán yì', + '尖酸刻薄' => ' jiān suān kè bó', + '顾曲周郎' => ' gù qǔ zhōu láng', + '鼓角齐鸣' => ' gǔ jiǎo qí míng', + '故技重演' => ' gù jì chóng yǎn', + '古调单弹' => ' gǔ diào dān tán', + '狗追耗子' => ' gǒu zhuī hào zǐ', + '钩章棘句' => ' gōu zhāng jí jù', + '功夫片儿' => ' gōng fū piān er', + '公共服务' => ' gōng gòng fú wù', + '小里小气' => ' xiǎo li xiǎo qì', + '工作任务' => ' gōng zuò rèn wu', + '公正不阿' => ' gōng zhèng bù ē', + '工业会计' => ' gōng yè kuài jì', + '供认不讳' => ' gòng rèn bù huì', + '哗啦一声' => ' huā lā yī shēng', + '乱作胡为' => ' luàn zuò hú wéi', + '落草为寇' => ' luò cǎo wéi kòu', + '帝王切开' => ' dì wáng qiē kāi', + '缝缝补补' => ' féng féng bǔ bǔ', + '埋三怨四' => ' mán sān yuàn sì', + '以疏间亲' => ' yǐ shū jiàn qīn', + '详尽无遗' => ' xiáng jìn wú yí', + '词项逻辑' => ' cí xiàng luó ji', + '一日之长' => ' yī rì zhī cháng', + '任人唯亲' => ' rèn rén wéi qīn', + '一时千载' => ' yī shí qiān zǎi', + '设身处地' => ' shè shēn chǔ dì', + '仰屋著书' => ' yǎng wū zhù shū', + '以眼还眼' => ' yǐ yǎn huán yǎn', + '以人为镜' => ' yǐ rén wéi jìng', + '扯纤拉烟' => ' chě qiàn lā yān', + '有失厚道' => ' yǒu shī hòu dao', + '有求必应' => ' yǒu qiú bì yìng', + '觅食行为' => ' mì shí xíng wéi', + '一网尽扫' => ' yī wǎng jìn sǎo', + '有缘无分' => ' yǒu yuán wú fèn', + '令人发指' => ' lìng rén fà zhǐ', + '要买人心' => ' yāo mǎi rén xīn', + '有头有尾' => ' yǒu tóu yǒu wěi', + '诛尽杀绝' => ' zhū jìn shā jué', + '黄发儿齿' => ' huáng fà ér chǐ', + '蒙蒙细雨' => ' mēng mēng xì yǔ', + '干将莫邪' => ' gān jiàng mò yé', + '福为祸先' => ' fú wéi huò xiān', + '靡靡之声' => ' mǐ mǐ zhī shēng', + '敢作敢为' => ' gǎn zuò gǎn wéi', + '敢为敢做' => ' gǎn wéi gǎn zuò', + '棉花套子' => ' mián huā tào zǐ', + '干卿底事' => ' gān qīng dǐ shì', + '干名采誉' => ' gān míng cǎi yù', + '父债子还' => ' fù zhài zǐ huán', + '面谩腹诽' => ' miàn màn fù fěi', + '称家有无' => ' chèn jiā yǒu wú', + '甘分随时' => ' gān fèn suí shí', + '左邻右舍' => ' zuǒ lín yòu shè', + '甘处下流' => ' gān chǔ xià liú', + '喇叭水仙' => ' lā bā shuǐ xiān', + '勉为其难' => ' miǎn wéi qí nán', + '善自为谋' => ' shàn zì wéi móu', + '邈处欿视' => ' miǎo chǔ jī shì', + '干戈扰攘' => ' gān gē rǎo rǎng', + '貌似强大' => ' mào sì qiáng dà', + '膏粱子弟' => ' gāo liáng zǐ dì', + '一言中的' => ' yī yán zhōng dì', + '平均误差' => ' píng jūn wù chā', + '难以为情' => ' nán yǐ wéi qíng', + '内省不疚' => ' nèi xǐng bù jiù', + '难更仆数' => ' nán gēng pú shǔ', + '分兵把口' => ' fèn bīng bǎ kǒu', + '咬文嚼字' => ' yǎo wén jiáo zì', + '培训要求' => ' péi xùn yāo qiú', + '东邻西舍' => ' dōng lín xī shè', + '品竹调丝' => ' pǐn zhú tiáo sī', + '弹痕累累' => ' dàn hén léi léi', + '难乎为情' => ' nán hū wéi qíng', + '凭虚公子' => ' píng xū gōng zǐ', + '弹道导弹' => ' dàn dào dǎo dàn', + '刁钻刻薄' => ' diāo zuān kè bó', + '铺眉蒙眼' => ' pū méi méng yǎn', + '雕蚶镂蛤' => ' diāo hān lòu gé', + '东飘西泊' => ' dōng piāo xī bó', + '仆仆风尘' => ' pú pú fēng chén', + '暴腮龙门' => ' pù sāi lóng mén', + '几可乱真' => ' jī kě luàn zhēn', + '反间之计' => ' fǎn jiàn zhī jì', + '七返还丹' => ' qī fǎn huán dān', + '发引千钧' => ' fà yǐn qiān jūn', + '攀花折柳' => ' pān huā shé liǔ', + '攀藤揽葛' => ' pān téng lǎn gě', + '庞眉白发' => ' páng méi bái fà', + '引以为荣' => ' yǐn yǐ wéi róng', + '引吭高歌' => ' yǐn háng gāo gē', + '度德量力' => ' duó dé liàng lì', + '庞眉皓发' => ' páng méi hào fà', + '泥名失实' => ' nì míng shī shí', + '费尽心血' => ' fèi jìn xīn xuè', + '反目为仇' => ' fǎn mù wéi chóu', + '飞将数奇' => ' fēi jiàng shù jī', + '非分之财' => ' fēi fèn zhī cái', + '方正不阿' => ' fāng zhèng bù ē', + '暮暮朝朝' => ' mù mù zhāo zhāo', + '方差分析' => ' fāng chā fēn xī', + '反应迅速' => ' fǎn yìng xùn sù', + '不遗寸长' => ' bù yí cùn cháng', + '南郭处士' => ' nán guō chǔ shì', + '电子音乐' => ' diàn zǐ yīn yuè', + '电子设备' => ' diàn zǐ shè bèi', + '锚固长度' => ' máo gù cháng dù', + '谩辞哗说' => ' màn cí huá shuō', + '斗榫合缝' => ' dǒu sǔn hé fèng', + '古为今用' => ' gǔ wéi jīn yòng', + '带薪休假' => ' dài xīn xiū jià', + '分内之事' => ' fèn nèi zhī shì', + '洛阳才子' => ' luò yáng cái zǐ', + '马咽车阗' => ' mǎ yān chē tián', + '买卖合同' => ' mǎi mài hé tong', + '麦丘之祝' => ' mài qiū zhī zhù', + '更仆难尽' => ' gèng pú nán jìn', + '东歪西倒' => ' dōng wāi xī dǎo', + '嗜血杆菌' => ' shì xuè gǎn jūn', + '更难仆数' => ' gēng nán pú shù', + '满堂喝彩' => ' mǎn táng hè cǎi', + '各自为政' => ' gè zì wéi zhèng', + '尨眉皓发' => ' mánɡ méi hào fà', + '各取所长' => ' gè qǔ suǒ cháng', + '毛发之功' => ' máo fà zhī gōng', + '告朔饩羊' => ' gù shuò xì yáng', + '漂泊无定' => ' piāo bó wú dìng', + '东西易面' => ' dōng xi yì miàn', + '电子词典' => ' diàn zǐ cí diǎn', + '睹物兴情' => ' dǔ wù xīng qíng', + '君子之交' => ' jūn zǐ zhī jiāo', + '铺眉苫眼' => ' pū méi shàn yǎn', + '断子绝孙' => ' duàn zǐ jué sūn', + '冬裘夏葛' => ' dōng qiú xià gě', + '披发缨冠' => ' pī fā yīng guàn', + '和睦相处' => ' hé mù xiāng chǔ', + '独有千秋' => ' dú yòu qiān qiū', + '弱不禁风' => ' ruò bù jīn fēng', + '读书种子' => ' dú shū zhǒng zǐ', + '东踅西倒' => ' dōng xué xī dǎo', + '和尚打伞' => ' hé shang dǎ sǎn', + '否往泰来' => ' pǐ wǎng tài lái', + '漂母进饭' => ' piāo mǔ jìn fàn', + '强词夺理' => ' qiǎng cí duó lǐ', + '否终复泰' => ' pǐ zhōng fù tài', + '否终则泰' => ' pǐ zhōng zé tài', + '斗酒学士' => ' dǒu jiǔ xué shì', + '骈拇枝指' => ' pián mǔ zhī zhǐ', + '意兴索然' => ' yì xīng suǒ rán', + '以水济水' => ' yǐ shuǐ jǐ shuǐ', + '度长絜大' => ' dù cháng xié dà', + '钻故纸堆' => ' zuàn gù zhǐ duī', + '间不容发' => ' jiān bù róng fà', + '开发银行' => ' kāi fā yín háng', + '开卷有益' => ' kāi juàn yǒu yì', + '闭卷考试' => ' bì juàn kǎo shì', + '无为自成' => ' wú wéi zì chéng', + '整齐划一' => ' zhěng qí huá yī', + '这还了得' => ' zhè hái liǎo dé', + '心驰魏阙' => ' xīn chí wèi què', + '晏子春秋' => ' yàn zǐ chūn qīu', + '无为而成' => ' wú wéi ér chéng', + '寻行数墨' => ' xún háng shǔ mò', + '压倒元白' => ' yā dǎo yuán bái', + '压卷之作' => ' yā juàn zhī zuò', + '燕岱之石' => ' yān dài zhī shí', + '言颠语倒' => ' yán diān yǔ dǎo', + '偃革为轩' => ' yǎn gé wéi xuān', + '酸不溜秋' => ' suān bu līu qīu', + '眼观六路' => ' yǎn guān liù lù', + '间接宾语' => ' jiàn jiē bīn yǔ', + '敷衍了事' => ' fū yǎn liǎo shì', + '都会传奇' => ' dū huì chuán qí', + '随声附和' => ' suí shēng fù hè', + '长安大学' => ' cháng ān dà xué', + '倒果为因' => ' dào guǒ wéi yīn', + '校园歌曲' => ' xiào yuán gē qǔ', + '斜纹软呢' => ' xié wén ruǎn ní', + '胁肩累足' => ' xié jiān lěi zú', + '数珠念佛' => ' shǔ zhū niàn fó', + '闭上嘴巴' => ' bì shang zuǐ bā', + '离子交换' => ' lí zǐ jiāo huàn', + '恶恶从短' => ' wù wù cóng duǎn', + '无声无臭' => ' wú shēng wú xiù', + '无缝天衣' => ' wú féng tiān yī', + '无间是非' => ' wú jiàn shì fēi', + '无尽无穷' => ' wú jìn wú qióng', + '乌面鹄形' => ' wū miàn hú xíng', + '阿里山乡' => ' a lǐ shān xiāng', + '阿谀奉承' => ' ē yú fèng chéng', + '长途跋涉' => ' cháng tú bá shè', + '舞衫歌扇' => ' wǔ shān gē shàn', + '炫石为玉' => ' xuàn shí wéi yù', + '逻辑演算' => ' luó ji yǎn suàn', + '倒买倒卖' => ' dǎo mǎi dǎo mài', + '雪山太子' => ' xuě shān tài zǐ', + '信以为真' => ' xìn yǐ wéi zhēn', + '兴微继绝' => ' xīng wēi jì jué', + '量子力学' => ' liàng zǐ lì xué', + '量力而为' => ' liàng lì ér wéi', + '行侠好义' => ' xíng xiá hào yì', + '兴云致雨' => ' xīng yún zhì yǔ', + '袖里乾坤' => ' xiù lǐ qián kūn', + '明智之举' => ' míng zhì zhī jǔ', + '花花公子' => ' huā huā gōng zǐ', + '形式逻辑' => ' xíng shì luó ji', + '花旗银行' => ' huā qí yín háng', + '褚小杯大' => ' zhǔ xiǎo bēi dà', + '犹未为晚' => ' yóu wèi wéi wǎn', + '狼号鬼哭' => ' láng háo guǐ kū', + '三更半夜' => ' sān gēng bàn yè', + '属垣有耳' => ' zhǔ yuán yǒu ěr', + '物资供应' => ' wù zī gōng yìng', + '爵士音乐' => ' jué shì yīn yuè', + '针线活儿' => ' zhēn xiàn huó r', + '宿雨餐风' => ' xiǔ yǔ cān fēng', + '适度微调' => ' shì dù wēi tiáo', + '心在魏阙' => ' xīn zài wèi què', + '佛蒙特州' => ' fó méng tè zhōu', + '偃武兴文' => ' yǎn wǔ xīng wén', + '逼人太甚' => ' bī rén tài shèn', + '进退为难' => ' jìn tuì wéi nán', + '曲终奏雅' => ' qǔ zhōng zòu yǎ', + '曲肱而枕' => ' qǔ gōng ér zhěn', + '燕跃鹄踊' => ' yàn yuè hú yǒng', + '都市传奇' => ' dū shì chuán qí', + '重码词频' => ' chóng mǎ cí pín', + '省身克己' => ' xǐng shēn kè jǐ', + '性别差异' => ' xìng bié chā yì', + '日本银行' => ' rì běn yín háng', + '兴讹造讪' => ' xīng é zào shàn', + '日电电子' => ' rì diàn diàn zǐ', + '兴废继绝' => ' xīng fèi jì jué', + '兴利除害' => ' xīng lì chú hài', + '键盘乐器' => ' jiàn pán yuè qì', + '兴如嚼蜡' => ' xìng rú jiáo là', + '相与为一' => ' xiāng yǔ wéi yī', + '长期以来' => ' cháng qī yǐ lái', + '一叶扁舟' => ' yī yè piān zhōu', + '引以为鉴' => ' yǐn yǐ wéi jiàn', + '衣锦荣归' => ' yì jǐn róng guī', + '处世之道' => ' chǔ shì zhī dào', + '人模狗样' => ' rén mú gǒu yàng', + '人情味儿' => ' rén qíng wèi ér', + '未卜先知' => ' wèi bǔ xiān zhī', + '蔚为大观' => ' wèi wéi dà guān', + '亹亹不倦' => ' tān wěi bù juàn', + '蓬头散发' => ' péng tóu sàn fà', + '著书立说' => ' zhù shū lì shuō', + '一重一掩' => ' yī chóng yī yǎn', + '应付票据' => ' yìng fù piào jù', + '处世原则' => ' chǔ shì yuán zé', + '交通阻塞' => ' jiāo tōng zǔ sè', + '鹰觑鹘望' => ' yīng qù hú wàng', + '亚曼牙乡' => ' yà màn yá xiāng', + '华纳兄弟' => ' huà nà xīong dì', + '用词不当' => ' yòng cí bù dàng', + '二重母音' => ' èr chóng mǔ yīn', + '洋里洋气' => ' yáng li yáng qì', + '一曲阳关' => ' yī qǔ yáng guān', + '有的放矢' => ' yǒu dì fàng shǐ', + '轻嘴薄舌' => ' qīng zuǐ bó shé', + '认识不能' => ' rèn shi bù néng', + '以人为鉴' => ' yǐ rén wéi jiàn', + '更仆难数' => ' gēng pú nán shǔ', + '摇滚音乐' => ' yáo gǔn yīn yuè', + '瑶台银阙' => ' yáo tái yín què', + '鹞子翻身' => ' yào zǐ fān shēn', + '辐射对称' => ' fú shè duì chèn', + '轻浪浮薄' => ' qīng làng fú bó', + '异常处理' => ' yì cháng chǔ lǐ', + '以毛相马' => ' yǐ máo xiàng mǎ', + '誓死不降' => ' shì sǐ bù xiáng', + '占为己有' => ' zhàn wéi jǐ yǒu', + '遗臭万载' => ' yí chòu wàn zǎi', + '赶尽杀绝' => ' gǎn jìn shā jué', + '走漏消息' => ' zǒu lòu xiāo xi', + '贸易顺差' => ' mào yì shùn chā', + '买椟还珠' => ' mǎi dú huán zhū', + '以规为瑱' => ' yǐ guī wéi tiàn', + '庄稼户儿' => ' zhuāng jia hù r', + '杏仁豆腐' => ' xìng rén dòu fu', + '相女配夫' => ' xiàng nǚ pèi fū', + '长足进步' => ' cháng zú jìn bù', + '朝鲜字母' => ' cháo xiǎn zì mǔ', + '人谁无过' => ' rén shéi wú guò', + '朝闻夕改' => ' zhāo wén xī gǎi', + '褎然举首' => ' yòu rán jǔ shǒu', + '燕子衔食' => ' yàn zǐ xián shí', + '燕昭好马' => ' yān zhāo hǎo mǎ', + '数码相机' => ' shù mǎ xiàng jī', + '长途汽车' => ' cháng tú qì chē', + '假假若是' => ' jiǎ jià ruò shì', + '一坐尽倾' => ' yī zuò jìn qīng', + '值得敬佩' => ' zhí de jìng pèi', + '夕惕朝乾' => ' xī tì zhāo qián', + '蹊田夺牛' => ' xī tián duó niú', + '斗筲之器' => ' dǒu shāo zhī qì', + '借花献佛' => ' jiè huā xiàn fó', + '借腹生子' => ' jiè fù shēng zǐ', + '先自隗始' => ' xiān zì wěi shǐ', + '相差无几' => ' xiāng chā wú jǐ', + '人鱼小姐' => ' rén yú xiǎo jie', + '一坐尽惊' => ' yī zuò jìn jīng', + '有借无还' => ' yǒu jiè wú huán', + '什么时候' => ' shén me shí hòu', + '交通堵塞' => ' jiāo tōng dǔ sè', + '一朝千里' => ' yī zhāo qiān lǐ', + '处之泰然' => ' chǔ zhī tài rán', + '饮马投钱' => ' yìn mǎ tóu qián', + '朝乾夕惕' => ' zhāo qián xī tì', + '一朝之忿' => ' yī zhāo zhī fèn', + '朝日新闻' => ' zhāo rì xīn wén', + '朝朝暮暮' => ' zhāo zhāo mù mù', + '行为主义' => ' xíng wéi zhǔ yì', + '朝阳地区' => ' zhāo yáng dì qū', + '什么地方' => ' shén me dì fang', + '朝气蓬勃' => ' zhāo qì péng bó', + '什锦果盘' => ' shí jǐn guǒ pán', + '处事原则' => ' chǔ shì yuán zé', + '朝秦暮楚' => ' zhāo qín mù chǔ', + '朝花夕拾' => ' zhāo huā xī shī', + '朝过夕改' => ' zhāo guò xī gǎi', + '一掷乾坤' => ' yī zhì qián kūn', + '多财善贾' => ' duō cái shàn gǔ', + '度己以绳' => ' duó jǐ yǐ shéng', + '精疲力尽' => ' jīng pí lì jìn', + '措置得当' => ' cuò zhì dé dàng', + '日省月试' => ' rì xǐng yuè shì', + '除害兴利' => ' chú hài xīng lì', + '重熙累绩' => ' chóng xī lěi jì', + '心神不属' => ' xīn shén bù zhǔ', + '应急措施' => ' yìng jí cuò shī', + '日朘月削' => ' rì juān yuè xuē', + '心急火燎' => ' xīn jí huǒ liǎo', + '且住为佳' => ' qiě zhù wéi jiā', + '日长似岁' => ' rì cháng sì suì', + '琴瑟失调' => ' qín sè shī tiáo', + '心宽体胖' => ' xīn kuān tǐ pán', + '只说不做' => ' zhī shuō bù zuò', + '存亡未卜' => ' cún wáng wèi bǔ', + '只言片语' => ' zhī yán piàn yǔ', + '乾乾翼翼' => ' qián qián yì yì', + '切树倒根' => ' qiē shù dǎo gēn', + '各尽所能' => ' gè jìn suǒ néng', + '合同各方' => ' hé tong gè fāng', + '倒三颠四' => ' dǎo sān diān sì', + '日省月修' => ' rì xǐng yuè xiū', + '日削月朘' => ' rì xuē yuè juān', + '合众为一' => ' hé zhòng wéi yī', + '敲骨剥髓' => ' qiāo gǔ bāo suǐ', + '成佛作祖' => ' chéng fó zuò zǔ', + '殚诚毕虑' => ' dān chéng bì lǜ', + '三尺童子' => ' sān chǐ tóng zǐ', + '青山一发' => ' qīng shān yī fà', + '千载一弹' => ' qiān zǎi yī dàn', + '另辟蹊径' => ' lìng pì xī jìng', + '应急出口' => ' yìng jí chū kǒu', + '重起炉灶' => ' chóng qǐ lú zào', + '寝丘之志' => ' qǐn qiū zhī zhì', + '成吉思汗' => ' chéng jí sī hán', + '德薄能鲜' => ' dé bó néng xiǎn', + '重熙累叶' => ' chóng xī lěi yè', + '箪食壶浆' => ' dān sì hú jiāng', + '恶名昭著' => ' è míng zhāo zhù', + '重纰貤缪' => ' chóng pī yí miù', + '危若朝露' => ' wēi ruò zhāo lù', + '危如累卵' => ' wēi rú lěi luǎn', + '千磨百折' => ' qiān mó bǎi shé', + '重理旧业' => ' chóng lǐ jiù yè', + '情至意尽' => ' qíng zhì yì jìn', + '青灯古佛' => ' qīng dēng gǔ fó', + '千载一会' => ' qiān zǎi yī huì', + '重规袭矩' => ' chóng guī xí jǔ', + '肉山脯林' => ' ròu shān fǔ lín', + '轻财好义' => ' qīng cái hào yì', + '人自为战' => ' rén zì wéi zhàn', + '应对如流' => ' yìng duì rú liú', + '古典音乐' => ' gǔ diǎn yīn yuè', + '微电子学' => ' wēi diàn zǐ xué', + '口干舌燥' => ' kǒu gān shé zào', + '日以为常' => ' rì yǐ wéi cháng', + '啛啛喳喳' => ' cuì cuì chā chā', + '重规沓矩' => ' chóng guī tà jǔ', + '千载一时' => ' qiān zǎi yī shí', + '处理程序' => ' chǔ lǐ chéng xù', + '汝成人耶' => ' nu cheng ren ye', + '淡然置之' => ' dàn rán zhì zhī', + '黑更半夜' => ' hēi gēng bàn yè', + '后翻筋斗' => ' hòu fān jīn dǒu', + '大局为重' => ' dà jú wéi zhòng', + '洒扫应对' => ' sǎ sào yìng duì', + '所作所为' => ' suǒ zuò suǒ wéi', + '攒锋聚镝' => ' cuán fēng jù dí', + '重新组合' => ' chóng xīn zǔ hé', + '所向披靡' => ' suǒ xiàng pī mǐ', + '道在人为' => ' dào zài rén wéi', + '应接不暇' => ' yìng jiē bù xiá', + '大事铺张' => ' dà shì pū zhāng', + '曲学诐行' => ' qǔ xué hǎo xíng', + '切近的当' => ' qiē jìn de dāng', + '千载难遇' => ' qiān zǎi nán yù', + '代表大会' => ' dài biǎo dà huì', + '口不应心' => ' kǒu bù yìng xīn', + '爱乐乐团' => ' ài yuè yuè tuán', + '词穷理尽' => ' cí qióng lǐ jìn', + '塞翁之马' => ' sài wēng zhī mǎ', + '出处进退' => ' chū chǔ jìn tuì', + '敲诈勒索' => ' qiāo zhà lè suǒ', + '出没不常' => ' chū mò bù cháng', + '大相国寺' => ' dà xiàng guó sì', + '倒因为果' => ' dǎo yīn wéi guǒ', + '反动分子' => ' fǎn dòng fèn zǐ', + '儋石之储' => ' dàn shí zhī chǔ', + '乐尽哀生' => ' lè jìn āi shēng', + '澄沙汰砾' => ' dèng shā tài lì', + '才轻德薄' => ' cái qīng dé bó', + '朝齑暮盐' => ' zhāo jī mù yán', + '才夸八斗' => ' cái kuā bā dǒu', + '累块积苏' => ' lěi kuài jī sū', + '老师宿儒' => ' lǎo shī xiǔ rú', + '称心如意' => ' chèn xīn rú yì', + '稻荷寿司' => ' dào hè shòu sī', + '累累如珠' => ' lěi lěi rú zhū', + '情见力屈' => ' qíng xiàn lì qū', + '累苏积块' => ' lěi sū jī kuài', + '重游故地' => ' chóng yóu gù dì', + '将伯之呼' => ' qiāng bó zhī hū', + '触目兴叹' => ' chù mù xīng tàn', + '抽筋剥皮' => ' chōu jīn bāo pí', + '千金之子' => ' qiān jīn zhī zǐ', + '复兴时代' => ' fù xīng shí dài', + '求神问卜' => ' qiú shén wèn bǔ', + '强食自爱' => ' qiǎng shí zì ài', + '横无忌惮' => ' hèng wú jì dàn', + '财竭力尽' => ' cái jié lì jìn', + '倒绷孩儿' => ' dào bēng hái ér', + '积德累功' => ' jī dé lěi gōng', + '累计折旧' => ' lěi jì zhé jiù', + '朝发夕至' => ' zhāo fā xī zhì', + '壶里乾坤' => ' hú lǐ qián kūn', + '粘皮著骨' => ' nián pí zhù gǔ', + '窝阔台汗' => ' wō kuò tái hán', + '互教互学' => ' hù jiāo hù xué', + '昭德塞违' => ' zhāo dé sè wéi', + '互为表里' => ' hù wéi biǎo lǐ', + '纷至沓来' => ' fēn zhì tà lái', + '结核杆菌' => ' jié hé gǎn jūn', + '老白干儿' => ' lǎo bái gān ér', + '出处殊途' => ' chū chǔ shū tú', + '笑口弥勒' => ' xiào kǒu mí lè', + '任人摆布' => ' rèn rén bǎi bù', + '横蛮无理' => ' hèng mán wú lǐ', + '热核反应' => ' rè hé fǎn yìng', + '触处机来' => ' chù chǔ jī lái', + '澜倒波随' => ' lán dǎo bō suí', + '瞪目哆口' => ' dèng mù chǐ kǒu', + '强得易贫' => ' qiǎng dé yì pín', + '箪食瓢饮' => ' dān sì piáo yǐn', + '同归于尽' => ' tóng guī yú jìn', + '人民公仆' => ' rén mín gōng pú', + '求过于供' => ' qiú guò yú gòng', + '陈力就列' => ' chén lì jiù liè', + '求神拜佛' => ' qiú shén bài fó', + '泣数行下' => ' qì shù háng xià', + '应用物理' => ' yìng yòng wù lǐ', + '赤绳系足' => ' chì shéng jì zú', + '苫眼铺眉' => ' shān yǎn pū méi', + '处尊居显' => ' chǔ zūn jū xiǎn', + '称心满意' => ' chèn xīn mǎn yì', + '曲眉丰颊' => ' qǔ méi fēng jiá', + '尺二冤家' => ' chǐ èr yuān jia', + '思觉失调' => ' sī jué shī tiáo', + '扎实推进' => ' zhā shi tuī jìn', + '千里之志' => ' qiān lǐ zhī zhì', + '强自取折' => ' qiáng zì qǔ shé', + '重足累息' => ' chóng zú lèi xī', + '性别角色' => ' xìng bié jué sè', + '淡然处之' => ' dàn rán chǔ zhī', + '半夜三更' => ' bàn yè sān gēng', + '情见乎辞' => ' qíng xiàn hū cí', + '倾心尽力' => ' qīng xīn jìn lì', + '厚死薄生' => ' hòu sǐ bó shēng', + '三老五更' => ' sān lǎo wǔ gēng', + '翘首企足' => ' qiáo shǒu qǐ zú', + '强而后可' => ' qiǎng ér hòu kě', + '鹑衣鹄面' => ' chún yī hú miàn', + '瞪眼咋舌' => ' dèng yǎn zé shé', + '吃着不尽' => ' chī zhuó bù jìn', + '应机立断' => ' yìng jī lì duàn', + '抵死谩生' => ' dǐ sǐ màn shēng', + '原子爆弹' => ' yuán zǐ bào dàn', + '穷不失义' => ' qióng bù shī yì', + '应县木塔' => ' yìng xiàn mù tǎ', + '反兴奋剂' => ' fǎn xīng fèn jì', + '反动势力' => ' fǎn dòng shì li', + '诞谩不经' => ' dàn màn bù jīng', + '人事不省' => ' rén shì bù xǐng', + '半身不遂' => ' bàn shēn bù suí', + '请自隗始' => ' qǐng zì wěi shǐ', + '了不可见' => ' liǎo bù kě jiàn', + '守正不阿' => ' shǒu zhèng bù ē', + '逆施倒行' => ' nì shī dǎo xíng', + '奏鸣曲式' => ' zòu míng qǔ shì', + '谨毛失貌' => ' jǐn máo shī mào', + '尽其所能' => ' jìn qí suǒ néng', + '天竺牡丹' => ' tiān zhú mǔ dan', + '进退出处' => ' jìn tuì chū chǔ', + '经纶济世' => ' jing lun ji shi', + '经史百子' => ' jīng shǐ bǎi zǐ', + '将门虎子' => ' jiàng mén hǔ zǐ', + '精义入神' => ' jing yi ru shen', + '还我河山' => ' huán wǒ hé shān', + '尽付东流' => ' jìn fù dōng liú', + '积以为常' => ' jī yǐ wéi cháng', + '举世混浊' => ' jǔ shì hún zhuó', + '娱乐场所' => ' yú lè cháng suǒ', + '火急火燎' => ' huǒ jí huǒ liǎo', + '倔头倔脑' => ' juè tou juè nǎo', + '婚生子女' => ' hūn shēng zǐ nǚ', + '多灾多难' => ' duō zāi duō nàn', + '魂飞魄丧' => ' hún fēi pò sāng', + '见义敢为' => ' jiàn yì gǎn wéi', + '浇风薄俗' => ' jiāo fēng bó sú', + '君子协定' => ' jūn zǐ xié dìng', + '戟指嚼舌' => ' jǐ zhǐ jiáo shé', + '捡洋落儿' => ' jiǎn yáng là ér', + '佳人薄命' => ' jiā rén bó mìng', + '鞠躬屏气' => ' jū gōng bǐng qì', + '好为人师' => ' hào wéi rén shī', + '好酒贪杯' => ' hào jiǔ tān bēi', + '好高骛远' => ' hào gāo wù yuǎn', + '如数家珍' => ' rú shǔ jiā zhēn', + '钜人长德' => ' jù rén cháng dé', + '举措失当' => ' jǔ cuò shī dàng', + '太阳黑子' => ' tài yáng hēi zǐ', + '基因重组' => ' jī yīn chóng zǔ', + '大处着眼' => ' dà chù zhuó yǎn', + '尖嘴薄舌' => ' jiān zuǐ bó shé', + '解除合同' => ' jiě chú hé tong', + '解衣盘磅' => ' jiě yī pán páng', + '教一识百' => ' jiāo yī shí bǎi', + '嚼铁咀金' => ' jiáo tiě jǔ jīn', + '教书育人' => ' jiāo shū yù rén', + '君子固穷' => ' jūn zǐ gù qióng', + '开户银行' => ' kāi hù yín háng', + '间不容息' => ' jiàn bù róng xī', + '鼓衰力尽' => ' gǔ shuāi lì jìn', + '积功兴业' => ' jī gōng xīng yè', + '急公好施' => ' jí gōng hào shī', + '疾风甚雨' => ' jí fēng shèn yǔ', + '抉奥阐幽' => ' jué ào chǎn yōu', + '激薄停浇' => ' jī bó tíng jiāo', + '饥寒交切' => ' jī hán jiāo qiē', + '吐蕃王朝' => ' tǔ bō wáng cháo', + '流血漂橹' => ' liú xiě piāo lǔ', + '黏黏糊糊' => ' nián nián hū hū', + '计尽力穷' => ' jì jìn lì qióng', + '年谊世好' => ' nián yì shì hào', + '康巴藏区' => ' kāng bā zàng qū', + '宁折不弯' => ' nìng zhé bù wān', + '扼腕长叹' => ' è wàn cháng tàn', + '廊坊地区' => ' láng fáng dì qū', + '峨峨汤汤' => ' é é shāng shāng', + '夺人所好' => ' duó rén suǒ hào', + '怒涛澎湃' => ' nù tāo péng pài', + '大便干燥' => ' dà biàn gān zào', + '豁然省悟' => ' huò rán xǐng wù', + '看家本事' => ' kān jiā běn shì', + '吉凶未卜' => ' jí xiōng wèi bǔ', + '回干就湿' => ' huí gān jiù shī', + '看守内阁' => ' kān shǒu nèi gé', + '子宫颈癌' => ' zǐ gōng jǐng ái', + '黄口孺子' => ' huáng kǒu rú zǐ', + '恪尽职守' => ' kè jìn zhí shǒu', + '没齿难忘' => ' mò chǐ nán wàng', + '还珠买椟' => ' huán zhū mǎi dú', + '回天倒日' => ' huí tiān dǎo rì', + '倔强倨傲' => ' jué jiàng jù ào', + '积累经验' => ' jī lěi jīng yàn', + '祸为福先' => ' huò wéi fú xiān', + '箕山之志' => ' jī shān zhī zhì', + '计穷虑尽' => ' jì qióng lǜ jìn', + '句子成分' => ' jù zi chéng fèn', + '计穷力尽' => ' jì qióng lì jìn', + '极情尽致' => ' jí qíng jìn zhì', + '积年累岁' => ' jī nián lěi suì', + '卷甲韬戈' => ' juàn jiǎ tāo gē', + '居不重茵' => ' jū bù chóng yīn', + '久要不忘' => ' jiǔ yāo bù wàng', + '累教不改' => ' lěi jiào bù gǎi', + '胡作乱为' => ' hú zuò luàn wéi', + '哗世取名' => ' huá shì qǔ míng', + '哗世动俗' => ' huá shì dòng sú', + '画蛇著足' => ' huà shé zhuó zú', + '化合反应' => ' huà hé fǎn yìng', + '红颜白发' => ' hóng yán bái fà', + '花攒锦聚' => ' huā cuán jǐn jù', + '花簇锦攒' => ' huā cù jǐn cuán', + '会计核算' => ' kuài jì hé suàn', + '塞翁失马' => ' sài wēng shī mǎ', + '怀敌附远' => ' huái dí fù yuǎn', + '快心遂意' => ' kuài xīn suí yì', + '字里行间' => ' zì lǐ háng jiān', + '揆理度情' => ' kuí lǐ duó qíng', + '揆情度理' => ' kuí qíng duó lǐ', + '花攒锦簇' => ' huā cuán jǐn cù', + '口轻舌薄' => ' kǒu qīng shé bó', + '好学不倦' => ' hào xué bù juàn', + '礼让为国' => ' lǐ ràng wéi guó', + '花朝月夜' => ' huā zhāo yuè yè', + '淮橘为枳' => ' huái jú wéi zhǐ', + '刿心鉥肾' => ' guì xīn xù shèn', + '李广不侯' => ' lǐ guǎng bù hòu', + '横行无忌' => ' héng xíng wú jì', + '横行不法' => ' héng xíng bù fǎ', + '擂天倒地' => ' léi tiān dǎo dì', + '累土至山' => ' lěi tǔ zhì shān', + '鸿鹄之志' => ' hóng hú zhī zhì', + '累足成步' => ' lěi zú chéng bù', + '客户应用' => ' kè hù yìng yòng', + '合同管理' => ' hé tong guǎn lǐ', + '历尽艰辛' => ' lì jìn jiān xīn', + '苦尽甜来' => ' kǔ jìn tián lái', + '河梁之谊' => ' hé liáng zhì yí', + '合两为一' => ' hé liǎng wéi yī', + '赫赫之光' => ' hè hè zhì guāng', + '横倒竖卧' => ' héng dǎo shù wò', + '历精为治' => ' lì jīng wéi zhì', + '洪炉燎发' => ' hóng lú liáo fà', + '腊尽春回' => ' là jìn chūn huí', + '口血未干' => ' kǒu xuè wèi gān', + '刿鉥肝肾' => ' guì xù gān shèn', + '桂折兰摧' => ' guì shé lán cuī', + '好吃懒做' => ' hào chī lǎn zuò', + '过为已甚' => ' guò wéi yǐ shèn', + '宁缺勿滥' => ' nìng quē wù làn', + '宁缺毋滥' => ' nìng quē wú làn', + '鬼蜮伎俩' => ' guǐ yù jì liǎng', + '图像处理' => ' tú xiàng chǔ lǐ', + '国定假日' => ' guó dìng jià rì', + '敛色屏气' => ' liǎn sè bǐng qì', + '含混不清' => ' hán hún bù qīng', + '量力度德' => ' liàng lì duó dé', + '行间字里' => ' háng jiān zì lǐ', + '礼为情貌' => ' lǐ wéi qíng mào', + '对称破缺' => ' duì chèn pò quē', + '空调机组' => ' kōng tiáo jī zǔ', + '还珠返璧' => ' huán zhū fǎn bì', + '女性厌恶' => ' nǔ: xìng yàn wù', + '大兴安岭' => ' dà xīng ān lǐng', + '好久不见' => ' hǎo jǐu bu jiàn', + '大腹便便' => ' dà fù pián pián', + '间接推理' => ' jiàn jiē tuī lǐ', + '宁左勿右' => ' nìng zuǒ wù yòu', + '察微知著' => ' chá wēi zhī zhù', + '专心一意' => ' zhuān xīn yī yì', + '管理会计' => ' guǎn lǐ kuài jì', + '劣迹昭着' => ' liè jì zhāo zhe', + '劣迹昭著' => ' liè jì zhāo zhù', + '裂眦嚼齿' => ' liè zì jiáo chǐ', + '临敌易将' => ' lín dí yì jiàng', + '射频干扰' => ' shè pín gān rǎo', + '临难不恐' => ' lín nàn bù kǒng', + '凌波仙子' => ' líng bō xiān zǐ', + '领导班子' => ' líng dǎo bān zǐ', + '冠绝一时' => ' guàn jué yī shí', + '里挑外撅' => ' lǐ tiǎo wài juē', + '冠屦倒施' => ' guàn jù dǎo shī', + '对牛弹琴' => ' duì niú tán qín', + '麟子凤雏' => ' lín zǐ fèng chú', + '毫无用处' => ' háo wú yòng chu', + '规旋矩折' => ' guī xuán jǔ shé', + '过而能改' => ' guò ér néng gǎi', + '立身处世' => ' lì shēn chǔ shì', + '好善乐施' => ' hào shàn lè shī', + '滔滔不尽' => ' tāo tāo bù jìn', + '敏而好学' => ' mǐn ér hào xué', + '抓破脸子' => ' zhuā pò liǎn zǐ', + '普什图语' => ' pǔ shí tú yǔ', + '的一确二' => ' dí yī què èr', + '柴立不阿' => ' chái lì bù ē', + '魂不附体' => ' hún bù fù tǐ', + '业余爱好' => ' yè yú ài hào', + '无与为比' => ' wú yǔ wéi bǐ', + '一鼻子灰' => ' yī bí zǐ huī', + '走背字儿' => ' zǒu bèi zì r', + '佛洛伊德' => ' fó luò yī dé', + '易地而处' => ' yì dì ér chǔ', + '呆似木鸡' => ' dāi sì mù jī', + '佛罗里达' => ' fó luó lǐ dá', + '偃旗仆鼓' => ' yǎn qí pú gǔ', + '悉索薄赋' => ' xī suǒ bó fù', + '太子河区' => ' tài zǐ hé qū', + '值得一提' => ' zhí de yī tí', + '贝塔粒子' => ' bèi tǎ lì zǐ', + '四季豆腐' => ' sì jì dòu fu', + '薄胎瓷器' => ' bó tāi cí qì', + '低音喇叭' => ' dī yīn lǎ ba', + '巴勒斯坦' => ' bā lè sī tǎn', + '一字不差' => ' yī zì bù chā', + '可疑分子' => ' kě yí fèn zǐ', + '豁达大度' => ' huò dá dà dù', + '如金似玉' => ' rú jīn sì yù', + '参差不一' => ' cēn cī bù yī', + '暴衣露盖' => ' pù yī lù gài', + '自给有余' => ' zì jǐ yǒu yú', + '耶鲁大学' => ' yē lǔ dà xué', + '打闷葫芦' => ' dǎ mèn hú lu', + '咸丝丝儿' => ' xián sī sī r', + '塞哥维亚' => ' sè gē wéi yà', + '仆仆亟拜' => ' pú pú jí bài', + '仆仆道途' => ' pú pú dào tú', + '赫鲁雪夫' => ' hè lǔ xuě fu', + '妇人孺子' => ' fù rén rú zǐ', + '夫子自道' => ' fū zǐ zì dào', + '萨哈罗夫' => ' sà hǎ luó fū', + '萨哈诺夫' => ' sà hǎ nuò fū', + '赛普勒斯' => ' sài pǔ lè sī', + '基本粒子' => ' jī běn lì zǐ', + '大头娃娃' => ' dà tóu wá wa', + '达到目的' => ' dá dào mù dì', + '故甚其词' => ' gù shèn qí cí', + '咖啡馆儿' => ' kā fēi guǎn r', + '如鱼似水' => ' rú yú sì shuǐ', + '指鹿为马' => ' zhǐ lù wéi mǎ', + '红得发紫' => ' hóng de fā zǐ', + '积日累久' => ' jī rì lěi jiǔ', + '卡文迪什' => ' kǎ wén dí shí', + '济济一堂' => ' jǐ jǐ yī táng', + '否极泰来' => ' pǐ jí tài lái', + '如拾地芥' => ' rú shí dì jiè', + '湮没无闻' => ' yān mò wú wén', + '不尽人意' => ' bù jìn rén yì', + '胡作胡为' => ' hú zuò hú wéi', + '济宁地区' => ' jǐ níng dì qū', + '左宜右有' => ' zuǒ yí yòu fú', + '坐地自划' => ' zuò dì zì huá', + '百思莫解' => ' bǎi sī mò jiě', + '孤行一意' => ' gū xíng yī yì', + '眦裂发指' => ' zì liè fà zhǐ', + '积时累日' => ' jī shí lěi rì', + '厚古薄今' => ' hòu gǔ bó jīn', + '累及无辜' => ' lěi jí wú gū', + '不可究诘' => ' bù kě jiū jié', + '析骸易子' => ' xī hái yì zǐ', + '呼来喝去' => ' hū lái hè qù', + '大雅君子' => ' dà yǎ jūn zǐ', + '葫芦依样' => ' hú lu yī yàng', + '模糊数学' => ' mó hu shù xué', + '击石弹丝' => ' jī shí tán sī', + '模糊不清' => ' mó hu bù qīng', + '如痴似醉' => ' rú chī sì zuì', + '一丘之貉' => ' yī qiū zhī hé', + '孤文只义' => ' gū wén zhī yì', + '半部论语' => ' bàn bù lún yǔ', + '咭咭呱呱' => ' jī jī guā guā', + '一暴十寒' => ' yī pù shí hán', + '白日衣绣' => ' bái rì yì xiù', + '百堕俱举' => ' bǎi huī jù jǔ', + '人足家给' => ' rén zú jiā jǐ', + '扳不倒儿' => ' bān bù dǎo ér', + '呼幺喝六' => ' hū yāo hè liù', + '耶利米书' => ' yē lì mǐ shū', + '严了眼儿' => ' yán le yǎn r', + '字纸篓子' => ' zì zhǐ lǒu zi', + '齐眉穗儿' => ' qí méi suì r', + '婀娜多姿' => ' ē nuó duō zī', + '不绝如发' => ' bù jué rú fà', + '包袱皮儿' => ' bāo fú pí er', + '刻鹄类鹜' => ' kè hú lèi wù', + '草字头儿' => ' cǎo zì tóu r', + '库尔勒市' => ' kù ěr lè shì', + '倒打一耙' => ' dào dǎ yī pá', + '柏蒂切利' => ' bó dì qiè lì', + '漫无目的' => ' màn wú mù dì', + '负担不起' => ' fù dān bu qǐ', + '泥而不滓' => ' niè ér bù zǐ', + '凡夫俗子' => ' fán fū sú zǐ', + '刻薄寡恩' => ' kè bó guǎ ēn', + '茄子河区' => ' qié zi hé qū', + '泥多佛大' => ' ní duō fó dà', + '哈德逊河' => ' hā dé xùn hé', + '舍利子塔' => ' shè lì zi tǎ', + '离本趣末' => ' lí běn qū mò', + '心细如发' => ' xīn xì rú fà', + '阿意取容' => ' ē yì qǔ róng', + '岁聿其莫' => ' suì yù qí mù', + '聚米为谷' => ' jù mǐ wéi gǔ', + '贝尔格勒' => ' bèi ěr gé lè', + '阿赖耶识' => ' ā lài yē shí', + '合而为一' => ' hé ér wéi yī', + '何足为奇' => ' hé zú wéi qí', + '削发披缁' => ' xuē fà pī zī', + '阿世媚俗' => ' ē shì mèi sú', + '子虚乌有' => ' zǐ xū wū yǒu', + '学习目的' => ' xué xí mù dì', + '杜塞道夫' => ' dù sè dào fū', + '德布勒森' => ' dé bù lè sēn', + '须弥芥子' => ' xū mí jiè zǐ', + '泥足巨人' => ' ní zú jù rén', + '私淑弟子' => ' sī shū dì zǐ', + '搭把手儿' => ' dā bǎ shǒu r', + '子母扣儿' => ' zǐ mǔ kòu ér', + '乌德勒支' => ' wū dé lè zhī', + '巴甫洛夫' => ' bā fǔ luò fū', + '合二为一' => ' hé èr wéi yī', + '分丝析缕' => ' fēn sī xī lǚ', + '企业倒闭' => ' qǐ yè dǎo bì', + '临崖勒马' => ' lín yá lè mǎ', + '济南地区' => ' jǐ nán dì qū', + '阿谀取容' => ' ē yú qǔ róng', + '爱素好古' => ' ài sù hào gǔ', + '八字帖儿' => ' bā zì tiě ér', + '不足为法' => ' bù zú wéi fǎ', + '不足为据' => ' bù zú wéi jù', + '过一会儿' => ' guò yī huì r', + '不以为奇' => ' bù yǐ wéi qí', + '一览无遗' => ' yī lǎi wú yí', + '一心一意' => ' yī xīn yī yì', + '不足为意' => ' bù zú wéi yì', + '布鲁塞尔' => ' bù lǔ sài ěr', + '妻梅子鹤' => ' qī mén zǐ hè', + '得马折足' => ' dé mǎ shé zú', + '几何拓扑' => ' jǐ hé tuò pū', + '打马虎眼' => ' dǎ mǎ hu yǎn', + '大吃大喝' => ' dà chī dà hè', + '跗萼载韡' => ' fū è zǎi wěi', + '历历可数' => ' lì lì kě shǔ', + '嘁哩喀喳' => ' qī lǐ kā chā', + '矮子看戏' => ' ǎi zǐ kàn xì', + '粪土不如' => ' fèn tú bù rú', + '哈佛大学' => ' hā fó dà xué', + '呢喃细语' => ' ní nán xì yǔ', + '遂迷不寤' => ' suí mí bù wù', + '不足部分' => ' bù zú bù fen', + '何乐不为' => ' hé lè bù wéi', + '不以为意' => ' bù yǐ wéi yì', + '泰极而否' => ' tài jí ér pǐ', + '葛瑞格尔' => ' gě ruì gé ěr', + '毫不客气' => ' háo bù kè qi', + '曲霉毒素' => ' qǔ méi dú sù', + '大难不死' => ' dà nàn bù sǐ', + '葡萄干儿' => ' pú tao gān r', + '聱牙诘屈' => ' áo yá jié qū', + '囤积居奇' => ' tún jī jū qí', + '帕特丽夏' => ' pà tè lí xià', + '艾哈迈德' => ' aì hǎ mài dé', + '为德不卒' => ' wéi dé bù zú', + '似有如无' => ' sì yǒu rú wú', + '伺瑕抵隙' => ' sì xiá dǐ xì', + '日薄西山' => ' rì bó xī shān', + '如花似锦' => ' rú huā sì jǐn', + '鹤子梅妻' => ' hè zǐ méi qī', + '木头人儿' => ' mù tóu rén er', + '发指眦裂' => ' fà zhǐ zì liè', + '哩哩罗罗' => ' lī lī luō luō', + '骨头节儿' => ' gú tou jié ér', + '不差毫厘' => ' bù chā háo lí', + '扒耳搔腮' => ' pá ěr sāo sāi', + '撒都该人' => ' sā dū gāi rén', + '高音喇叭' => ' gāo yīn lǎ ba', + '火妻灰子' => ' huǒ qī huī zǐ', + '狗拿耗子' => ' gǒu ná hào zǐ', + '哩哩啰啰' => ' lī li luō luō', + '吉卜赛人' => ' jí bǔ sài rén', + '独辟蹊径' => ' dú pì xī jìng', + '披头散发' => ' pī tóu sàn fà', + '独山子区' => ' dú shān zǐ qū', + '号寒啼饥' => ' háo hán tí jī', + '庆父不死' => ' qìng fǔ bù sǐ', + '不可数集' => ' bù kě shuò jí', + '独具只眼' => ' dú jù zhī yǎn', + '不以为然' => ' bù yǐ wéi rán', + '厚德载福' => ' hòu dé zǎi fú', + '拉卜楞寺' => ' lā bǔ lèng sì', + '掳人勒赎' => ' lǔ rén lè shú', + '富得流油' => ' fù de líu yóu', + '好逸恶劳' => ' hào yì wù láo', + '积累基金' => ' jī lěi jī jīn', + '鼓起勇气' => ' gǔ qǐ yǒng qì', + '不是滋味' => ' bú shì zī wèi', + '助桀为恶' => ' zhù jié wéi è', + '只骑不反' => ' zhī qí bù fǎn', + '不是味儿' => ' bú shì wèi ér', + '子孝父慈' => ' zǐ xiào fù cí', + '难乎为继' => ' nán hū wéi jì', + '不拔之志' => ' bù bá zhī zhì', + '子子孙孙' => ' zǐ zǐ sūn sūn', + '蔽日干云' => ' bì rì gān yún', + '比物属事' => ' bǐ wù zhǔ shì', + '置之不顾' => ' zhì zhī bù gù', + '豁人耳目' => ' huò rén ěr mù', + '差异分析' => ' chā yì fēn xī', + '厚今薄古' => ' hòu jīn bó gǔ', + '骨头架子' => ' gú tou jià zi', + '之子于归' => ' zhī zǐ yú guī', + '好语似珠' => ' hǎo yǔ sì zhū', + '刿目鉥心' => ' guì mù xù xīn', + '莫可究诘' => ' mò kě jiū jié', + '脾气暴躁' => ' pí qi bào zào', + '靡有孑遗' => ' mǐ yǒu jié yí', + '靡靡之音' => ' mǐ mǐ zhī yīn', + '背碑覆局' => ' bèi bēi fù jú', + '著于竹帛' => ' zhù yú zhú bó', + '一饮而尽' => ' yī yǐn ér jìn', + '子为父隐' => ' zǐ wéi fù yǐn', + '比什凯克' => ' bǐ shí kǎi kè', + '不切实际' => ' bù qiē shí jì', + '主要目的' => ' zhǔ yào mù dì', + '不足为训' => ' bù zú wéi xùn', + '鲍子知我' => ' bào zǐ zhī wǒ', + '气息奄奄' => ' qì xī yān yān', + '电荷耦合' => ' diàn hè ǒu hé', + '仁义君子' => ' rén yì jūn zǐ', + '丢三落四' => ' diū sān là sì', + '原子武器' => ' yuán zǐ wǔ qì', + '墨迹未干' => ' mò jì wèi gān', + '挨打受气' => ' ái dǎ shòu qì', + '马萨诸塞' => ' mǎ sà zhū sài', + '白眼珠儿' => ' bái yǎn zhū r', + '吼三喝四' => ' hǒu sān hè sì', + '驽马十舍' => ' nú mǎ shí shè', + '父慈子孝' => ' fù cí zǐ xiào', + '目眦尽裂' => ' mù zì jìn liè', + '清辞丽曲' => ' qīng cí lì qǔ', + '难以为继' => ' nán yǐ wéi jì', + '不差什么' => ' bù chà shí mǒ', + '温切斯特' => ' wēn qiē sī tè', + '互不干涉' => ' hù bù gān shè', + '匹马只轮' => ' pǐ mǎ zhī lún', + '没有意思' => ' méi yǒu yì si', + '为仁不富' => ' wéi rén bù fù', + '热热闹闹' => ' rè rè nāo nao', + '安分知足' => ' ān fèn zhī zú', + '击排冒没' => ' jī pái mào mò', + '为富不仁' => ' wéi fù bù rén', + '比佛利山' => ' bǐ fó lì shān', + '挨打受骂' => ' ái dǎ shòu mà', + '及时处理' => ' jí shí chǔ lǐ', + '各行各业' => ' gè háng gè yè', + '只眼独具' => ' zhī yǎn dú jù', + '呢呢痴痴' => ' ní ní chī chī', + '七老八倒' => ' qī lǎo bā dǎo', + '暴衣露冠' => ' pù yī lù guàn', + '雷扎耶湖' => ' léi zhá yē hú', + '子母炸弹' => ' zǐ mǔ zhà dàn', + '日乾夕惕' => ' rì qián xī tì', + '日甚一日' => ' rì shèn yī rì', + '如履薄冰' => ' rú lǚ bó bīng', + '一朝一夕' => ' yī zhāo yī xī', + '卸磨杀驴' => ' xiè mò shā lǘ', + '朝不及夕' => ' zhāo bù jí xī', + '化腐为奇' => ' huà fǔ wéi qí', + '波流茅靡' => ' bō liú máo mǐ', + '别开一格' => ' bié kāi yí gé', + '寡凫单鹄' => ' guǎ fú dān hú', + '电子伏特' => ' diàn zǐ fú tè', + '如切如磋' => ' rú qiē rú cuō', + '否极泰回' => ' pǐ jí tài huí', + '披发左衽' => ' pī fà zuǒ rèn', + '子母炮弹' => ' zǐ mǔ pào dàn', + '白费力气' => ' bái fèi lì qi', + '如龙似虎' => ' rú lóng sì hǔ', + '彬彬济济' => ' bīn bīn jǐ jǐ', + '不可揆度' => ' bù kě kuí duó', + '呼天吁地' => ' hū tiān yù dì', + '积素累旧' => ' jī sù lěi jiù', + '含含糊糊' => ' hán hán hu hū', + '卜夜卜昼' => ' bǔ yè bǔ zhòu', + '弃甲倒戈' => ' qì jiǎ dǎo gē', + '播糠眯目' => ' bō kāng mǐ mù', + '扑杀此獠' => ' pū shā cǐ lǎo', + '不亦善夫' => ' bù yi shàn fū', + '如花似月' => ' rú huā sì yuè', + '气克斗牛' => ' qì kè dǒu niú', + '一模一样' => ' yī mú yī yàng', + '不随以止' => ' bu shui yi zi', + '一分为二' => ' yī fēn wéi èr', + '椎髻布衣' => ' zhuī jì bù yī', + '朝斯夕斯' => ' zhāo sī xī sī', + '苞苴竿牍' => ' bāo jū gān dú', + '尽力而为' => ' jìn lì ér wéi', + '积日累月' => ' jī rì lěi yuè', + '皮包骨头' => ' pí bāo gú tou', + '鼻口部分' => ' bí kǒu bù fèn', + '目空余子' => ' mù kōng yú zǐ', + '指头肚儿' => ' zhǐ tou dù er', + '青紫被体' => ' qīng zǐ pī tǐ', + '属辞比事' => ' zhǔ cí bǐ shì', + '不差毫发' => ' bù chā háo fà', + '避祸求福' => ' bì huò qiú fù', + '至矣尽矣' => ' zhì yǐ jìn yǐ', + '余勇可贾' => ' yú yǒng kě gǔ', + '史蒂文斯' => ' shǐ dì wén sī', + '积德累仁' => ' jī dé lěi rén', + '朱干玉戚' => ' zhū gān yù qī', + '宁死不屈' => ' nìng sǐ bù qū', + '母慈子孝' => ' mǔ cí zǐ xiào', + '下不为例' => ' xià bù wéi lì', + '黑芝麻糊' => ' hēi zhī ma hú', + '好问则裕' => ' hào wèn zé yù', + '属毛离里' => ' zhǔ máo lí lǐ', + '吆五喝六' => ' yāo wǔ hè liù', + '厚味腊毒' => ' hòu wèi xī dú', + '卑礼厚币' => ' bēi lǐ hòu bì', + '好善恶恶' => ' hǎo shàn wù è', + '不识起倒' => ' bù shí qǐ dǎo', + '气动葫芦' => ' qì dòng hú lu', + '激进分子' => ' jī jìn fèn zǐ', + '皮诺切特' => ' pí nuò qiē tè', + '不以为耻' => ' bù yǐ wéi chǐ', + '呱嗒板儿' => ' guā dā bǎn ér', + '宏都拉斯' => ' hóng dū lā sī', + '骑驴倒堕' => ' qí lǘ dǎo duò', + '积日累岁' => ' jī rì lěi suì', + '虎珀拾芥' => ' hǔ pò shí jiè', + '插入因子' => ' chā rù yīn zǐ', + '惠子知我' => ' huì zǐ zhī wǒ', + '如花似朵' => ' rú huā sì duǒ', + '一意孤行' => ' yī yì gū xíng', + '何所不为' => ' hé suǒ bù wéi', + '豁然大悟' => ' huò rán dà wù', + '八大胡同' => ' bā dà hú tòng', + '号啕大哭' => ' háo táo dà kū', + '自以为然' => ' zì yǐ wéi rán', + '没人味儿' => ' méi rén wèi r', + '贝克勒尔' => ' bèi kè lè ěr', + '村夫俗子' => ' cūn fū sú zǐ', + '默而识之' => ' mò ér zhì zhī', + '德勒巴克' => ' dé lè bā kè', + '吧唧吧唧' => ' bā ji bā ji', + '第一义谛' => ' dì yī yì dì', + '嘀里嘟噜' => ' dī lǐ dū lū', + '滴滴答答' => ' dī dī dā dā', + '乐乐呵呵' => ' lè lè hē hē', + '蹀儿鸭子' => ' dié r yā zi', + '忍饥挨饿' => ' rěn jī ái è', + '遗妻弃子' => ' yí qī qì zǐ', + '七子八婿' => ' qī zǐ bā xù', + '百色起义' => ' bó sè qǐ yì', + '迪斯科吧' => ' dí sī kē bā', + '不易一字' => ' bù yì yī zì', + '迷离马虎' => ' mí lí mǎ hu', + '迷迷糊糊' => ' mí mi hū hū', + '马路牙子' => ' mǎ lù yá zǐ', + '扎尔达里' => ' zā ěr dá lǐ', + '阿多尼斯' => ' a duō ní sī', + '扎鲁特旗' => ' zā lǔ tè qí', + '提拉米苏' => ' dī lā mǐ sū', + '古希腊语' => ' gǔ xī là yǔ', + '阿弥陀佛' => ' ē mí tuó fó', + '孤立子波' => ' gū lì zǐ bō', + '格格不吐' => ' gē gē bù tǔ', + '格格不纳' => ' gē gē bù nà', + '子女玉帛' => ' zǐ nǚ yù bó', + '法不阿贵' => ' fǎ bù ē guì', + '一古脑儿' => ' yī gǔ nǎo r', + '茶叶末儿' => ' chá yè mò r', + '计日而俟' => ' jì rì ér sì', + '一字不易' => ' yī zì bù yì', + '糊糊涂涂' => ' hū hū tú tú', + '哩哩啦啦' => ' li li lā lā', + '的黎波里' => ' dì lí bō lǐ', + '莫扎里拉' => ' mò zā lǐ lā', + '那不勒斯' => ' nà bù lè sī', + '那曲地区' => ' nà qǔ dì qū', + '哪儿的话' => ' nǎ r de huà', + '嗳气呕逆' => ' ài qì ǒu nì', + '劈里啪啦' => ' pī li pā lā', + '可歌可泣' => ' kě gē kě qì', + '阿富汗语' => ' a fù hàn yǔ', + '阿得莱德' => ' a dé lái dé', + '第比利斯' => ' dì bì lì sī', + '叽哩咕噜' => ' jī li gū lū', + '阿尔泰语' => ' a ěr tài yǔ', + '阿尔瓦雷' => ' a ěr wǎ léi', + '阿尔盖达' => ' a ěr gài dá', + '勒斯波斯' => ' lè sī bō sī', + '头儿脑儿' => ' tóu r nǎo r', + '墨子泣丝' => ' mò zǐ qì sī', + '马路沿儿' => ' mǎ lù yán r', + '目无余子' => ' mù wú yú zǐ', + '霹雳啪啦' => ' pī lì pā lā', + '卡拉季奇' => ' kǎ lā jì jī', + '模模糊糊' => ' mó mó hu hū', + '彼得格勒' => ' bǐ dé gé lè', + '克拉科夫' => ' kè lā kē fū', + '克孜勒苏' => ' kè zī lè sū', + '薄物细故' => ' bó wù xì gù', + '噼里啪啦' => ' pī lǐ pā lā', + '弥勒菩萨' => ' mí lè pú sà', + '认死理儿' => ' rèn sǐ lǐ r', + '卡拉奇那' => ' kǎ lā jī nà', + '阿尔卑斯' => ' a ěr bēi sī', + '阿德莱德' => ' a dé lái dé', + '阿美尼亚' => ' a měi ní yà', + '阿德雷德' => ' a dé léi dé', + '阿加迪尔' => ' a jiā dí ěr', + '阿利坎特' => ' a lì kǎn tè', + '阿伦达尔' => ' a lún dá ěr', + '阿克伦河' => ' a kè lún hé', + '阿克苏市' => ' a kè sū shì', + '阿拉伯文' => ' a lā bó wén', + '阿摩司书' => ' a mó sī shū', + '阿美恩斯' => ' a měi ēn sī', + '卡波耶拉' => ' kǎ bō yē lā', + '喀啦喀啦' => ' kā lā kā lā', + '阿金库尔' => ' a jīn kù ěr', + '阿拉尔市' => ' a lā ěr shì', + '阿特金斯' => ' a tè jīn sī', + '批哩啪啦' => ' pī li pā la', + '鸡蛋壳儿' => ' jī dàn ké r', + '卡尔扎伊' => ' kǎ ěr zā yī', + '阿普尔顿' => ' a pǔ ěr dùn', + '疙疙瘩瘩' => ' gē gē dā dá', + '粒子物理' => ' lì zǐ wù lǐ', + '闭目塞耳' => ' bì mù sè ěr', + '阿拉法特' => ' a lā fǎ tè', + '阿尔卡特' => ' a ěr kǎ tè', + '阿里巴巴' => ' a lǐ bā bā', + '阿耳戈斯' => ' a ěr gē sī', + '阿瓦里德' => ' a wǎ lǐ dé', + '阿穆尔河' => ' a mù ěr hé', + '阿斯马拉' => ' a sī mǎ lā', + '阿斯塔纳' => ' a sī tǎ nà', + '阿丽亚娜' => ' a lì yà nà', + '打哈哈儿' => ' dǎ hā ha r', + '阿尔伯特' => ' a ěr bó tè', + '阿拉巴马' => ' a lā bā mǎ', + '阿拉塔斯' => ' a lā tǎ sī', + '阿拉米语' => ' a lā mǐ yǔ', + '阿斯伯格' => ' a sī bó gé', + '阿格尼迪' => ' a gé ní dí', + '无恶不为' => ' wú è bù wéi', + '可泣可歌' => ' kě qì kě gē', + '呵佛骂祖' => ' hē fó mà zǔ', + '阿尔萨斯' => ' a ěr sà sī', + '阿尔伯塔' => ' a ěr bó tǎ', + '与物无忤' => ' yǔ wù wú wǔ', + '阿奇里斯' => ' a qí lǐ sī', + '阿瓦鲁阿' => ' a wǎ lǔ ā', + '苦不唧儿' => ' kǔ bu jī r', + '阿兹特克' => ' a zī tè kè', + '不大离儿' => ' bù dà lí r', + '挨饥抵饿' => ' ái jī dǐ è', + '阿卡迪亚' => ' a kǎ dí yà', + '厄勒布鲁' => ' e lè bù lǔ', + '阿卜杜拉' => ' a bǔ dù lā', + '阿布哈兹' => ' a bù hā zī', + '阿里地区' => ' a lǐ dì qū', + '阿布扎比' => ' a bù zā bǐ', + '阿布达比' => ' a bù dá bǐ', + '阿姆哈拉' => ' a mǔ hā lā', + '纳德阿里' => ' nà dé a lǐ', + '双重标准' => ' shuāng chóng biāo zhǔn', + '阿克苏河' => ' a kè sū hé', + '阿迪达斯' => ' a dí dá sī', + '阿达比尔' => ' a dá bǐ ěr', + '阿时趋俗' => ' ē shí qū sú', + '如饥似渴' => ' rú jī sì kě', + '计日以俟' => ' jì rì yǐ sì', + '巴克科思' => ' bā kè kē sī', + '差不离儿' => ' chà bù lí r', + '呼不给吸' => ' hū bù jǐ xī', + '击其不意' => ' jī qí bù yì', + '鸡皮鹤发' => ' jī pí hè fà', + '大麻哈鱼' => ' dà má hǎ yú', + '耶稣基督' => ' yē sū jī dū', + '叽叽哇哇' => ' jī jī wā wā', + '胳臂箍儿' => ' gē bei gū r', + '自给自足' => ' zì jǐ zì zú', + '格拉斯哥' => ' gé lā sī gē', + '巴耶利巴' => ' bā yē lì bā', + '巴尔的摩' => ' bā ěr dì mó', + '一笔抹摋' => ' yī bǐ mò sà', + '负固不服' => ' fù gù bù fú', + '一大早儿' => ' yī dà zǎo r', + '耶哥尼雅' => ' yē gē ní yǎ', + '欲取姑与' => ' yù qǔ gū yǔ', + '一口气儿' => ' yī kǒu qì r', + '以利亚撒' => ' yǐ lì yà sǎ', + '希腊字母' => ' xī là zì mǔ', + '一语破的' => ' yī yǔ pò dì', + '福斯塔夫' => ' fú sī tǎ fū', + '密密麻麻' => ' mì mì mā mɑ', + '鹤发鸡皮' => ' hè fà jī pí', + '萨格勒布' => ' sà gé lè bù', + '乌尔都语' => ' wū ěr dū yǔ', + '握发吐哺' => ' wò fà tǔ bǔ', + '无以塞责' => ' wú yǐ sè zé', + '乌衣子弟' => ' wū yī zǐ dì', + '呜呜咽咽' => ' wū wū yè yè', + '科纳克里' => ' kē nà kè lǐ', + '不起眼儿' => ' bù qǐ yǎn r', + '傻大个儿' => ' shǎ dà gè r', + '诃佛诋巫' => ' hē fó dǐ wū', + '科迪勒拉' => ' kē dí lè lā', + '余子碌碌' => ' yú zǐ lù lù', + '饿虎之蹊' => ' è hǔ zhī xī', + '父析子荷' => ' fù xī zǐ hé', + '一发破的' => ' yī fā pò dì', + '依撒意亚' => ' yī sā yì yà', + '塔塔儿人' => ' tǎ tǎ r rén', + '不可移易' => ' bù kě yí yì', + '敷衍塞责' => ' fū yǎn sè zé', + '阿育吠陀' => ' a yù fèi tuó', + '三臡八菹' => ' sān ní bā zū', + '喀喇沁旗' => ' kā lā qìn qí', + '阿瓦提县' => ' a wǎ tí xiàn', + '色厉胆薄' => ' sè lì dǎn bó', + '打击乐器' => ' dǎ jī yuè qì', + '克什米尔' => ' kè shí mǐ ěr', + '只字不提' => ' zhī zì bù tí', + '刻木为鹄' => ' kè mù wéi hú', + '喀什地区' => ' kā shí dì qū', + '小媳妇儿' => ' xiǎo xí fu r', + '喀什噶尔' => ' kā shí gá ěr', + '刻木为吏' => ' kè mù wéi lì', + '刻足适屦' => ' kè zú shì jù', + '集体宿舍' => ' jí tǐ sù shè', + '无地自处' => ' wú dì zì chǔ', + '泥古不化' => ' nì gǔ bù huà', + '奥西娜斯' => ' aò xī nuó sī', + '支吾其词' => ' zhī wū qí cí', + '除恶务尽' => ' chú è wù jìn', + '如花似玉' => ' rú huā sì yù', + '胳膊腕子' => ' gē bo wàn zǐ', + '毒泷恶雾' => ' dú lóng è wù', + '阿尔衮琴' => ' a ěr gǔn qín', + '克莱斯勒' => ' kè lái sī lè', + '阿图什市' => ' a tú shí shì', + '刻薄寡思' => ' kè bó guǎ sī', + '鹘入鸦群' => ' hú rù yā qún', + '靡颜腻理' => ' mǐ yán nì lǐ', + '入土为安' => ' rù tú wéi ān', + '磁单极子' => ' cí dān jí zǐ', + '蠹居棋处' => ' dù jū qí chǔ', + '赍志而没' => ' jī zhì ér mò', + '日居月诸' => ' rì jī yuè zh', + '糊涂一时' => ' hú tu yī shí', + '入不敷出' => ' rù bù fū chū', + '阿合奇县' => ' a hé qí xiàn', + '阿尔都塞' => ' a ěr dōu sāi', + '看得过儿' => ' kàn de guò r', + '阿非利加' => ' a fēi lì jiā', + '即心即佛' => ' jí xīn jí fó', + '高足弟子' => ' gāo zú dì zǐ', + '一仆二主' => ' yī pú èr zhǔ', + '目不暇给' => ' mù bù xiá jǐ', + '厚此薄彼' => ' hòu cǐ bó bǐ', + '科尼赛克' => ' kē ní sài kè', + '居礼夫人' => ' jū lǐ fū ren', + '日不暇给' => ' rì bù xiá jǐ', + '画虎刻鹄' => ' huà hǔ kè hú', + '阿克赛钦' => ' a kè sài qīn', + '阿其所好' => ' ē qí suǒ hào', + '螺丝起子' => ' luó sī qǐ zǐ', + '阿勒泰市' => ' a lè tài shì', + '厚积薄发' => ' hòu jī bó fā', + '敌特分子' => ' dí tè fèn zǐ', + '握发吐飧' => ' wò fà tǔ sūn', + '吁咈都俞' => ' yù fú dōu yú', + '积极分子' => ' jī jí fèn zǐ', + '稀里哗啦' => ' xī lǐ huā lā', + '诘屈聱牙' => ' jié qū áo yá', + '予取予夺' => ' yú qǔ yú duó', + '予取予求' => ' yú qǔ yú qiú', + '予取予携' => ' yú qǔ yú xié', + '厄勒海峡' => ' e lè hǎi xiá', + '泰西大儒' => ' tài xī dà rú', + '膏腴子弟' => ' gāo yú zǐ dì', + '包袱底儿' => ' bāo fú dǐ ér', + '梅妻鹤子' => ' méi qī hè zǐ', + '呼卢喝雉' => ' hū lú hè zhì', + '我黼子佩' => ' wǒ fǔ zǐ pèi', + '我负子戴' => ' wǒ fù zǐ dài', + '不足为虑' => ' bù zú wéi lǜ', + '高歌一曲' => ' gāo gē yī qǔ', + '虚与委蛇' => ' xū yǔ wēi yí', + '胡子拉碴' => ' hú zǐ lā chā', + '毛发丝粟' => ' máo fà sī sù', + '秘鲁苦蘵' => ' bì lǔ kǔ zhí', + '第二个人' => ' dì èr ge rén', + '热毛子马' => ' rè máo zǐ mǎ', + '懒得搭理' => ' lǎn de dā lǐ', + '群雌粥粥' => ' qún cí yù yù', + '扎格罗斯' => ' zā gé luó sī', + '人莫予毒' => ' rén mò yú dú', + '故家子弟' => ' gù jiā zǐ dì', + '居里夫人' => ' jū lǐ fū ren', + '模糊逻辑' => ' mó hu luó ji', + '遭遇不偶' => ' zāo yù bù ǒu', + '塞拉耶佛' => ' sè lā yē fó', + '结发夫妻' => ' jié fà fū qī', + '曲突移薪' => ' qǔ tū yí xīn', + '叽里呱啦' => ' jī lǐ guā lā', + '以苦为乐' => ' yǐ kǔ wéi lè', + '铆足劲儿' => ' mǎo zú jìn r', + '以蠡测海' => ' yǐ lǐ cè hǎi', + '失而复得' => ' shī ér fù de', + '绕弯子儿' => ' rào wān zǐ r', + '以鹿为马' => ' yǐ lù wéi mǎ', + '时移俗易' => ' shí yí sú yì', + '纨绔子弟' => ' wán kù zǐ dì', + '核子医学' => ' hé zǐ yī xué', + '母以子贵' => ' mǔ yǐ zǐ guì', + '马拉喀什' => ' mǎ lā kā shí', + '扞格不入' => ' hàn gé bù rù', + '绿林起义' => ' lù lín qǐ yì', + '碌碌无为' => ' lù lù wú wéi', + '狗玩儿的' => ' gǒu wán r de', + '不可分割' => ' bù kě fēn gē', + '勒维纳斯' => ' lè wéi nà sī', + '博物君子' => ' bó wù jūn zǐ', + '撒马尔罕' => ' sǎ mǎ ěr hǎn', + '过河卒子' => ' guò hé zú zǐ', + '哇哩哇啦' => ' wā lǐ wā lā', + '吐哺握发' => ' tǔ bǔ wò fà', + '除恶务本' => ' chú è wù běn', + '曲不离口' => ' qǔ bù lí kǒu', + '卖妻鬻子' => ' mài qī yù zǐ', + '不计其数' => ' bù jì qí shù', + '参差不齐' => ' cēn cī bù qí', + '勒哈费尔' => ' lè hā fèi ěr', + '异议分子' => ' yì yì fèn zǐ', + '妻离子散' => ' qī lí zǐ sàn', + '不拔一毛' => ' bù bá yì máo', + '去本趋末' => ' qù běn qū mò', + '吱哩哇啦' => ' zhī lī wā lā', + '纨裤子弟' => ' wán kù zǐ dì', + '亚麻子油' => ' yà má zǐ yóu', + '皈依佛法' => ' guī yī fó fǎ', + '积露为波' => ' jī lù wéi bō', + '曲学阿世' => ' qǔ xué ā shì', + '撒马尔干' => ' sǎ mǎ ěr gàn', + '依阿取容' => ' yī ē qǔ róng', + '压压脚儿' => ' yā ya jiǎo r', + '爱民如子' => ' ài mín rú zǐ', + '徒子徒孙' => ' tú zǐ tú sūn', + '兔起鹘落' => ' tù qǐ hú luò', + '以斯帖记' => ' yǐ sī tiě jì', + '误人子弟' => ' wù rén zǐ dì', + '格子花呢' => ' gé zi huā ní', + '姿态婀娜' => ' zī tài ē nuó', + '格洛斯特' => ' gé luò sī tè', + '格洛纳斯' => ' gé luò nà sī', + '慕古薄今' => ' mù gǔ bó jīn', + '铁哥们儿' => ' tiě gē men r', + '拭目以俟' => ' shì mù yǐ sì', + '不足为奇' => ' bù zú wéi qí', + '洗发水儿' => ' xǐ fà shuǐ r', + '为法自弊' => ' wéi fǎ zì bì', + '法尔卡什' => ' fǎ ěr kǎ shí', + '喜不自禁' => ' xǐ bù zì jīn', + '阿尔加维' => ' a ěr jiā wéi', + '擘肌分理' => ' bò jī fēn lǐ', + '疾不可为' => ' jí bù kě wéi', + '易子而食' => ' yì zǐ ér shí', + '以耳为目' => ' yǐ ěr wéi mù', + '塞浦路斯' => ' sài pǔ lù sī', + '拿印把儿' => ' ná yìn bà er', + '服务态度' => ' fú wù tài du', + '狐狸尾巴' => ' hú li wěi ba', + '易卜拉辛' => ' yì bǔ lā xīn', + '罗马尼亚' => ' luō mǎ ní yà', + '木骨都束' => ' mù gǔ dū shù', + '一呵而就' => ' yī hē ér jiù', + '细细地流' => ' xì xì de líu', + '不对劲儿' => ' bù duì jìn r', + '闭门塞户' => ' bì kǒu sè hù', + '词不逮意' => ' cí bù dài yì', + '词不逮理' => ' cí bù dài lǐ', + '干戈四起' => ' gān gē sì qǐ', + '至死靡它' => ' zhì sǐ mǐ tā', + '不对碴儿' => ' bù duì chá r', + '认死扣儿' => ' rèn sǐ kòu r', + '唏哩哗啦' => ' xī lī huā lā', + '六枝特区' => ' lù zhī tè qū', + '不知起倒' => ' bù zhī qǐ dǎo', + '不禁不由' => ' bù jīn bù yóu', + '一发千钧' => ' yī fà qiān jūn', + '引以为憾' => ' yǐn yǐ wéi hàn', + '几乎完全' => ' jī hū wán quán', + '分子杂交' => ' fēn zǐ zá jiāo', + '含情脉脉' => ' hán qíng mò mò', + '氧乙炔炬' => ' yǎng yǐ quē jù', + '偏差距离' => ' piān chā jù lí', + '书不尽言' => ' shū bù jìn yán', + '塞韦里诺' => ' sài wéi lǐ nuò', + '一呼百应' => ' yī hū bǎi yìng', + '弹射座椅' => ' tán shè zuò yǐ', + '含糊不清' => ' hán hu bù qīng', + '夙兴夜寐' => ' sù xīng yè mèi', + '一吐为快' => ' yī tǔ wéi kuài', + '孤家寡人' => ' gū jiā guǎ rén', + '有好奇心' => ' yǒu hào qí xīn', + '有子存焉' => ' yǒu zǐ cún yān', + '有朝一日' => ' yǒu zhāo yī rì', + '全军覆没' => ' quán jūn fù mò', + '朝露溘至' => ' zhāo lù kè zhì', + '大动干戈' => ' dà dòng gān gē', + '数理逻辑' => ' shù lǐ luó ji', + '有头无尾' => ' yǒu tóu wú wěi', + '乌踆兔走' => ' wū cún tù zǒu', + '神不附体' => ' shén bù fù tǐ', + '信而好古' => ' xìn ér hào gǔ', + '恫疑虚猲' => ' dòng yí xū gé', + '恫疑虚喝' => ' dòng yí xū hè', + '干云蔽日' => ' gān yún bì rì', + '公子哥儿' => ' gōng zǐ gē ér', + '处理办法' => ' chǔ lǐ bàn fǎ', + '嗓子眼儿' => ' sǎng zi yǎn r', + '跑堂儿的' => ' pǎo táng r de', + '瞰瑕伺隙' => ' kàn xiá sì xì', + '离本徼末' => ' lí běn yāo mò', + '出处语默' => ' chū chǔ yǔ mò', + '刚直不阿' => ' gāng zhí bù ē', + '身体发肤' => ' shēn tǐ fà fū', + '亚塞拜然' => ' yà sè bài rán', + '丑类恶物' => ' chǒu lèi è wù', + '节骨眼儿' => ' jiē gu yǎn er', + '五尺竖子' => ' wǔ chǐ shù zǐ', + '一劳永逸' => ' yī láo yǒng yì', + '张掖地区' => ' zhāng yè dì qū', + '出奇划策' => ' chū qí huá cè', + '孙子兵法' => ' sūn zǐ bīng fǎ', + '彼唱此和' => ' bǐ chàng cǐ hè', + '彬彬君子' => ' bīn bīn jūn zǐ', + '一小阵儿' => ' yī xiǎo zhèn r', + '值得品味' => ' zhí de pǐn wèi', + '一小部分' => ' yī xiǎo bù fèn', + '广安地区' => ' guǎng ān dì qū', + '沉不住气' => ' chén bú zhù qì', + '沉得住气' => ' chén de zhù qì', + '多劳多得' => ' duō láo duō de', + '往心里去' => ' wǎng xīn li qù', + '沐猴而冠' => ' mù hóu ér guàn', + '木管乐器' => ' mù guǎn yuè qì', + '会计制度' => ' kuài jì zhì dù', + '值得信赖' => ' zhí de xìn lài', + '分而治之' => ' fēn ér zhì zhī', + '柔情脉脉' => ' róu qíng mò mò', + '人尽其才' => ' rén jìn qí cái', + '不为酒困' => ' bù wéi jǐu kùn', + '凸面部分' => ' tū miàn bù fèn', + '借方差额' => ' jiè fāng chā é', + '孑孑为义' => ' jié jié wéi yì', + '和记黄埔' => ' hé jì huáng pǔ', + '挑三窝四' => ' tiǎo sān wō sì', + '咬舌自尽' => ' yǎo shé zì jìn', + '一干二净' => ' yī gān èr jìng', + '孔子家语' => ' kǒng zǐ jiā yǔ', + '克什克腾' => ' kè shí kè téng', + '压轴好戏' => ' yā zhòu hǎo xì', + '朝三暮四' => ' zhāo sān mù sì', + '没齿不忘' => ' mò chǐ bù wàng', + '安步当车' => ' ān bù dàng chē', + '厦门大学' => ' xià mén dà xué', + '一唱一和' => ' yī chàng yī hè', + '安分守己' => ' ān fèn shǒu jǐ', + '倒背手儿' => ' dào bèi shǒu r', + '曾外祖母' => ' zēng wài zǔ mǔ', + '曾外祖父' => ' zēng wài zǔ fù', + '水磨沟区' => ' shuǐ mò gōu qū', + '多那太罗' => ' duō nǎ tài luó', + '往泥里踩' => ' wǎng nì lǐ cǎi', + '言不尽意' => ' yán bù jìn yì', + '梨俱吠陀' => ' lí jū fèi tuó', + '新式拚法' => ' xīn shì pīn fǎ', + '感激不尽' => ' gǎn jī bù jìn', + '落魄不偶' => ' luo tuo bu ou', + '新斯科舍' => ' xīn sī kē shè', + '动嘴皮儿' => ' dòng zuǐ pí r', + '精子密度' => ' jīng zǐ mì dù', + '应付自如' => ' yìng fù zì rú', + '右翼分子' => ' yòu yì fēn zǐ', + '扎欧扎翁' => ' zā ōu zā wēng', + '扎马鲁丁' => ' zā mǎ lǔ dīng', + '德薄才疏' => ' dé bó cái shū', + '落拓不羁' => ' luò tuò bù jī', + '爱丽思泉' => ' aì lí sī quán', + '丢卒保车' => ' diū zú bǎo jū', + '结结巴巴' => ' jiē jiē bā bā', + '日积月累' => ' rì jī yuè lěi', + '干戈载戢' => ' gàn gē zǎi jí', + '绝大部分' => ' jué dà bù fen', + '络腮胡子' => ' luò sāi hú zǐ', + '依样葫芦' => ' yī yàng hú lu', + '糖醋里脊' => ' táng cù lǐ jǐ', + '逻辑思维' => ' luó ji sī wéi', + '锡林郭勒' => ' xī lín guō lè', + '纽几内亚' => ' nǐu jī nèi yà', + '分离分子' => ' fēn lí fèn zǐ', + '没金饮羽' => ' mò jīn yǐn yǔ', + '数以万计' => ' shǔ yǐ wàn jì', + '引以为傲' => ' yǐn yǐ wéi ào', + '纶音佛语' => ' lún yīn fó yǔ', + '东抹西涂' => ' dōng mò xī tú', + '纳什维尔' => ' nà shí wéi ěr', + '弹拨乐器' => ' tán bō yuè qì', + '实逼处此' => ' shí bī chǔ cǐ', + '引喻失义' => ' yǐn yù shī yì', + '人给家足' => ' rén jǐ jiā zú', + '心里踏实' => ' xīn li tā shi', + '五侯蜡烛' => ' wǔ hòu là zhú', + '纳粹分子' => ' nà cuì fèn zǐ', + '素食主义' => ' sù shí zhǔ yì', + '人才济济' => ' rén cái jǐ jǐ', + '应付裕如' => ' yìng fù yù rú', + '处心积虑' => ' chǔ xīn jī lǜ', + '视民如子' => ' shì mín rú zǐ', + '推枯折腐' => ' tuī kū shé fǔ', + '奥维耶多' => ' aò wéi yē duō', + '二道贩子' => ' èr dào fàn zǐ', + '等一会儿' => ' děng yī huì r', + '等一下儿' => ' děng yī xià r', + '为民父母' => ' wéi mín fù mǔ', + '恶不去善' => ' wù bù qù shàn', + '膏车秣马' => ' gào chē mò mǎ', + '奥切诺斯' => ' aò qiē nuò sī', + '高义薄云' => ' gāo yì bó yún', + '个别差异' => ' gè bié chā yì', + '分析处理' => ' fēn xī chǔ lǐ', + '等离子体' => ' děng lí zǐ tǐ', + '二十八宿' => ' èr shí bā xiù', + '唐突西子' => ' táng tū xī zǐ', + '悬崖勒马' => ' xuán yá lè mǎ', + '泪眼模糊' => ' lèi yǎn mó hu', + '森海塞尔' => ' sēn hǎi sè ěr', + '有机分子' => ' yǒu jī fēn zǐ', + '蜜月假期' => ' mì yuè jià qī', + '累土聚沙' => ' lěi tǔ jù shā', + '梨园子弟' => ' lí yuán zǐ dì', + '筋疲力尽' => ' jīn pí lì jìn', + '以意为之' => ' yǐ yì wéi zhī', + '会计科目' => ' kuài jì kē mù', + '扎马剌丁' => ' zā mǎ lá dīng', + '无理要求' => ' wú lǐ yāo qiú', + '细胞因子' => ' xì bāo yīn zǐ', + '细磨刀石' => ' xì mò dāo shí', + '因敌为资' => ' yīn dí wéi zī', + '恶居下流' => ' wù jū xià liú', + '犁牛之子' => ' lí niú zhī zǐ', + '无尽无休' => ' wú jìn wú xiū', + '无间可伺' => ' wú jiān kě sì', + '高干子弟' => ' gāo gàn zǐ dì', + '无后为大' => ' wú hòu wéi dà', + '具体地说' => ' jù tǐ de shuō', + '五侯七贵' => ' wǔ hòu qī guì', + '力尽筋疲' => ' lì jìn jīn pí', + '淫词艳曲' => ' yín cí yàn qǔ', + '曲高和寡' => ' qǔ gāo hè guǎ', + '五斗米道' => ' wǔ dǒu mǐ dào', + '数据处理' => ' shù jù chǔ lǐ', + '嘉柏隆里' => ' jiā bó lóng lǐ', + '参差错落' => ' cēn cī cuò luò', + '家给民足' => ' jiā jǐ mín zú', + '各有所好' => ' gè yǒu suǒ hào', + '松巴哇岛' => ' sōng bā wā dǎo', + '一盘散沙' => ' yī pán sǎn shā', + '一目了然' => ' yī mù liǎo rán', + '一目十行' => ' yī mù shí háng', + '一网打尽' => ' yī wǎng dǎ jìn', + '忍俊不禁' => ' rěn jùn bù jīn', + '国际关系' => ' guó jì guān xì', + '拨弦乐器' => ' bō xián yuè qì', + '佛口蛇心' => ' fó kǒu shé xīn', + '俄勒冈州' => ' e lè gāng zhōu', + '悲歌当哭' => ' bēi gē dàng kū', + '情不自禁' => ' qíng bù zì jīn', + '嗳腐吞酸' => ' ài fǔ tūn suān', + '哩溜歪斜' => ' li liū wāi xié', + '伽马射线' => ' gā mǎ shè xiàn', + '不可估量' => ' bù kě gū liang', + '失败主义' => ' shī bài zhǔ yì', + '伸缩喇叭' => ' shēn suō lǎ ba', + '子弹火车' => ' zǐ dàn huǒ chē', + '带电粒子' => ' dài diàn lì zǐ', + '可数名词' => ' kě shǔ míng cí', + '倒戈卸甲' => ' dǎo gē xiè jiǎ', + '剃发留辫' => ' tì fà líu biàn', + '拉开架势' => ' lā kāi jià shi', + '天雨路滑' => ' tiān yù lù huá', + '前途未卜' => ' qián tú wèi bǔ', + '子宫肌瘤' => ' zǐ gōng jī liú', + '殒身不恤' => ' yǔn shēn bú xù', + '侯门似海' => ' hóu mén sì hǎi', + '列别杰夫' => ' liè biè jié fū', + '气鸣乐器' => ' qì míng yuè qì', + '信号处理' => ' xìn hào chǔ lǐ', + '杜松子酒' => ' dù sōng zǐ jiǔ', + '大有作为' => ' dà yǒu zuò wéi', + '一气呵成' => ' yī qì hē chéng', + '室町幕府' => ' shì tǐng mù fǔ', + '心肌梗塞' => ' xīn jī gěng sè', + '心里有数' => ' xīn li yǒu shù', + '曲麻莱县' => ' qǔ má lái xiàn', + '列宁格勒' => ' liè níng gé lè', + '嗳气吞酸' => ' ài qì tūn suān', + '意犹未尽' => ' yì yóu wèi jìn', + '急公好义' => ' jí gōng hào yì', + '切理会心' => ' qiē lǐ huì xīn', + '啰里啰嗦' => ' luō li luō suo', + '罗曼带克' => ' luō màn dài kè', + '佛朗机炮' => ' fó lǎng jī pào', + '柔佛海峡' => ' róu fó hǎi xiá', + '佛兰芒语' => ' fó lán máng yǔ', + '互为因果' => ' hù wéi yīn guò', + '一一对应' => ' yī yī duì yìng', + '贫嘴薄舌' => ' pín zuǐ bó shé', + '大煞风趣' => ' dà shà fēng qù', + '四大名著' => ' sì dà míng zhù', + '一成不易' => ' yī chéng bù yì', + '面似靴皮' => ' miàn sì xuē pí', + '大肆铺张' => ' dà sì pū zhāng', + '改曲易调' => ' gǎi qǔ yì diào', + '义薄云天' => ' yì bó yún tiān', + '改口沓舌' => ' gǎi kǒu tà shé', + '改恶为善' => ' gǎi è wéi shàn', + '抱法处势' => ' bào fǎ chǔ shì', + '右派分子' => ' yòu pài fèn zǐ', + '恣意妄为' => ' zì yì wàng wéi', + '步罡踏斗' => ' bù gāng tà dǒu', + '不干不净' => ' bù gān bù jìng', + '昌都地区' => ' chāng dū dì qū', + '步步为营' => ' bù bù wéi yíng', + '助人为乐' => ' zhù rén wéi lè', + '步斗踏罡' => ' bù dǒu tà gāng', + '核战斗部' => ' hé zhàn dǒu bù', + '任人宰割' => ' rèn rén zǎi gē', + '夷为平地' => ' yí wéi píng dì', + '暴虎冯河' => ' bào hǔ píng hé', + '托勒密王' => ' tuō lè mì wáng', + '死海古卷' => ' sǐ hǎi gǔ juàn', + '太仆寺卿' => ' tài pú sì qīng', + '各奔东西' => ' gè bèn dōng xī', + '恐怖分子' => ' kǒng bù fèn zi', + '恐怖片儿' => ' kǒng bù piān r', + '四海为家' => ' sì hǎi wéi jiā', + '四方台区' => ' sì fāng tái qū', + '是非曲直' => ' shì fēi qǔ zhí', + '太子太保' => ' tài zǐ tài bǎo', + '佳人才子' => ' jiā rén cái zǐ', + '到此为止' => ' dào cǐ wéi zhǐ', + '封妻荫子' => ' fēng qī yìn zǐ', + '朝不保夕' => ' zhāo bù bǎo xī', + '爱丽舍宫' => ' ài lì shè gōng', + '不怀好意' => ' bù huái hào yì', + '慈悲为本' => ' cí bēi wéi běn', + '反客为主' => ' fǎn kè wéi zhǔ', + '人际关系' => ' rén jì guān xì', + '不是东西' => ' bù shì dōng xi', + '嘎嘎小姐' => ' gā gā xiǎo jie', + '反叛分子' => ' fǎn pàn fèn zǐ', + '勉力而为' => ' miǎn lì ér wéi', + '仁人君子' => ' rén rén jūn zǐ', + '以人为本' => ' yǐ rén wéi běn', + '棋高一着' => ' qí gāo yī zhāo', + '找台阶儿' => ' zhǎo tái jiē r', + '印度音乐' => ' yìn dù yīn yuè', + '南箕北斗' => ' nán jī běi dǒu', + '妄自菲薄' => ' wàng zì fěi bó', + '打家劫舍' => ' dǎ jiā jié shè', + '乳臭未干' => ' rǔ xiù wèi gān', + '不为已甚' => ' bù wéi yǐ shèn', + '仁至义尽' => ' rén zhì yì jìn', + '小苏打粉' => ' xiǎo sū dá fěn', + '文书处理' => ' wén shū chǔ lǐ', + '动弹不得' => ' dòng tan bù dé', + '嘉兴地区' => ' jiā xīng dì qū', + '投其所好' => ' tóu qí suǒ hào', + '斗酒只鸡' => ' dǒu jiǔ zhī jī', + '五行八作' => ' wǔ háng bā zuò', + '技术知识' => ' jì shù zhī shi', + '化敌为友' => ' huà dí wéi yǒu', + '文艺复兴' => ' wén yì fù xīng', + '斜愣眼儿' => ' xié leng yǎn r', + '哲人其萎' => ' zhé rén qí wěi', + '化为乌有' => ' huà wéi wū yǒu', + '人尽其材' => ' rén jìn qí cái', + '不为人知' => ' bù wéi rén zhī', + '令狐德棻' => ' líng hú dé fēn', + '代数拓扑' => ' dài shù tuò pū', + '受冻挨饿' => ' shòu dòng ái è', + '委靡不振' => ' wěi mǐ bù zhèn', + '委内瑞拉' => ' wěi nèi ruì lā', + '危如朝露' => ' wēi rú zhāo lù', + '小菜碟儿' => ' xiǎo cài dié r', + '拓扑结构' => ' tuò pū jié gòu', + '一语中的' => ' yī yǔ zhōng dì', + '才子佳人' => ' cái zǐ jiā rén', + '才高八斗' => ' cái gāo bā dǒu', + '干脆利落' => ' gān cuì lì luò', + '堆案盈几' => ' duī àn yíng jī', + '巨海扇蛤' => ' jù hǎi shàn gé', + '核电荷数' => ' hé diàn hè shù', + '一言难尽' => ' yī yán nán jìn', + '别具只眼' => ' bié jù zhī yǎn', + '林荫大道' => ' lín yìn dà dào', + '手板葫芦' => ' shǒu bǎn hú lu', + '搞花样儿' => ' gǎo huā yàng r', + '母音调和' => ' mǔ yīn tiáo hé', + '枯草杆菌' => ' kū cǎo gǎn jūn', + '怎么得了' => ' zěn me dé liǎo', + '巴塞隆纳' => ' bā sài lóng nà', + '希腊神话' => ' xī là shén huà', + '供大于求' => ' gòng dà yú qiú', + '利他行为' => ' lì tā xíng wéi', + '卑鄙龌龊' => ' bēi bǐ wò chuò', + '极端分子' => ' jí duān fèn zǐ', + '小时候儿' => ' xiǎo shí hou r', + '数不过来' => ' shǔ bù guò lái', + '打躬作揖' => ' dǎ gōng zuō yī', + '乙种粒子' => ' yǐ zhǒng lì zǐ', + '启应祈祷' => ' qǐ yìng qí dǎo', + '好自为之' => ' hào zì wéi zhī', + '棒子面儿' => ' bàng zi miàn r', + '扼襟控咽' => ' è jīn kòng yān', + '尺寸过大' => ' chǐ cun guò dà', + '千钧一发' => ' qiān jūn yī fà', + '戴高帽子' => ' dài gāo mào zǐ', + '败子回头' => ' bài zǐ huí tóu', + '原子序数' => ' yuán zǐ xù shù', + '并不在乎' => ' bìng bù zài hu', + '极深研几' => ' jí shēn yán jī', + '极为庞大' => ' jí wéi páng dà', + '好大喜功' => ' hào dà xǐ gōng', + '丫头片子' => ' yā tou piān zi', + '手扳葫芦' => ' shǒu bān hú lu', + '横竖劲儿' => ' héng shù jìn r', + '委肉虎蹊' => ' wěi ròu hǔ xī', + '喀拉汗国' => ' kā lā hán guó', + '批亢抵巇' => ' pī gāng dǐ xī', + '路易十六' => ' lù yì shí liù', + '塞拉利昂' => ' sài lā lì áng', + '以邻为壑' => ' yǐ lín wéi hè', + '干巴利落' => ' gān bā lì luò', + '昆都仑区' => ' kūn dū lún qū', + '羿射九日' => ' yì shè jiǔ rì', + '干巴利脆' => ' gān bā lì cuì', + '多哈回合' => ' duō hā huí hé', + '无为自化' => ' wú wéi zì huà', + '网路特务' => ' wǎng lù tè wu', + '物稀为贵' => ' wù xī wéi guì', + '以为后图' => ' yǐ wéi hòu tú', + '无为而治' => ' wú wéi ér zhì', + '绿林豪客' => ' lù lín háo kè', + '詈夷为跖' => ' lì yí wéi zhí', + '偶一为之' => ' ǒu yī wéi zhī', + '经济体系' => ' jīng jì tǐ xì', + '大大咧咧' => ' dà dà liē liē', + '地区差价' => ' dì qū chā jià', + '鲁难未已' => ' lǔ nàn wèi yǐ', + '以牙还牙' => ' yǐ yá huán yá', + '一夕一朝' => ' yī xī yī zhāo', + '符号逻辑' => ' fú hào luó ji', + '临难不惧' => ' lín nàn bù jǔ', + '耶稣会士' => ' yē sū huì shì', + '束蒲为脯' => ' shù pú wéi pú', + '大而无当' => ' dà ér wú dàng', + '附肤落毛' => ' fù fū luò máo', + '耳鼻咽喉' => ' ěr bí yān hóu', + '服服帖帖' => ' fú fu tiē tiē', + '打打闹闹' => ' dá dǎ nào nào', + '佛山地区' => ' fó shān dì qū', + '以书为御' => ' yǐ shū wéi yù', + '昭披耶河' => ' zhāo pī yē hé', + '值得注意' => ' zhí de zhù yì', + '符合要求' => ' fú hé yāo qiú', + '耶律大石' => ' yē lù: dà shí', + '以己度人' => ' yǐ jǐ duó rén', + '罗切斯特' => ' luó qiē sī tè', + '移天易日' => ' yí tiān yì rì', + '易俗移风' => ' yì sú yí fēng', + '格物致知' => ' gé wù zhì zhī', + '夹起尾巴' => ' jiā qǐ wěi ba', + '耶酥会士' => ' yē sū huì shì', + '巴马干酪' => ' bā mǎ gān lào', + '一谦四益' => ' yī qiān sì yì', + '差不多的' => ' chà bu duō de', + '差一点儿' => ' chà yī diǎn r', + '一切万物' => ' yī qiē wàn wù', + '视为儿戏' => ' shì wéi ér xì', + '丝毫不差' => ' sī háo bù chā', + '恶迹昭著' => ' è jì zhāo zhù', + '凯撒肋雅' => ' kǎi sā lèi yǎ', + '须发皆白' => ' xū fà jiē bái', + '绿林大盗' => ' lù lín dà dào', + '死亡无日' => ' sǐ wáng wú rì', + '与时俱进' => ' yǔ shí jū jìn', + '伺瑕导隙' => ' sì xiá dǎo xì', + '法家拂士' => ' fǎ jiā bì shì', + '羞与为伍' => ' xiū yǔ wéi wǔ', + '倒持太阿' => ' dǎo chí tài ā', + '羞人答答' => ' xiū rén dā dā', + '布干维尔' => ' bù gān wéi ěr', + '岂弟君子' => ' kǎi tì jūn zǐ', + '一目五行' => ' yī mù wǔ háng', + '砥砺琢磨' => ' dǐ lì zhuó mó', + '多子多福' => ' duō zǐ duō fú', + '藉资挹注' => ' jiè zī yì zhù', + '石头子儿' => ' shí tou zǐ er', + '一脚不移' => ' yī jiǎo bù yí', + '骂骂咧咧' => ' mà mà liē liē', + '意念移物' => ' yì niàn yí wù', + '置之死地' => ' zhì zhī sǐ dì', + '加利肋亚' => ' jiā lì lèi yà', + '置之不理' => ' zhì zhī bù lǐ', + '吾自有处' => ' wu zi you chu', + '时移世易' => ' shí yí shì yì', + '一劳久逸' => ' yī láo jiǔ yì', + '恶紫夺朱' => ' wù zǐ duó zhū', + '加勒比海' => ' jiā lè bǐ hǎi', + '五子登科' => ' wǔ zǐ dēng kē', + '实与有力' => ' shí yù yǒu lì', + '加德满都' => ' jiā dé mǎn dū', + '萨摩耶犬' => ' sà mó yē quǎn', + '加里肋亚' => ' jiā lǐ lèi yà', + '耶路撒冷' => ' yē lù sā lěng', + '聊以塞责' => ' liáo yǐ sè zé', + '似醉如痴' => ' sì zuì rú chī', + '财殚力痡' => ' cái dān lì pū', + '只鸡絮酒' => ' zhī jī xù jiǔ', + '不对茬儿' => ' bú duì chá ér', + '不用客气' => ' bù yòng kè qi', + '投机倒把' => ' tóu jī dǎo bǎ', + '鹤处鸡群' => ' hè chǔ jī qún', + '毕毕剥剥' => ' bì bì bāo bāo', + '朱颜绿发' => ' zhū yán lǜ fà', + '拖拖沓沓' => ' tuō tuō tà tà', + '爱莫之助' => ' ài mò zhī zhù', + '蒿子秆儿' => ' hāo zǐ gǎn ér', + '吉电子伏' => ' jí diàn zǐ fú', + '刿鉥心腑' => ' guì xù xīn fǔ', + '画地为狱' => ' huà dì wéi yù', + '朱颜鹤发' => ' zhū yán hè fà', + '千里一曲' => ' qiān lǐ yī qǔ', + '才疏德薄' => ' cái shū dé bó', + '取之不尽' => ' qǔ zhī bù jìn', + '狐藉虎威' => ' hú jiè hǔ wēi', + '曲曲折折' => ' qū qū zhé shé', + '一哄而起' => ' yī hòng ér qǐ', + '无以为报' => ' wú yǐ wéi bào', + '一年四季' => ' yì nián sì jì', + '一般贸易' => ' yī bān mào yì', + '世界屋脊' => ' shì jiè wū jǐ', + '不足齿数' => ' bù zú chǐ shǔ', + '不足为道' => ' bù zú wéi dào', + '家给人足' => ' jiā jǐ rén zú', + '掎挈伺诈' => ' jǐ qiè sì zhà', + '鸡毛掸子' => ' jī máo dǎn zǐ', + '鹤发松姿' => ' hè fà sōng zī', + '泥古拘方' => ' nì gǔ jū fāng', + '止戈为武' => ' zhǐ gē wéi wǔ', + '曲意迎合' => ' qǔ yì yíng hé', + '不置可否' => ' bù zhì kě fǒu', + '呲牙咧嘴' => ' zī yá liě zuǐ', + '被褐怀玉' => ' pī hè huái yù', + '泥古非今' => ' nì gǔ fēi jīn', + '洛克菲勒' => ' luò kè fēi lè', + '男子气概' => ' nán zǐ qì gài', + '不当得利' => ' bù dàng de lì', + '否去泰来' => ' pǐ qù tài lái', + '据为己有' => ' jù wéi jǐ yǒu', + '大拇指头' => ' dà mǔ zhǐ tou', + '金发碧眼' => ' jīn fà bì yǎn', + '父为子隐' => ' fù wéi zǐ yǐn', + '服务行业' => ' fú wù háng yè', + '移风易俗' => ' yí fēng yì sú', + '梨园弟子' => ' lí yuán dì zǐ', + '一佛出世' => ' yī fó chū shì', + '似是而非' => ' sì shì ér fēi', + '功不可没' => ' gōng bù kě mò', + '一毫不差' => ' yī háo bù chā', + '覆亡无日' => ' fù wáng wú rì', + '一定不易' => ' yī dìng bù yì', + '攒眉蹙额' => ' cuán mei cù é', + '书不尽意' => ' shū bù jìn yì', + '恶迹昭着' => ' è jì zhāo zhe', + '恰如其分' => ' qià rú qí fèn', + '大有可为' => ' dà yǒu kě wéi', + '投河自尽' => ' tóu hé zì jìn', + '大气磅礴' => ' dà qì páng bó', + '大璞不完' => ' tài bú bù wán', + '嗳气腐臭' => ' ài qì fǔ chòu', + '头足异处' => ' tóu zú yì chǔ', + '纪律处分' => ' jì lǜ chǔ fèn', + '福孙荫子' => ' fú sūn yìn zǐ', + '哈巴河县' => ' hā bā hé xiàn', + '卑鄙无耻' => ' bēi bǐ wú chǐ', + '无所不为' => ' wú suǒ bù wéi', + '畸流洽客' => ' jī liú qià kè', + '孺子可教' => ' rú zǐ kě jiào', + '伺瑕抵蠙' => ' sì xiá dǐ pín', + '集腋为裘' => ' jí yè wéi qiú', + '嗳气酸腐' => ' ài qì suān fǔ', + '失去意识' => ' shī qù yì shí', + '喷薄欲出' => ' pēn bó yù chū', + '低音提琴' => ' dī yīn tí qín', + '俯拾地芥' => ' fǔ shí dì jiè', + '解衣衣人' => ' jiè yī yī rén', + '头没杯案' => ' tóu mò bēi àn', + '投袂荷戈' => ' tóu mèi hè gē', + '万柏林区' => ' wàn bó lín qū', + '浮名薄利' => ' fú míng bó lì', + '负气斗狠' => ' fù qì dǒu hěn', + '布隆伯格' => ' bù lōng bó gé', + '羞羞答答' => ' xiū xiū dā dā', + '少儿不宜' => ' shào ér bù yí', + '没世无闻' => ' mò shì wú wén', + '渔阳鞞鼓' => ' yǔ yáng pí gǔ', + '苏州码子' => ' sū zhōu mǎ zǐ', + '闭目塞听' => ' bì mù sè tīng', + '闭门塞窦' => ' bì kǒu sè dòu', + '歙漆阿胶' => ' shè qī ē jiāo', + '文似其人' => ' wén sì qí rén', + '桃蹊柳曲' => ' táo qī liǔ qǔ', + '朝露暮霭' => ' zhāo lù mù ǎi', + '笃学好古' => ' dǔ xué hào gǔ', + '靡靡之乐' => ' mǐ mǐ zhī yuè', + '业余教育' => ' yè yú jiào yù', + '门单户薄' => ' mén dān hù bó', + '斗绝一隅' => ' dǒu jué yī yú', + '娱妻弄子' => ' yú qī nòng zǐ', + '数一数二' => ' shǔ yī shǔ èr', + '御史大夫' => ' yù shǐ dài fū', + '西格蒙德' => ' xī gé mēng dé', + '吁天呼地' => ' yù tiān hū dì', + '外孙女儿' => ' wài sūn nǔ: r', + '尼勒克县' => ' ní lè kè xiàn', + '孝子爱日' => ' xiào zǐ ài rì', + '手拉葫芦' => ' shǒu lā hú lu', + '裸子植物' => ' luǒ zǐ zhí wù', + '居民点儿' => ' jū mín diǎn r', + '毓子孕孙' => ' yù zǐ yùn sūn', + '阿克塞县' => ' a kè sài xiàn', + '阿什拉维' => ' a shén lā wéi', + '帷薄不修' => ' wéi bó bù xiū', + '谓予不信' => ' wèi yú bù xìn', + '郁郁累累' => ' yù yù lěi lěi', + '读书君子' => ' dú shū jūn zǐ', + '阿图什县' => ' a tú shí xiàn', + '卑辞厚币' => ' bēi cí hòu bì', + '阿克陶县' => ' a kè táo xiàn', + '尺布斗粟' => ' chǐ bù dǒu sù', + '为恶不悛' => ' wéi è bù quān', + '斗米尺布' => ' dǒu mǐ chǐ bù', + '血肉模糊' => ' xuè ròu mó hu', + '神乎其技' => ' shén hū qí jì', + '未为不可' => ' wèi wéi bù kě', + '优游自得' => ' yōu yóu zì dé', + '屈指可数' => ' qū zhǐ kě shǔ', + '吃里扒外' => ' chī lǐ pá wài', + '绨袍之义' => ' tì páo zhī yì', + '如胶似漆' => ' rú jiāo sì qī', + '衣单食薄' => ' yī dān shí bó', + '阿旺曲培' => ' a wàng qū péi', + '克仑特罗' => ' kè lún tè luō', + '阿旺曲沛' => ' a wàng qǔ pèi', + '允许误差' => ' yǔn xǔ wù chā', + '砚水壶儿' => ' yàn shuǐ hú r', + '运移时易' => ' yùn yí shí yì', + '靡衣偷食' => ' mǐ yī tōu shí', + '如法炮制' => ' rú fǎ páo zhì', + '载入史册' => ' zǎi rù shǐ cè', + '扫地以尽' => ' sǎo dì yǐ jìn', + '石河子市' => ' shí hé zǐ shì', + '阿尔山市' => ' a ěr shān shì', + '凿坏以遁' => ' záo pī yǐ dùn', + '打心眼里' => ' dǎ xīn yǎn li', + '泽被后世' => ' zé pī hòu shì', + '味如鸡肋' => ' wèi rú jī lèi', + '如狼似虎' => ' rú láng sì hǔ', + '踏踏实实' => ' tā tā shi shí', + '大吃苦头' => ' dà chī kǔ tou', + '阿苏火山' => ' a sū huǒ shān', + '极右分子' => ' jí yòu fèn zǐ', + '为非作恶' => ' wéi fēi zuò è', + '朝不虑夕' => ' zhāo bù lǜ xī', + '扫地俱尽' => ' sǎo dì jù jìn', + '乐府诗集' => ' yuè fǔ shī jí', + '好色之徒' => ' hào sè zhī tú', + '阿肯色州' => ' a kěn sè zhōu', + '小步舞曲' => ' xiǎo bù wǔ qǔ', + '啼饥号寒' => ' tí jī háo hán', + '看不过去' => ' kàn bu guò qu', + '乐山大佛' => ' lè shān dà fó', + '三闾大夫' => ' sān lǘ dài fū', + '斗粟尺布' => ' dǒu sù chǐ bù', + '小日本儿' => ' xiǎo rì běn r', + '卜昼卜夜' => ' bǔ zhòu bǔ yè', + '咳珠唾玉' => ' ké zhū tuò yù', + '如其所好' => ' rú qí suǒ hào', + '脊柱后凸' => ' jǐ zhù hòu tū', + '杜默为诗' => ' dù mò wéi shī', + '塞尔维亚' => ' sài ěr wéi yà', + '别太客气' => ' bié tài kè qi', + '列缺霹雳' => ' liè quē pī lì', + '大不列蹀' => ' dà bu lie die', + '这早晚儿' => ' zhè zǎo wǎn r', + '赫鲁晓夫' => ' hè lǔ xiǎo fu', + '赞叹不已' => ' zàn tàn bù yǐ', + '劳筋苦骨' => ' láo jīn kǔ gǔ', + '纷纭杂沓' => ' fēn yún zá tà', + '曼切斯特' => ' màn qiē sī tè', + '发秃齿豁' => ' fà tū chǐ huò', + '大陆漂移' => ' dà lù piāo yí', + '曲靖地区' => ' qǔ jìng dì qū', + '必和必拓' => ' bì huó bì tuò', + '过去分词' => ' guò qù fèn cí', + '荆棘塞途' => ' jīng jí sè tú', + '分子医学' => ' fēn zǐ yī xué', + '浮白载笔' => ' fú bái zǎi bǐ', + '巴塞罗那' => ' bā sài luó nà', + '丝恩发怨' => ' sī ēn fà yuàn', + '来日大难' => ' lái rì dà nàn', + '逐次近似' => ' zhú cì jìn sì', + '倒戢干戈' => ' dǎo jí gān gē', + '拉枯折朽' => ' lā kū shé xiǔ', + '贫富差距' => ' pín fù chā jù', + '须眉男子' => ' xū méi nán zǐ', + '自繇自在' => ' zì yóu zì zai', + '贩夫走卒' => ' fàn fū zǒu fú', + '畜妻养子' => ' xù qī yǎng zǐ', + '似漆如胶' => ' sì qī rú jiāo', + '杜莎夫人' => ' dù suō fū ren', + '自怨自艾' => ' zì yuàn zì yì', + '四马攒蹄' => ' sì mǎ cuán tí', + '自以为是' => ' zì yǐ wéi shì', + '弟男子侄' => ' dì nán zǐ zhí', + '巴巴结结' => ' bā bā jiē jiē', + '出臭子儿' => ' chū chòu zǐ r', + '似水如鱼' => ' sì shuǐ rú yú', + '贸易逆差' => ' mào yì nì chā', + '卷席而居' => ' juàn xí ér jū', + '大兴土木' => ' dà xīng tǔ mù', + '太阿倒持' => ' tài ē dào chí', + '牢什古子' => ' láo shí gǔ zi', + '胳臂肘儿' => ' gē bei zhǒu r', + '四邻八舍' => ' sì lín bā shè', + '代客泊车' => ' dài kè bó chē', + '刻意为之' => ' kè yì wéi zhī', + '穴居野处' => ' xué jū yě chǔ', + '削木为吏' => ' xuē mù wéi lì', + '逻辑错误' => ' luó ji cuò wù', + '金吾不禁' => ' jīn wú bù jìn', + '二三君子' => ' èr sān jūn zǐ', + '寻瑕伺隙' => ' xún xiá sì xì', + '尽如人意' => ' jìn rú rén yì', + '倒霉蛋儿' => ' dǎo méi dàn r', + '竖起耳朵' => ' shù qǐ ěr duo', + '儿童歌曲' => ' ér tóng gē qǔ', + '宴安酖毒' => ' yàn ān dān dú', + '雁泊人户' => ' yàn bó rén hù', + '单鹄寡凫' => ' dān hú guǎ fú', + '淡而不厌' => ' dàn ér bù yàn', + '胆大如斗' => ' dǎn dà rú dǒu', + '典妻鬻子' => ' diǎn qī yù zǐ', + '偃革倒戈' => ' yǎn gé dǎo gē', + '箪食壶酒' => ' dān sì hú jiǔ', + '四不拗六' => ' sì bù niù liù', + '似非而是' => ' sì fēi ér shì', + '蒙帕纳斯' => ' měng pà nà sī', + '阿世取容' => ' ē shì qǔ róng', + '眼犄角儿' => ' yǎn jī jué ér', + '胳膊肘子' => ' gē bo zhǒu zǐ', + '菲食薄衣' => ' fěi shí bó yī', + '差额选举' => ' chā é xuǎn jǔ', + '胡椒薄荷' => ' hú jiāo bò he', + '阿谀谄媚' => ' ē yú chǎn mèi', + '通都大邑' => ' tōng dū dà yì', + '释迦牟尼' => ' shì jiā mù ní', + '恶湿居下' => ' wù shī jū xià', + '腐败分子' => ' fǔ bài fēn zǐ', + '佛眼佛心' => ' fó yǎn fó xīn', + '大溪豆干' => ' dà xī dòu gān', + '言无不尽' => ' yán wú bù jìn', + '了无惧色' => ' liǎo wū jǔ sè', + '四子王旗' => ' sì zǐ wáng qí', + '佛罗伦萨' => ' fó luó lún sà', + '未知数儿' => ' wèi zhī shù r', + '佛法僧目' => ' fó fǎ sēng mù', + '著作权法' => ' zhù zuò quán fǎ', + '六安地区' => ' lù ān dì qū', + '归心似箭' => ' guī xīn sì jiàn', + '各奔前程' => ' gè bèn qián chéng', + '受洗命名' => ' shòu xǐ mìng míng', + '少男少女' => ' shào nán shào nǔ:', + '论黄数黑' => ' lùn huáng shǔ hēi', + '心潮澎湃' => ' xīn cháo péng pài', + '各显所长' => ' gè xiǎn suǒ cháng', + '鸾只凤单' => ' luán zhī fèng dān', + '心电感应' => ' xīn diàn gǎn yìng', + '心灵感应' => ' xīn líng gǎn yìng', + '乱葬岗子' => ' luàn zàng gǎng zǐ', + '商业行为' => ' shāng yè xíng wéi', + '千载一圣' => ' qiān zǎi yī shèng', + '倒箧倾囊' => ' dǎo qiè qīng náng', + '攻过箴阙' => ' gōng guò zhēn què', + '其应若响' => ' qí yìng ruò xiǎng', + '高风劲节' => ' gāo fēng jìng jié', + '颠倒乾坤' => ' diān dǎo qián kūn', + '蒙古人种' => ' měng gǔ rén zhǒng', + '当着不着' => ' dāng zhuó bù zhuó', + '电子邮箱' => ' diàn zǐ yóu xiāng', + '磨盘两圆' => ' mò pán liǎng yuán', + '梦魂颠倒' => ' mèng hún diān dǎo', + '东量西折' => ' dōng liàng xī shé', + '缝纫车间' => ' féng rèn chē jiān', + '绕梁之音' => ' rǎo liáng zhī yīn', + '愁多夜长' => ' chóu duō yè cháng', + '横行直走' => ' héng xíng zhí zǒu', + '原子质量' => ' yuán zǐ zhì liàng', + '横殃飞祸' => ' hèng yāng fēi huò', + '应用数学' => ' yìng yòng shù xué', + '凤楼龙阙' => ' fèng lóu lóng què', + '人头攒动' => ' rén tóu cuán dòng', + '应运而生' => ' yìng yùn ér shēng', + '任性妄为' => ' rèn xìng wàng wéi', + '瓶沈簪折' => ' píng shěn zān shé', + '横征暴赋' => ' hèng zhēng bào fù', + '应用软体' => ' yìng yòng ruǎn tǐ', + '重纸累札' => ' chóng zhǐ lèi zhá', + '红不棱登' => ' hóng bù lēng dēng', + '老之将至' => ' lǎo zhī jiāng zhì', + '鸿鹄将至' => ' hóng hú jiāng zhì', + '强干弱枝' => ' qiáng gān ruò zhī', + '鸾漂凤泊' => ' luán piāo fèng bó', + '横抢武夺' => ' hèng qiǎng wǔ duó', + '流觞曲水' => ' liú shāng qǔ shuǐ', + '抢购一空' => ' qiāng gòu yī kōng', + '怒目相向' => ' nù mù xiāng xiàng', + '寡见鲜闻' => ' guǎ jiàn xiǎn wén', + '厝火燎原' => ' cuò huǒ liǎo yuán', + '寸有所长' => ' cùn yǒu suǒ cháng', + '寡廉鲜耻' => ' guǎ lián xiǎn chǐ', + '流水游龙' => ' liú shuǐ yóu lóng', + '只争朝夕' => ' zhǐ zhēng zhāo xī', + '六畜兴旺' => ' liù chù xīng wàng', + '更仆难终' => ' gēng pú nán zhōng', + '凌霄之志' => ' líng xiāo zhī zhì', + '电子手表' => ' diàn zǐ shǒu biǎo', + '患难之交' => ' huàn nàn zhī jiāo', + '零零散散' => ' líng líng sǎn sǎn', + '弃短就长' => ' qì duǎn jiù cháng', + '榱栋崩折' => ' cuī dòng bēng shé', + '榱崩栋折' => ' cuī bēng dòng shé', + '观者成堵' => ' guān zhě chéng dǔ', + '裂冠毁冕' => ' liè guàn huǐ miǎn', + '倒山倾海' => ' dǎo shān qīng hǎi', + '功遂身退' => ' gōng suí shēn tuì', + '前程似锦' => ' qián chéng sì jǐn', + '少说为佳' => ' shǎo shuō wéi jiā', + '满天星斗' => ' mǎn tiān xīng dǒu', + '前功尽废' => ' qián gōng jìn fèi', + '签订合同' => ' qiān dìng hé tong', + '高山反应' => ' gāo shān fǎn yìng', + '前功尽灭' => ' qián gōng jìn miè', + '更阑人静' => ' gēng lán rén jǐng', + '昧旦晨兴' => ' mèi dàn chún xīng', + '满腔热枕' => ' mǎn qiāng rè chén', + '公共管理' => ' gōng gòng guǎn lǐ', + '合众银行' => ' hé zhòng yín háng', + '潜孔钻机' => ' qián kǒng zuàn jī', + '更深夜静' => ' gēng shēn yè jìng', + '更生霉素' => ' gēng shēng méi sù', + '吉人天相' => ' jí rén tiān xiàng', + '更弦改辙' => ' gēng xián gǎi zhé', + '马瘦毛长' => ' mǎ shòu máo cháng', + '尺短寸长' => ' chǐ duǎn cù cháng', + '捋袖揎拳' => ' luō xiù xuān quán', + '弹尽粮绝' => ' dàn jìn liáng jué', + '凤泊鸾漂' => ' fèng bó luán piāo', + '光杆司令' => ' guāng gǎn sī lìng', + '好行小惠' => ' hào xíng xiǎo huì', + '传为美谈' => ' chuán wéi měi tán', + '传为佳话' => ' chuán wéi jiā huà', + '好谋善断' => ' hào móu shàn duàn', + '铺床叠被' => ' pū chuáng dié bèi', + '形单影只' => ' xíng dān yǐng zhī', + '地久天长' => ' dì jiǔ tiān cháng', + '传柄移藉' => ' chuán bǐng yí jiè', + '秋行夏令' => ' qiū xíng xià líng', + '呵欠连天' => ' hē qiàn lián tiān', + '连车平斗' => ' lián chē píng dǒu', + '酩酊烂醉' => ' mǐng dǐng làn zuì', + '鼎铛有耳' => ' dǐng chēng yǒu ěr', + '曲水流觞' => ' qǔ shuǐ liú shāng', + '处实效功' => ' chǔ shí xiào gōng', + '楞眉横眼' => ' lèng méi héng yǎn', + '累月经年' => ' lěi yuè jīng nián', + '力能扛鼎' => ' lì néng gāng dǐng', + '冥行擿埴' => ' míng xíng zhì zhí', + '好梦不长' => ' hǎo mèng bù cháng', + '刁钻促搯' => ' diāo zuàn cù chāo', + '干干净净' => ' gàn gān jìng jìng', + '伤痕累累' => ' shāng hén léi léi', + '谋臣武将' => ' móu chén wǔ jiàng', + '温衾扇枕' => ' wēn qīn shān zhěn', + '变态反应' => ' biàn tài fǎn yìng', + '直柄钻头' => ' zhí bǐng zuàn tóu', + '电子信箱' => ' diàn zǐ xìn xiāng', + '蜗杆传动' => ' wō gǎn chuán dòng', + '散兵游勇' => ' sǎn bīng yóu yǒng', + '植发穿冠' => ' zhí fà chuān guàn', + '月中折桂' => ' yuè zhōng shé guì', + '反应时间' => ' fǎn yìng shí jiān', + '传道受业' => ' chuán dào shòu yè', + '植发冲冠' => ' zhí fà chōng guàn', + '云合响应' => ' yún hé xiǎng yìng', + '文子同升' => ' wén zǐ tóng shēng', + '影只形单' => ' yǐng zhī xíng dān', + '十里长亭' => ' shí lǐ cháng tíng', + '狼艰狈蹶' => ' láng jiān bèi juě', + '行号巷哭' => ' xíng háo xiàng kū', + '敛容屏气' => ' liǎn róng bǐng qì', + '行行蛇蚓' => ' háng háng shé yǐn', + '吹拉弹唱' => ' chuī lā tán chàng', + '日短心长' => ' rì duǎn xīn cháng', + '商业银行' => ' shāng yè yín háng', + '日久天长' => ' rì jiǔ tiān cháng', + '浪子宰相' => ' làng zǐ zǎi xiàng', + '重生父母' => ' chóng shēng fù mǔ', + '东墙处子' => ' dōng qiáng chǔ zǐ', + '卷土重来' => ' juǎn tǔ chóng lái', + '老老少少' => ' lǎo lǎo shào shào', + '弹性模量' => ' tán xìng mó liàng', + '重明继焰' => ' chóng míng jì yàn', + '充类至尽' => ' chōng lèi zhì jìn', + '垂死挣扎' => ' chuí sǐ zhēng zhá', + '重金袭汤' => ' chóng jīn xí tāng', + '重金兼紫' => ' chóng jīn jiān zǐ', + '冲击钻头' => ' chōng jī zuàn tóu', + '戎马倥偬' => ' róng mǎ kǒng zǒng', + '后生小子' => ' hòu shēng xiǎo zǐ', + '喟然长叹' => ' kuì rán cháng tàn', + '喉长气短' => ' hóu cháng qì duǎn', + '却老还童' => ' què lǎo huán tóng', + '重手累足' => ' chóng shǒu lěi zú', + '横恩滥赏' => ' hèng ēn làn shǎng', + '吹花嚼蕊' => ' chuī huā jiáo ruǐ', + '改行为善' => ' gǎi xíng wéi shàn', + '扛鼎拔山' => ' gāng dǐng bá shān', + '了身达命' => ' liǎo shēn dá mìng', + '了然于胸' => ' liǎo rán yú xiōng', + '感应电流' => ' gǎn yìng diàn liú', + '了了可见' => ' liǎo liǎo kě jiàn', + '桂子飘香' => ' guì zǐ piāo xiāng', + '撩蜂吃螫' => ' liáo fēng chī shì', + '干霄凌云' => ' gān xiāo líng yún', + '了不长进' => ' liǎo bù zhǎng jǐn', + '擎天之柱' => ' qíng tiān zhī zhù', + '琼楼金阙' => ' qióng lóu jīn què', + '两世为人' => ' liǎng shì wéi rén', + '国之干城' => ' guó zhī gān chéng', + '电子元件' => ' diàn zǐ yuán jiàn', + '影像处理' => ' yǐng xiàng chǔ lǐ', + '海相沉积' => ' hǎi xiàng chén jī', + '穷途潦倒' => ' qióng tú liáo dǎo', + '反败为胜' => ' fǎn bài wéi shèng', + '穷理尽性' => ' qióng lǐ jìn xìng', + '电子天平' => ' diàn zǐ tiān píng', + '受降仪式' => ' shòu xiáng yí shì', + '一箭上垛' => ' yī jiàn shàng duò', + '喙长三尺' => ' huì cháng sān chǐ', + '黄干黑廋' => ' huáng gān hēi sōu', + '黄冠草服' => ' huáng guàn cǎo fú', + '黄冠草履' => ' huáng guàn cǎo lǚ', + '栋折榱坏' => ' dòng shé cuī huài', + '黄雀伺蝉' => ' huáng què sì chán', + '骈兴错出' => ' pián xīng cuò chū', + '戒奢宁俭' => ' jiè shē nìng jiān', + '空腹便便' => ' kōng fù pián pián', + '借尸还阳' => ' jiè shī huán yáng', + '还元返本' => ' huán yuán fǎn běn', + '盛水不漏' => ' chéng shuǐ bù lòu', + '长念却虑' => ' cháng niàn què lǜ', + '分分秒秒' => ' fèn fēn miǎo miǎo', + '长篇大论' => ' cháng piān dà lùn', + '翩翩公子' => ' piān piān gōng zǐ', + '毁钟为铎' => ' huǐ zhōng wéi duó', + '镌脾琢肾' => ' juān pí zhuó shèn', + '看清形势' => ' kān qīng xíng shì', + '溘然长往' => ' kè rán cháng wǎng', + '斗量筲计' => ' dǒu liáng shāo jì', + '诘戎治兵' => ' jié róng zhì bīng', + '强人所难' => ' qiǎng rén suǒ nán', + '深切著明' => ' shēn qiè zhù míng', + '天长日久' => ' tiān cháng rì jiǔ', + '江翻海倒' => ' jiāng fān hǎi dǎo', + '静影沉璧' => ' jing ying chen bi', + '参商之虞' => ' shēn shāng zhī yú', + '经验教训' => ' jīng yàn jiào xun', + '天人感应' => ' tiān rén gǎn yìng', + '径行直遂' => ' jìng xíng zhí suí', + '长绳系日' => ' cháng shéng jì rì', + '千载难逢' => ' qiān zǎi nán féng', + '鸟面鹄形' => ' niǎo miàn hú xíng', + '身无长处' => ' shēn wú cháng chù', + '返本还源' => ' fǎn běn huán yuán', + '短叹长吁' => ' duǎn tàn cháng xū', + '声应气求' => ' shēng yìng qì qiú', + '旁引曲证' => ' páng yǐn qǔ zhèng', + '度长絜短' => ' dù cháng xié duǎn', + '沉谋重虑' => ' chén móu chóng lǜ', + '魂颠梦倒' => ' hún diān mèng dǎo', + '充分利用' => ' chōng fèn lì yòng', + '江淹才尽' => ' jiāng yān cái jìn', + '乘肥衣轻' => ' chéng féi yì qīng', + '斗方名士' => ' dǒu fāng míng shì', + '绝长继短' => ' jué cháng jì duǎn', + '倔头强脑' => ' juè tóu jiàng nǎo', + '撅坑撅堑' => ' jué kēng jué qiàn', + '截长补短' => ' jié cháng bǔ duǎn', + '藉草枕块' => ' jiè cǎo zhěn kuài', + '教猱升木' => ' jiāo náo shēng mù', + '称德度功' => ' chēng dé duó gōng', + '成本会计' => ' chéng běn kuài jì', + '绝长补短' => ' jué cháng bǔ duǎn', + '绝长续短' => ' jué cháng xù duǎn', + '山鸣谷应' => ' shān míng gǔ yìng', + '截铁斩钉' => ' jié tiě zhǎn dìng', + '舍短取长' => ' shě duǎn qǔ cháng', + '乘利席胜' => ' chéng lì xí shèng', + '长篇大套' => ' cháng piān dà tào', + '活剥生吞' => ' huó bāo shēng tūn', + '干扁豆角' => ' gān biǎn dòu jiǎo', + '昏镜重磨' => ' hūn jìng chóng mó', + '山行海宿' => ' shān xíng hǎi xiǔ', + '框架结构' => ' kuàng jià jié gòu', + '撒豆成兵' => ' sǎ dòu chéng bīng', + '狂风怒号' => ' kuáng fēng nù háo', + '冲冠眦裂' => ' chōng guàn zì liè', + '鹄形鸟面' => ' hú xíng niǎo miàn', + '鹄峙鸾翔' => ' hú zhì luán xiáng', + '竭诚尽节' => ' jié chéng jìn jié', + '东山之志' => ' dōng shān zhī zhì', + '若崩厥角' => ' ruò bēng jué jiǎo', + '充分发展' => ' chōng fèn fā zhǎn', + '房贷银行' => ' fáng dài yín háng', + '兜肚连肠' => ' dōu dǔ lián cháng', + '重关击柝' => ' chóng guān jī tuò', + '还年却老' => ' huán nián què lǎo', + '什锦炒面' => ' shí jǐn chǎo miàn', + '三差两错' => ' sān chā liǎng cuò', + '重复使用' => ' chóng fù shǐ yòng', + '三长四短' => ' sān cháng sì duǎn', + '坏裳为裤' => ' huài shang wéi kù', + '杀出重围' => ' shā chū chóng wéi', + '盘水加剑' => ' pan shui jia jian', + '静电干扰' => ' jìng diàn gān rǎo', + '长谈阔论' => ' cháng tán kuò lùn', + '广寒仙子' => ' guǎng hán xiān zǐ', + '长途车站' => ' cháng tú chē zhàn', + '大人先生' => ' dà rén xiān sheng', + '掂斤抹两' => ' diān jīn mò liǎng', + '户限为穿' => ' hù xiàn wéi chuān', + '高原反应' => ' gāo yuán fǎn yìng', + '长途飞行' => ' cháng tú fēi xíng', + '神哗鬼叫' => ' shén huá guǐ jiào', + '家道从容' => ' jiā dào cōng róng', + '尽诚竭节' => ' jìn chéng jié jié', + '承销利差' => ' chéng xiāo lì chā', + '颠乾倒坤' => ' diān qiān dǎo kūn', + '甲冠天下' => ' jiǎ guàn tiān xià', + '好景不长' => ' hǎo jǐng bù cháng', + '挟势弄权' => ' jiā shì nòng quán', + '奉子成婚' => ' fèng zǐ chéng hūn', + '家长里短' => ' jiā cháng lǐ duǎn', + '津关险塞' => ' jīn guān xiǎn sài', + '挟主行令' => ' jiā zhǔ xíng lìng', + '斗筲穿窬' => ' dǒu shāo chuān yú', + '龙楼凤阙' => ' lóng lóu fèng què', + '龙兴凤举' => ' lóng xīng fèng jǔ', + '间接经验' => ' jiàn jiē jīng yàn', + '颠倒衣裳' => ' diān dǎo yī cháng', + '啜菽饮水' => ' chuò shū yǐn shuǐ', + '倾肠倒肚' => ' qīng cháng dào dǔ', + '悄然无声' => ' qiǎo rán wú shēng', + '掂梢折本' => ' diān shāo shé běn', + '共为唇齿' => ' gòng wéi chún chǐ', + '鸾凤和鸣' => ' luán fèng hè míng', + '强食靡角' => ' qiǎng shí mí jiǎo', + '当务始终' => ' dang wu shi zhong', + '颠倒阴阳' => ' diān dǎo yīn yáng', + '弹冠结绶' => ' tán guān jié shòu', + '颠颠倒倒' => ' diān diān dǎo dǎo', + '顾虑重重' => ' gù lǜ chóng chóng', + '党豺为虐' => ' dǎng chái wéi nüè', + '梦撒撩丁' => ' mèng sā liáo dīng', + '猛将如云' => ' měng jiàng rú yún', + '强作解人' => ' qiǎng zuò jiě rén', + '电力供应' => ' diàn lì gōng yìng', + '股票行情' => ' gǔ piào háng qíng', + '龙兴云属' => ' lóng xīng yún shǔ', + '旧雨重逢' => ' jiù yǔ chóng féng', + '年轻有为' => ' nián qīng yǒu wéi', + '能不称官' => ' néng bù chèn guān', + '发上指冠' => ' fà shàng zhǐ guān', + '翻黄倒皂' => ' fān huáng dǎo zào', + '锦囊还矢' => ' jǐn náng huán shǐ', + '发植穿冠' => ' fà zhí chuān guān', + '内部调整' => ' nèi bù tiáo zhěng', + '动态更新' => ' dòng tài gēng xīn', + '强嘴拗舌' => ' jiàng zuǐ niù shé', + '盛必虑衰' => ' shèng bì lǜ shuāi', + '捻土为香' => ' niǎn tǔ wéi xiāng', + '动词重叠' => ' dòng cí chóng dié', + '长枕大被' => ' cháng zhěn dà bèi', + '遁迹黄冠' => ' dùn jì huáng guàn', + '强嘴硬牙' => ' jiàng zuǐ yìng yá', + '顿足椎胸' => ' dùn zú zhuī xiōng', + '生理反应' => ' shēng lǐ fǎn yìng', + '声求气应' => ' shēng qiú qì yìng', + '天长地久' => ' tiān cháng dì jiǔ', + '声势显赫' => ' shēng shì xiǎn hè', + '发短心长' => ' fà duǎn xīn cháng', + '侔色揣称' => ' móu sè chuǎi chèn', + '参辰卯酉' => ' shēn chén mǎo yǒu', + '强迫劳动' => ' qiǎng pò láo dòng', + '车削加工' => ' chē xiāo jiā gōng', + '袅袅娜娜' => ' niǎo niǎo nuó nuó', + '皮相之见' => ' pí xiàng zhī jiàn', + '化学成分' => ' huà xué chéng fèn', + '命名系统' => ' mìng míng xì tǒng', + '竭忠尽智' => ' jié zhōng jìn zhì', + '居轴处中' => ' jū zhóu chǔ zhōng', + '焦唇干肺' => ' jiāo chún gān fèi', + '急景凋年' => ' jí yǐng diāo nián', + '蛇鼠横行' => ' shé shǔ héng xíng', + '非分之想' => ' fēi fèn zhī xiǎng', + '卷甲衔枚' => ' juàn jiǎ xián méi', + '卷甲束兵' => ' juàn jiǎ shù bīng', + '吵吵闹闹' => ' chāo chao nào nào', + '长枕大衾' => ' cháng zhěn dà qīn', + '极限量规' => ' jí xiàn liáng guī', + '汇丰银行' => ' huì fēng yín háng', + '舌咽神经' => ' shé yān shén jīng', + '竭智尽忠' => ' jié zhì jìn zhōng', + '长斋绣佛' => ' cháng zhāi xiù fó', + '电子空间' => ' diàn zǐ kōng jiān', + '翻江倒海' => ' fān jiāng dǎo hǎi', + '长篇累牍' => ' cháng piān lěi dú', + '率土宅心' => ' shuài tǔ zhái xīn', + '盈千累万' => ' yíng qiān lěi wàn', + '缠绵蕴藉' => ' chán mián yùn jiè', + '搀行夺市' => ' chān háng duó shì', + '层见错出' => ' céng xiàn cuò chū', + '勇冠三军' => ' yǒng guàn sān jūn', + '沧浪老人' => ' cāng láng lǎo rén', + '竖子成名' => ' shù zǐ chéng míng', + '应变无方' => ' yìng biàn wú fāng', + '残军败将' => ' cán jūn bài jiàng', + '钻天觅缝' => ' zuàn tiān mì féng', + '束椽为柱' => ' shù chuán wéi zhù', + '天长地老' => ' tiān cháng dì lǎo', + '泊船瓜洲' => ' bó chuán guā zhōu', + '苦难深重' => ' kǔ nàn shēn zhòng', + '银行存款' => ' yín háng cún kuǎn', + '朝鲜半岛' => ' cháo xiǎn bàn dǎo', + '转嗔为喜' => ' zhuǎn chēn wéi xǐ', + '陂湖禀量' => ' bēi hú bǐng liáng', + '长才短驭' => ' cháng cái duǎn yù', + '数白论黄' => ' shǔ bái lùn huáng', + '有三有俩' => ' yǒu sān yǒu liǎng', + '用其所长' => ' yòng qí suǒ cháng', + '友好相处' => ' yǒu hǎo xiāng chǔ', + '二重下标' => ' èr chóng xià biāo', + '深源地震' => ' shēn yuán dì zhèn', + '薪尽火传' => ' xīn jìn huǒ chuán', + '饮水啜菽' => ' yǐn shuǐ chuò shū', + '钻洞觅缝' => ' zuàn dòng mì féng', + '蓝田种玉' => ' lán tián zhòng yù', + '双柑斗酒' => ' shuài gān dǒu jiǔ', + '油炸圈饼' => ' yóu zhá quān bǐng', + '长材茂学' => ' cháng cái mào xué', + '长驾远驭' => ' cháng jià yuǎn yù', + '应用化学' => ' yìng yòng huà xué', + '万古长青' => ' wàn gǔ cháng qīng', + '洋场恶少' => ' yáng chǎng è shào', + '万应灵丹' => ' wàn yìng líng dān', + '应时对景' => ' yìng shí duì jǐng', + '长春不老' => ' cháng chún bù lǎo', + '收拾收拾' => ' shōu shi shōu shi', + '烧香拜佛' => ' shāo xiāng bài fó', + '切片检查' => ' qiē piàn jiǎn chá', + '拳头产品' => ' quán tou chǎn pǐn', + '银行贷款' => ' yín háng dài kuǎn', + '挑灯夜战' => ' tiǎo dēng yè zhàn', + '水刑逼供' => ' shuǐ xíng bī gòng', + '朝鲜海峡' => ' cháo xiǎn hǎi xiá', + '温室效应' => ' wēn shì xiào yìng', + '弹空说嘴' => ' tán kōng shuō zuǐ', + '兵不由将' => ' bīng bù yóu jiàng', + '人种差别' => ' rén zhǒng chā bié', + '银行汇票' => ' yín háng huì piào', + '转辗反侧' => ' zhuǎn zhǎn fǎn cè', + '挑战者号' => ' tiǎo zhàn zhě hào', + '民穷财尽' => ' mín qióng cái jìn', + '摽梅之年' => ' biào mén zhī nián', + '火烧火燎' => ' huǒ shāo huǒ liǎo', + '弹冠振衿' => ' tán guān zhèn jīn', + '辩证逻辑' => ' biàn zhèng luó ji', + '炎性反应' => ' yán xìng fǎn yìng', + '损军折将' => ' sǔn jūn zhé jiàng', + '膀胱气化' => ' páng guāng qì huà', + '屏气凝神' => ' bǐng qì níng shén', + '摒弃前嫌' => ' bìng qì qián xián', + '谈言微中' => ' tán yán wēi zhòng', + '白衣卿相' => ' bái yī qīng xiàng', + '事后聪明' => ' shì hòu cōng ming', + '转愁为喜' => ' zhuǎn chóu wéi xǐ', + '杠杆收购' => ' gàng gǎn shōu gòu', + '著作等身' => ' zhù zuò děng shēn', + '逾墙钻穴' => ' yú qiáng zuàn xué', + '歪打正着' => ' wāi dǎ zhèng zháo', + '蟏蛸满室' => ' xiāo shāo mǎn shì', + '特种警察' => ' tè zhòng jǐng chá', + '杼柚之空' => ' zhù zhóu zhī kōng', + '褎然冠首' => ' yòu rán guàn shǒu', + '白手兴家' => ' bái shǒu xīng jiā', + '白首为郎' => ' bái shǒu wéi láng', + '百兽率舞' => ' bǎi shòu shuài wǔ', + '伴食宰相' => ' bàn shí zǎi xiàng', + '松松散散' => ' sōng sōng sǎn sǎn', + '鼻孔撩天' => ' bí kǒng liáo tiān', + '人行横道' => ' rén xíng héng dào', + '水浒后传' => ' shuǐ hǔ hòu zhuàn', + '薮中荆曲' => ' sǒu zhōng jīng qǔ', + '不遑宁处' => ' bù huáng níng chǔ', + '不良反应' => ' bù liáng fǎn yìng', + '长夜难明' => ' cháng yè nán míng', + '鬼使神差' => ' guǐ shǐ shén chāi', + '调频电台' => ' tiáo pín diàn tái', + '诱掖奖劝' => ' yòu yè jiǎng quàn', + '行侠仗义' => ' xìng xiá zhàng yì', + '挑牙料唇' => ' tiǎo yá liào chún', + '拔山扛鼎' => ' bá shān gāng dǐng', + '龙生九子' => ' lóng shēng jiǔ zǐ', + '铢两悉称' => ' zhū liǎng xī chèn', + '鱼与熊掌' => ' yú yǔ xióng zhǎng', + '兴妖作怪' => ' xīng yāo zuò guài', + '朝阳产业' => ' zhāo yáng chǎn yè', + '兴奋高潮' => ' xīng fèn gāo cháo', + '高高兴兴' => ' gāo gāo xìng xīng', + '公诸同好' => ' gōng zhū tóng hào', + '终焉之志' => ' zhōng yān zhī zhì', + '高空弹跳' => ' gāo kōng tán tiào', + '外公切线' => ' wài gōng qiē xiàn', + '众怨之的' => ' zhòng yuàn zhī dì', + '臭味相投' => ' xiù wèi xiāng tóu', + '插不上手' => ' chā bu shàng shǒu', + '白发千丈' => ' bái fà qiān zhàng', + '意味深长' => ' yì wèi shēn cháng', + '调养身体' => ' tiáo yǎng shēn tǐ', + '一字长城' => ' yī zì cháng chéng', + '牵羊担酒' => ' qiān yáng dàn jiǔ', + '败军之将' => ' bài jūn zhī jiàng', + '调朱弄粉' => ' tiáo zhū nòng fěn', + '切中要害' => ' qiē zhōng yào hài', + '调脂弄粉' => ' tiáo zhī nòng fěn', + '卫满朝鲜' => ' wèi mǎn cháo xiǎn', + '援助之手' => ' yuán zhù zhī shǒu', + '暗箭中人' => ' àn jiàn zhòng rén', + '了如指掌' => ' liǎo rú zhǐ zhǎng', + '调风变俗' => ' tiáo fēng biàn sú', + '著述等身' => ' zhù shù děng shēn', + '天下为公' => ' tiān xià wéi gōng', + '主要成分' => ' zhǔ yào chéng fèn', + '卫氏朝鲜' => ' wèi shì cháo xiǎn', + '狼狈为奸' => ' láng bèi wéi jiān', + '了却此生' => ' liǎo què cǐ shēng', + '天下为笼' => ' tiān xià wéi lóng', + '筑坛拜将' => ' zhù tán bài jiàng', + '自奉甚俭' => ' zì fèng shèn jiǎn', + '中间部分' => ' zhōng jiān bù fen', + '招降纳叛' => ' zhāo xiáng nà pàn', + '钻山塞海' => ' zuàn shān sāi hǎi', + '当世之冠' => ' dāng shì zhī guàn', + '黄埔军校' => ' huáng pǔ jūn xiào', + '麟角凤觜' => ' lín jiǎo fèng zuǐ', + '衣锦还乡' => ' yì jǐn huán xiāng', + '天道好还' => ' tiān dào hǎo huán', + '占风望气' => ' zhān fēng wàng qì', + '挂在嘴上' => ' guà zai zuǐ shang', + '水果罐头' => ' shuǐ guǒ guàn tóu', + '裁长补短' => ' cái cháng bǔ duǎn', + '黑汗王朝' => ' hēi hán wáng cháo', + '钻坚研微' => ' zuàn jiān yán wēi', + '不胜杯杓' => ' bù shèng bēi sháo', + '水宿风餐' => ' shuǐ xiǔ fēng cān', + '沉没成本' => ' chén mò chéng běn', + '解发佯狂' => ' jiě fà yáng kuáng', + '着着失败' => ' zhuó zhuó shī bài', + '不甚了了' => ' bù shèn liǎo liǎo', + '招揽生意' => ' zhāo lǎn shēng yi', + '止咳糖浆' => ' zhǐ ké táng jiāng', + '痛自创艾' => ' tòng zì chuāng yì', + '公共关系' => ' gōng gòng guān xì', + '令人兴奋' => ' lìng rén xīng fèn', + '公共开支' => ' gōng gòng kāi zhī', + '见闻有限' => ' jiàn wén yǒu xiàn', + '重铬酸钾' => ' chóng gè suān jiǎ', + '公共设施' => ' gōng gòng shè shī', + '逾墙钻蠙' => ' yú qiáng zuàn pín', + '高村正彦' => ' gāo cūn zhēng yàn', + '调嘴调舌' => ' tiáo zuǐ diào shé', + '鸾飘凤泊' => ' luán piāo fèng bó', + '调嘴弄舌' => ' tiáo zuǐ nòng shé', + '鼎铛玉石' => ' dǐng chēng yù shí', + '铁板钉钉' => ' tiě bǎn dìng dīng', + '安常守分' => ' ān cháng shǒu fèn', + '恫瘝在抱' => ' tōng guān zài bào', + '安常处顺' => ' ān cháng chǔ shùn', + '铢量寸度' => ' zhū liáng cùn duó', + '矮人观场' => ' ǎi rén guān cháng', + '同恶相助' => ' tóng wù xiāng zhù', + '下旋削球' => ' xià xuán xiāo qíu', + '天地长久' => ' tiān dì cháng jiǔ', + '公共团体' => ' gōng gòng tuán tǐ', + '识明智审' => ' shí míng zhì shěn', + '惟肖惟妙' => ' wéi xiāo wéi miào', + '翻箱倒箧' => ' fān xiāng dǎo qiè', + '智穷才尽' => ' zhì qióng cái jìn', + '真相大白' => ' zhēn xiàng dà bái', + '眼不转睛' => ' yǎn bù zhuàn jīng', + '月落参横' => ' yuè luò shēn héng', + '质量要求' => ' zhì liàng yāo qiú', + '支链反应' => ' zhī liàn fǎn yìng', + '止咳平喘' => ' zhǐ ké píng chuǎn', + '先王之乐' => ' xiān wáng zhī yuè', + '聪明过头' => ' cōng ming guò tóu', + '月经失调' => ' yuè jīng shī tiáo', + '音乐光碟' => ' yīn yuè guāng dié', + '腰椎间盘' => ' yāo zhuī jiān pán', + '雨顺风调' => ' yǔ shùn fēng tiáo', + '刓方为圆' => ' shū fāng wéi yuán', + '万古长春' => ' wàn gǔ cháng chūn', + '尽释前嫌' => ' jìn shì qián xián', + '万箭攒心' => ' wàn jiàn cuán xīn', + '风调雨顺' => ' fēng tiáo yǔ shùn', + '凶相毕露' => ' xiōng xiàng bì lù', + '衣轻乘肥' => ' yì qīng chéng féi', + '占风使帆' => ' zhān fēng shǐ fān', + '相依为命' => ' xiāng yī wéi mìng', + '宿水餐风' => ' xiǔ shuǐ cān fēng', + '红泥月亮' => ' hóng ní yuè liang', + '郢书燕说' => ' yǐng shū yān shuō', + '兴妖作乱' => ' xīng yāo zuò luàn', + '饶舌调唇' => ' ráo shé tiáo chún', + '数黄道黑' => ' shu huang dao hei', + '云集响应' => ' yún jí xiǎng yìng', + '置水之情' => ' zhì shuǐ zhī qíng', + '相向突击' => ' xiāng xiàng tū jī', + '青年旅舍' => ' qīng nián lǔ: shè', + '委曲成全' => ' wěi qǔ chéng quán', + '为人正直' => ' wéi rén zhèng zhí', + '光阴似箭' => ' guāng yīn sì jiàn', + '相对位置' => ' xiāng duì wèi zhi', + '语境效应' => ' yǔ jìng xiào yìng', + '鸢肩鹄颈' => ' yuān jiān hú jǐng', + '青藏公路' => ' qīng zàng gōng lù', + '直接了当' => ' zhí jiē liǎo dàng', + '万头攒动' => ' wàn tóu cuán dòng', + '两肋插刀' => ' liǎng lèi chā dāo', + '形只影单' => ' xíng zhī yǐng dān', + '一长半短' => ' yī cháng bàn duǎn', + '间谍活动' => ' jiàn dié huó dòng', + '神差鬼使' => ' shén chāi guǐ shǐ', + '真命天子' => ' zhēn mìng tiān zǐ', + '神魂颠倒' => ' shén hún diān dǎo', + '位置效应' => ' wèi zhì xiào yìng', + '真龙天子' => ' zhēn lóng tiān zǐ', + '间接证据' => ' jiàn jiē zhèng jù', + '贞观之治' => ' zhēn guān zhī zhì', + '折长补短' => ' zhé cháng bǔ duǎn', + '正冠李下' => ' zhèng guàn lǐ xià', + '长途电话' => ' cháng tú diàn huà', + '间歇训练' => ' jiàn xiē xùn liàn', + '充分考虑' => ' chōng fèn kǎo lù:', + '超敏反应' => ' chāo mǐn fǎn yìng', + '似曾相识' => ' sì céng xiāng shí', + '翻箱倒柜' => ' fān xiāng dǎo guì', + '调研人员' => ' tiáo yán rén yuán', + '伴随效应' => ' bàn suí xiào yìng', + '车载斗量' => ' chē zài dǒu liáng', + '蒸沙为饭' => ' zhēng shā wéi fàn', + '音乐之声' => ' yīn yuè zhī shēng', + '访问方式' => ' fǎng wèn fāng shì', + '两性差距' => ' liǎng xìng chā jù', + '直截了当' => ' zhí jié liǎo dàng', + '枉口嚼舌' => ' wǎng kǒu jiáo shé', + '头晕脑涨' => ' tóu yūn nǎo zhàng', + '汪洋闳肆' => ' wāng yáng hóng sì', + '望子成才' => ' wàng zǐ chéng cái', + '头昏脑涨' => ' tóu hūn nǎo zhàng', + '预防接种' => ' yù fáng jiē zhòng', + '直捷了当' => ' zhí jié liǎo dàng', + '正式合同' => ' zhèng shì hé tong', + '两侧对称' => ' liǎng cè duì chèn', + '数见不鲜' => ' shuò jiàn bù xiān', + '置换反应' => ' zhì huàn fǎn yìng', + '调和振动' => ' tiáo hé zhèn dòng', + '似鸟恐龙' => ' sì niǎo kǒng lóng', + '应用系统' => ' yìng yòng xì tǒng', + '五陵年少' => ' wǔ líng nián shào', + '执鞭随蹬' => ' zhí biān suí dèng', + '阿甘正传' => ' a gān zhèng zhuàn', + '战争贩子' => ' zhàn zhēng fàn zǐ', + '行政干预' => ' xíng zhèng gān yù', + '遥相呼应' => ' yáo xiāng hū yìng', + '朝乾夕愓' => ' zhāo qián xī dàng', + '长辔远驭' => ' cháng pèi yuǎn yù', + '更有甚者' => ' gèng yǒu shèn zhě', + '长袖善舞' => ' cháng xiù shàn wǔ', + '科学种田' => ' kē xué zhòng tián', + '一鞭先著' => ' yī biān xiān zhuó', + '长生不死' => ' cháng shēng bū sǐ', + '辗转反侧' => ' zhǎn zhuǎn fǎn cè', + '斗转星移' => ' dǒu zhuǎn xīng yí', + '夜静更深' => ' yè jìng gēng shēn', + '倒海翻江' => ' dǎo hǎi fān jiāng', + '朝夕相处' => ' zhāo xī xiāng chǔ', + '长乐公主' => ' cháng lè gōng zhǔ', + '先我着鞭' => ' xiān wǒ zhuó biān', + '闲言长语' => ' xián yán cháng yǔ', + '长期饭票' => ' cháng qī fàn piào', + '长期共存' => ' cháng qī gòng cún', + '更新换代' => ' gēng xīn huàn dài', + '相时而动' => ' xiàng shí ér dòng', + '数黑论黄' => ' shǔ hēi lùn huáng', + '羊肠九曲' => ' yáng cháng jiǔ qǔ', + '长乐未央' => ' cháng lè wèi yāng', + '心长发短' => ' xīn cháng fà duǎn', + '摇头晃脑' => ' yáo tóu huàng nǎo', + '小子后生' => ' xiǎo zǐ hòu shēng', + '长命富贵' => ' cháng mìng fù guì', + '长吁短叹' => ' cháng xū duǎn tàn', + '朝梁暮晋' => ' zhāo liáng mù jìn', + '转浑天仪' => ' zhuàn hún tiān yí', + '中箭落马' => ' zhòng jiàn luò mǎ', + '转弯抹角' => ' zhuǎn wān mò jiǎo', + '管弦乐队' => ' guǎn xián yuè duì', + '心长力短' => ' xīn cháng lì duǎn', + '铁血宰相' => ' tiě xuè zǎi xiàng', + '朝穿暮塞' => ' zhāo chuān mù sāi', + '遥呼相应' => ' yáo hū xiāng yìng', + '咬钉嚼铁' => ' yǎo dīng jiáo tiě', + '朝成夕毁' => ' zhāo chéng xī huǐ', + '镜框舞台' => ' jìng kuàng wǔ tái', + '返老还童' => ' fǎn lǎo huán tóng', + '链式反应' => ' liàn shì fǎn yìng', + '铺张浪费' => ' pū zhāng làng fèi', + '夜长梦多' => ' yè cháng mèng duō', + '兆载永劫' => ' zhào zǎi yǒng jié', + '燕石妄珍' => ' yān shí wàng zhēn', + '保角对应' => ' bǎo jiǎo duì yìng', + '重文轻武' => ' chóng wén qīng wǔ', + '退耕还林' => ' tuì gēng huán lín', + '曾参杀人' => ' zēng shēn shā rén', + '悬梁自尽' => ' xuán liáng zì jìn', + '朝成暮毁' => ' zhāo chéng mù huǐ', + '逐行扫描' => ' zhú háng sǎo miáo', + '金融杠杆' => ' jīn róng gàng gǎn', + '金帐汗国' => ' jīn zhàng hán guó', + '连锁反应' => ' lián suǒ fǎn yìng', + '酒精中毒' => ' jǐu jīng zhòng dú', + '精尽人亡' => ' jīng jìn rén wáng', + '游手好闲' => ' yóu shǒu hào xián', + '言为心声' => ' yán wéi xīn shēng', + '揎拳捋袖' => ' xuān quán luō xiù', + '网路应用' => ' wǎng lù yìng yòng', + '星移斗转' => ' xīng yí dǒu zhuǎn', + '侵权行为' => ' qīn quán xíng wéi', + '行政处罚' => ' xíng zhèng chǔ fá', + '斩将夺旗' => ' zhǎn jiàng duó qí', + '斩钉切铁' => ' zhǎn dīng qiē tiě', + '返还占有' => ' fǎn huán zhàn yǒu', + '压电效应' => ' yā diàn xiào yìng', + '金蝉脱壳' => ' jīn chán tuō qiào', + '轩轩甚得' => ' xuān xuān shèn dé', + '延颈跂踵' => ' yán jǐng qǐ zhǒng', + '来日方长' => ' lái rì fāng cháng', + '重新开始' => ' chóng xīn kāi shǐ', + '重新造林' => ' chóng xīn zào lín', + '使徒行传' => ' shǐ tú xíng zhuàn', + '重新统一' => ' chóng xīn tǒng yī', + '重新启动' => ' chóng xīn qǐ dòng', + '重修旧好' => ' chóng xiū jiù hǎo', + '压良为贱' => ' yā liáng wéi jiàn', + '璇霄丹阙' => ' xuán xiāo dān què', + '重新做人' => ' chóng xīn zuò rén', + '重整旗鼓' => ' chóng zhěng qí gǔ', + '经年累月' => ' jīng nián lěi yuè', + '乍暖还寒' => ' zhà nuǎn huán hán', + '细水长流' => ' xì shuǐ cháng liú', + '乡村音乐' => ' xiāng cūn yīn yuè', + '重庆大学' => ' chóng qìng dà xué', + '豁然贯通' => ' huò rán guàn tōng', + '斗量车载' => ' dǒu liáng chē zài', + '经济力量' => ' jīng jì lì liang', + '长绳系景' => ' cháng shéng xì jǐng', + '短中取长' => ' duǎn zhōng qǔ cháng', + '话长说短' => ' huà cháng shuō duǎn', + '生拉硬拽' => ' shēng lā yìng zhuài', + '深中肯綮' => ' shēn zhōng kěn qìng', + '坑绷拐骗' => ' kēng bēng guǎi piàn', + '质量效应' => ' zhì liàng xiào yìng', + '众星攒月' => ' zhòng xīng cuán yuè', + '冲动行为' => ' chōng dòng xíng wéi', + '神霄绛阙' => ' shén xiāo jiàng què', + '自坏长城' => ' zì huài cháng chéng', + '兢兢乾乾' => ' jīng jīng qián qián', + '外强中干' => ' wài qiáng zhōng gān', + '逞性妄为' => ' chěng xìng wàng wéi', + '参横斗转' => ' shēn héng dǒu zhuǎn', + '双重国籍' => ' shuāng chóng guó jí', + '双重人格' => ' shuāng chóng rén gé', + '成为笑柄' => ' chéng wéi xiào bǐng', + '转败为成' => ' zhuǎn bài wéi chéng', + '创巨痛深' => ' chuāng jù tòng shēn', + '纸短情长' => ' zhǐ duǎn qíng cháng', + '乘胜逐北' => ' chéng shèng zhú běi', + '下风方向' => ' xià fēng fāng xiàng', + '水穷山尽' => ' shuǐ qióng shān jìn', + '说短论长' => ' shuō duǎn lùn cháng', + '率由旧章' => ' shuài yóu jiù zhāng', + '少成若性' => ' shào chéng ruò xìng', + '长往远引' => ' cháng wǎng yuǎn yǐn', + '长线产品' => ' cháng xiàn chǎn pǐn', + '持枪抢劫' => ' chí qiāng qiāng jié', + '正中要害' => ' zhèng zhòng yào hài', + '沉着痛快' => ' chén zhuó tòng kuài', + '产生偏差' => ' chǎn shēng piān chā', + '众啄同音' => ' zhòng zhòu tóng yīn', + '侜张为幻' => ' zhōu zhāng wéi huàn', + '铢称寸量' => ' zhū chēng cùn liáng', + '鞭长驾远' => ' biān cháng jià yuǎn', + '重光累洽' => ' chóng guāng lèi qià', + '水尽山穷' => ' shuǐ jìn shān qióng', + '将门有将' => ' jiàng mén yǒu jiàng', + '源远流长' => ' yuán yuǎn liú cháng', + '霜行草宿' => ' shuāng xíng cǎo xiǔ', + '拽巷啰街' => ' zhuài xiàng luó jiē', + '正当防卫' => ' zhèng dàng fáng wèi', + '强颜欢笑' => ' qiǎng yán huān xiào', + '恒生银行' => ' héng shēng yín háng', + '说长道短' => ' shuō cháng dào duǎn', + '对证命名' => ' duì zhèng mìng míng', + '心手相应' => ' xīn shǒu xiāng yìng', + '旋转乾坤' => ' xuán zhuǎn qián kūn', + '兴师动众' => ' xīng shī dòng zhòng', + '双栖双宿' => ' shuāng qī shuāng sù', + '板上钉钉' => ' bǎn shàng dìng dīng', + '当轴处中' => ' dāng zhóu chǔ zhōng', + '官方网站' => ' guān fāng wǎng zhàn', + '史传小说' => ' shǐ zhuàn xiǎo shuō', + '说岳全传' => ' shuō yuè quán zhuàn', + '寸善片长' => ' cùn shàn piàn cháng', + '行险侥幸' => ' xíng xiǎn jiǎo xìng', + '量身定制' => ' liáng shēn dìng zhì', + '明窗净几' => ' míng chuāng jìng jī', + '长山山脉' => ' cháng shān shān mài', + '姓甚名谁' => ' xìng shèn míng shuí', + '龙荒蛮甸' => ' lóng huāng mán diàn', + '旧景重现' => ' jìu jǐng chóng xiàn', + '锥处囊中' => ' zhuī chǔ náng zhōng', + '草率收兵' => ' cǎo shuài shōu bīng', + '穷愁潦倒' => ' qióng chóu liáo dǎo', + '瞬态响应' => ' shùn tài xiǎng yìng', + '横抢硬夺' => ' hèng qiǎng yìng duó', + '洞中肯綮' => ' dòng zhōng kěn qìng', + '晃晃悠悠' => ' huàng huǎng yōu yōu', + '论长说短' => ' lùn cháng shuō duǎn', + '五行相生' => ' wǔ háng xiāng shēng', + '漂蓬断梗' => ' piāo péng duàn gěng', + '少年老成' => ' shào nián lǎo chéng', + '翩翩少年' => ' piān piān shào nián', + '汹涌澎湃' => ' xiōng yǒng péng pài', + '微创手术' => ' wēi chuāng shǒu shù', + '言归正传' => ' yán guī zhèng zhuàn', + '亲水长廊' => ' qīn shuǐ cháng láng', + '长生久视' => ' cháng shēng jiǔ shì', + '营养成分' => ' yíng yǎng chéng fèn', + '长江三峡' => ' cháng jiāng sān xiá', + '长话短说' => ' cháng huà duǎn shuō', + '嫌长道短' => ' xián cháng dào duǎn', + '说短道长' => ' shuō duǎn dào cháng', + '说长论短' => ' shuō cháng lùn duǎn', + '山穷水尽' => ' shān qióng shuǐ jìn', + '养虺成蛇' => ' yǎng huǐ chéng shé', + '长远目标' => ' cháng yuǎn mù biāo', + '浓抹淡妆' => ' nóng mò dàn zhuāng', + '斜行横阵' => ' xié xíng héng zhèn', + '迎接挑战' => ' yíng jiē tiǎo zhàn', + '化整为零' => ' huà zhěng wéi líng', + '不明真相' => ' bù míng zhēn xiàng', + '小时了了' => ' xiǎo shí liǎo liǎo', + '详星拜斗' => ' xiáng xīng bài dǒu', + '参回斗转' => ' shēn huí dǒu zhuǎn', + '多言数穷' => ' duō yán shuò qióng', + '雕肝琢肾' => ' diāo gān zhuó shèn', + '顺人应天' => ' shùn rén yìng tiān', + '适应环境' => ' shì yìng huán jìng', + '长命百岁' => ' cháng mìng bǎi suì', + '师直为壮' => ' shī zhí wéi zhuàng', + '长林丰草' => ' cháng lín fēng cǎo', + '博采众长' => ' bó cǎi zhòng cháng', + '钟鸣漏尽' => ' zhōng míng lòu jìn', + '弃短用长' => ' qì duǎn yòng cháng', + '扬长补短' => ' yáng cháng bǔ duǎn', + '十载寒窗' => ' shí zǎi hán chuāng', + '重温旧梦' => ' chóng wēn jiù mèng', + '蝉联冠军' => ' chán lián guàn jūn', + '赌长较短' => ' dǔ cháng jiào duǎn', + '长才广度' => ' cháng cái guǎng dù', + '重碳酸盐' => ' chóng tàn suān yán', + '蝉喘雷干' => ' chán chuǎn léi gān', + '昼短夜长' => ' zhòu duǎn yè cháng', + '测量元件' => ' cè liáng yuán jiàn', + '草率从事' => ' cǎo shuài cóng shì', + '悬石程书' => ' xuán dàn chéng shū', + '浓妆淡抹' => ' nóng zhuāng dàn mò', + '重碳酸钙' => ' chóng tàn suān gài', + '虚晃一枪' => ' xiù huàng yī qiāng', + '手足重茧' => ' shǒu zú chóng jiǎn', + '长年累月' => ' cháng nián lěi yuè', + '晨昏定省' => ' chén hūn dìng xǐng', + '深山长谷' => ' shēn shān cháng gǔ', + '昏定晨省' => ' hūn dìng chén xǐng', + '强迫观念' => ' qiǎng pò guān niàn', + '长岛冰茶' => ' cháng dǎo bīng chá', + '深仇宿怨' => ' shēn chóu xiǔ yuàn', + '鉴真和尚' => ' jiàn zhēn hé shang', + '中坚力量' => ' zhōng jiān lì liang', + '书归正传' => ' shū guī zhèng zhuàn', + '整躬率物' => ' zhěng gōng shuài wù', + '振兴中华' => ' zhèn xīng zhōng huá', + '校短推长' => ' xiào duǎn tuī cháng', + '沉着应战' => ' chén zhuó yìng zhàn', + '首尾相应' => ' shǒu wěi xiāng yìng', + '长篇连载' => ' cháng piān lián zǎi', + '阆苑琼楼' => ' làng yuàn qióng lóu', + '还乡昼锦' => ' huán xiāng zhòu jǐn', + '昏镜重明' => ' hūn jìng chóng míng', + '重峦复嶂' => ' chóng luán fù zhàng', + '长算远略' => ' cháng suàn yuǎn lüè', + '水中著盐' => ' shuǐ zhōng zhuó yán', + '秤斤注两' => ' chēng jīn zhù liǎng', + '火耕水种' => ' huǒ gēng shuǐ zhòng', + '山长水阔' => ' shān cháng shuǐ kuò', + '水中捉月' => ' shui zhong zhuo yue', + '说长话短' => ' shuō cháng huà duǎn', + '山阴乘兴' => ' shān yīn chéng xīng', + '长春新碱' => ' cháng chūn xīn jiǎn', + '重建家园' => ' chóng jiàn jiā yuán', + '遁世长往' => ' dùn shì cháng wǎng', + '解弦更张' => ' jiě xián gēng zhāng', + '水宿山行' => ' shuǐ xiǔ shān xíng', + '相去甚远' => ' xiāng qù shèn yuǎn', + '日月重光' => ' rì yuè chóng guāng', + '逼良为娼' => ' bī liáng wéi chāng', + '事实真相' => ' shì shí zhēn xiàng', + '进行性交' => ' jìn xíng xìng jiāo', + '钻坚仰高' => ' zuàn jiān yǎng gāo', + '膀胱结石' => ' páng guāng jié shí', + '政治斗争' => ' zhèng zhì dòu zhēng', + '推广应用' => ' tuī guǎng yìng yòng', + '窗明几净' => ' chuāng míng jī jìng', + '使羊将狼' => ' shǐ yáng jiàng láng', + '正中己怀' => ' zhèng zhòng jǐ huái', + '长颈鸟喙' => ' cháng jǐng niǎo huì', + '困难重重' => ' kùn nán chóng chóng', + '应用程式' => ' yìng yòng chéng shì', + '朝阳鸣凤' => ' zhāo yáng míng fèng', + '中央银行' => ' zhōng yāng yín háng', + '应用软件' => ' yìng yòng ruǎn jiàn', + '改弦更张' => ' gǎi xián gēng zhāng', + '彰明较著' => ' zhāng míng jiào zhù', + '重新评价' => ' chóng xīn píng jià', + '重生爷娘' => ' chóng shēng yé niáng', + '广文先生' => ' guǎng wén xiān sheng', + '光电效应' => ' guāng diàn xiào yìng', + '调神畅情' => ' tiáo shén chàng qíng', + '正身率下' => ' zhèng shēn shuài xià', + '山崩钟应' => ' shān bēng zhōng yìng', + '调停两用' => ' tiáo tíng liǎng yòng', + '争短论长' => ' zhēng duǎn lùn cháng', + '穷形尽相' => ' qióng xíng jìn xiàng', + '抢风航行' => ' qiāng fēng háng xíng', + '圣经外传' => ' shèng jīng wài zhuàn', + '重新调整' => ' chóng xīn tiáo zhěng', + '公共行政' => ' gōng gòng xíng zhèng', + '算命先生' => ' suàn mìng xiān sheng', + '笑脸相迎' => ' xiào liǎn xiàng yíng', + '横行直撞' => ' héng xíng zhí zhuàng', + '穿着讲究' => ' chuān zhuó jiǎng jiu', + '王侯将相' => ' wáng hóu jiàng xiàng', + '逞娇呈美' => ' chěng jiāo chéng měi', + '蒙头转向' => ' mēng tóu zhuàn xiàng', + '青灯黄卷' => ' qīng dēng huáng juàn', + '堂皇冠冕' => ' táng huáng guàn miǎn', + '率尔成章' => ' shuài ěr chéng zhāng', + '重新装修' => ' chóng xīn zhuāng xīu', + '庄严宝相' => ' zhuāng yán bǎo xiàng', + '唱筹量沙' => ' chàng chóu liáng shā', + '胸中万卷' => ' xiōng zhōng wàn juàn', + '兴衰成败' => ' xīng shuāi chéng bài', + '盛衰兴废' => ' shèng shuāi xīng fèi', + '萍飘蓬转' => ' píng piāo péng zhuàn', + '双足重茧' => ' shuāng zú chóng jiǎn', + '长盛不衰' => ' cháng shèng bù shuāi', + '长亭短亭' => ' cháng tíng duǎn tíng', + '争长论短' => ' zhēng cháng lùn duǎn', + '赤诚相见' => ' chì chéng xiàng jiàn', + '兵多将广' => ' bīng duō jiàng guǎng', + '黄卷青灯' => ' huáng juàn qīng dēng', + '道长争短' => ' dào cháng zhēng duǎn', + '防火长城' => ' fáng huǒ cháng chéng', + '承平盛世' => ' chéng píng shèng shì', + '黄钟长弃' => ' huáng zhōng cháng qì', + '转轮圣王' => ' zhuàn lún shèng wáng', + '甚嚣尘上' => ' shèn xiāo chén shàng', + '壮发冲冠' => ' zhuàng fā chōng guàn', + '逞强好胜' => ' chěng qiáng hào shèng', + '拳脚相向' => ' quán jiǎo xiāng xiàng', + '长江天堑' => ' cháng jiāng tiān qiàn', + '条条框框' => ' tiáo tiáo kuàng kuàng', + '勉勉强强' => ' miǎn miǎn qiǎng qiáng', + '同声相应' => ' tóng shēng xiāng yìng', + '争强好胜' => ' zhēng qiáng hào shèng', + '争长竞短' => ' zhēng cháng jìng duǎn', + '圣君贤相' => ' shèng jīng xián xiàng', + '圣经贤传' => ' shèng jīng xián zhuàn', + '山中宰相' => ' shān zhōng zǎi xiàng', + '精兵强将' => ' jīng bīng qiáng jiàng', + '竞短争长' => ' jìng duǎn zhēng cháng', + '正邪相争' => ' zhèng xié xiāng zhēng', + '校短量长' => ' jiào duǎn liáng cháng', + '率尔操觚' => ' shuài ěr cāo gū', + '账房先生' => ' zhàng fáng xiān sheng', + '短中抽长' => ' duǎn zhōng chōu cháng', + '长长短短' => ' cháng cháng duǎn duǎn', + '中山狼传' => ' zhōng shān láng zhuàn', + '上当受骗' => ' shàng dàng shòu piàn', + '生肖属相' => ' shēng xiào shǔ xiàng', + '长城饭店' => ' cháng chéng fàn diàn', + '蒸气重整' => ' zhēng qì chóng zhěng', + '遣兵调将' => ' qiǎn bīng diào jiàng', + '山长水远' => ' shān cháng shuǐ yuǎn', + '三长两短' => ' sān cháng liǎng duǎn', + '车载船装' => ' chē zǎi chuán zhuāng', + '铢两相称' => ' zhū liǎng xiāng chèn', + '更长梦短' => ' gēng cháng mèng duǎn', + '着手成春' => ' zhuó shǒu chéng chūn', + '项背相望' => ' xiàng bèi xiāng wàng', + '朝鲜战争' => ' cháo xiǎn zhàn zhēng', + '敩学相长' => ' zuàn xué xiāng cháng', + '长篇小说' => ' cháng piān xiǎo shuō', + '善善从长' => ' shàn shàn cóng cháng', + '乘风兴浪' => ' chéng fēng xīng làng', + '扇枕温被' => ' shān zhěn wēn chuáng', + '说长说短' => ' shuō cháng shuō duǎn', + '长荣航空' => ' cháng róng háng kōng', + '九转功成' => ' jiǔ zhuàn gōng chéng', + '正中下怀' => ' zhèng zhòng xià huái', + '调兵遣将' => ' diào bīng qiǎn jiàng', + '天兵天将' => ' tiān bīng tiān jiàng', + '丧尽天良' => ' sàng jìn tiān liáng', + '一长两短' => ' yī cháng liǎng duǎn', + '电动转盘' => ' diàn dòng zhuàn pán', + '寸长片善' => ' cùn cháng piàn shàn', + '发上冲冠' => ' fà shàng chōng guān', + '较长絜短' => ' jiào cháng xié duǎn', + '彰明昭着' => ' zhāng míng zhāo zhe', + '长虺成蛇' => ' zhǎng huǐ chéng shé', + '洋为中用' => ' yáng wéi zhōng yòng', + '浩浩汤汤' => ' hào hào shāng shāng', + '寸量铢称' => ' cùn liáng zhū chēng', + '山峦重叠' => ' shān luán chóng dié', + '乘胜追击' => ' chéng shèng zhuī jī', + '过关斩将' => ' guò guān zhǎn jiàng', + '还魂橡胶' => ' huán hún xiàng jiāo', + '传为笑柄' => ' chuán wéi xiào bǐng', + '连中三元' => ' lián zhòng sān yuán', + '彰明昭著' => ' zhāng míng zhāo zhù', + '多口相声' => ' duō kǒu xiàng sheng', + '雀屏中选' => ' què píng zhòng xuǎn', + '单口相声' => ' dān kǒu xiàng sheng', + '相切相磋' => ' xiāng qiē xiāng cuō', + '公共零点' => ' gōng gòng líng diǎn', + '降妖捉怪' => ' xiáng yāo zhuō guài', + '山高水长' => ' shān gāo shuǐ cháng', + '干城之将' => ' gān chéng zhī jiàng', + '工商银行' => ' gōng shāng yín háng', + '心长绠短' => ' xīn cháng gěng duǎn', + '梦想颠倒' => ' mèng xiǎng diān dǎo', + '心事重重' => ' xīn shì chóng chóng', + '面折庭争' => ' miàn shé tíng zhēng', + '凤鸣朝阳' => ' fèng míng zhāo yáng', + '顶踵尽捐' => ' dǐng zhǒng jìn juān', + '膏粱年少' => ' gāo liáng nián shào', + '交响乐团' => ' jiāo xiǎng yuè tuán', + '香花供养' => ' xiāng huā gòng yǎng', + '东方将白' => ' dong fang jiang bai', + '鸣凤朝阳' => ' míng fèng zhāo yáng', + '长平之战' => ' cháng píng zhī zhàn', + '宏观调控' => ' hóng guān tiáo kòng', + '相貌堂堂' => ' xiàng mào táng táng', + '相门有相' => ' xiàng mén yǒu xiàng', + '东窗消息' => ' dōng chuāng xiāo xi', + '仰天长叹' => ' yǎng tiān cháng tàn', + '轰动效应' => ' hōng dòng xiào yìng', + '兴兵动众' => ' xīng bīng dòng zhòng', + '滚滚长江' => ' gǔn gǔn cháng jiāng', + '反正还淳' => ' fǎn zhèng huán chún', + '运动健将' => ' yùn dòng jiàn jiàng', + '羊瘙痒症' => ' yáng sào yǎng zhèng', + '旋乾转坤' => ' xuán qián zhuǎn kūn', + '响应时间' => ' xiǎng yìng shí jiān', + '受到影响' => ' shòu dào yǐng xiǎng', + '正宫娘娘' => ' zhēng gōng niáng niáng', + '晕头转向' => ' yūn tóu zhuàn xiàng', + '造谣中伤' => ' zào yáo zhòng shāng', + '方领圆冠' => ' fāng lǐng yuán guàn', + '斗转参横' => ' dǒu zhuǎn shēn héng', + '缅甸联邦' => ' miǎn diàn lián bāng', + '作善降祥' => ' zuò shàn jiàng xiáng', + '长街短巷' => ' cháng jiē duǎn xiàng', + '重峦叠嶂' => ' chóng luán dié zhàng', + '转轮手枪' => ' zhuàn lún shǒu qiāng', + '水远山长' => ' shuǐ yuǎn shān cháng', + '仰天长啸' => ' yǎng tiān cháng xiào', + '广种薄收' => ' guǎng zhòng bó shōu', + '广为流传' => ' guǎng wéi liú chuán', + '康乾盛世' => ' kāng qián shèng shì', + '公共交通' => ' gōng gòng jiāo tōng', + '公共卫生' => ' gōng gòng wèi shēng', + '传风扇火' => ' chuán fēng shān huǒ', + '衡短论长' => ' héng duǎn lùn cháng', + '楞手楞脚' => ' lèng shǒu lèng jiǎo', + '应天承运' => ' yìng tiān chéng yùn', + '笼鸟槛猿' => ' lóng niǎo jiàn yuán', + '公共场所' => ' gōng gòng chǎng suǒ', + '摇摇晃晃' => ' yáo yáo huàng huàng', + '转败为胜' => ' zhuǎn bài wéi shèng', + '沸沸汤汤' => ' fèi fèi shāng shāng', + '跌跌跄跄' => ' diē diē qiàng qiàng', + '较短絜长' => ' jiào duǎn xié cháng', + '情长纸短' => ' qíng cháng zhǐ duǎn', + '对口相声' => ' duì kǒu xiàng sheng', + '青春年少' => ' qīng chūn nián shào', + '学有专长' => ' xué yǒu zhuān cháng', + '远涉重洋' => ' yuǎn shè chóng yáng', + '从长商议' => ' cóng cháng shāng yì', + '弹冠相庆' => ' tán guān xiāng qìng', + '长安少年' => ' cháng ān shào nián', + '斗升之水' => ' dǒu shēng zhī shuǐ', + '虱处裈中' => ' shī chǔ kūn zhōng', + '将遇良才' => ' jiàng yù liáng cái', + '鉴影度形' => ' jiàn yǐng duó xíng', + '将功折过' => ' jiāng gōng shé guò', + '残兵败将' => ' cán bīng bài jiàng', + '拽象拖犀' => ' zhuài xiàng tuō xī', + '转败为功' => ' zhuǎn bài wéi gōng', + '氧炔吹管' => ' yǎng quē chuī guǎn', + '惊起梁尘' => ' jīng qǐ liáng chén', + '静电感应' => ' jìng diàn gǎn yìng', + '进贤兴功' => ' jìn xián xīng gōng', + '满口应承' => ' mǎn kǒu yìng chéng', + '气贯长虹' => ' qì guàn cháng hóng', + '着人先鞭' => ' zhuó rén xiān biān', + '进退消长' => ' jìn tuì xiāo cháng', + '将相之器' => ' jiàng xiàng zhī qì', + '交响乐队' => ' jiāo xiǎng yuè duì', + '确定效应' => ' què dìng xiào yìng', + '神成为人' => ' shén chéng wéi rén', + '哗众取宠' => ' huá zhòng qǔ chǒng', + '见性成佛' => ' jiàn xìng chéng fó', + '嚼齿穿龈' => ' jiáo chǐ chuān yín', + '鸾颠凤倒' => ' luán diān fèng dǎo', + '清史列传' => ' qīng shǐ liè zhuàn', + '开疆拓境' => ' kāi jiāng tuò jìng', + '亢音高唱' => ' gāng yīn gāo chàng', + '不见经传' => ' bù jiàn jīng zhuàn', + '简切了当' => ' jiǎn qiè liǎo dàng', + '天旋地转' => ' tiān xuán dì zhuàn', + '水浒全传' => ' shuǐ hǔ quán zhuàn', + '嚼穿龈血' => ' jiáo chuān yín xuè', + '清风劲节' => ' qīng fēng jìng jié', + '永贞内禅' => ' yǒng zhēn nèi shàn', + '间见层出' => ' jiàn xiàn céng chū', + '较短比长' => ' jiào duǎn bǐ cháng', + '沅江九肋' => ' yuán jiāng jiǔ lèi', + '嚼腭搥床' => ' jiáo è chuí chuáng', + '嘴快舌长' => ' zuǐ kuài shé cháng', + '阶跃响应' => ' jiē yuè xiǎng yìng', + '作浪兴风' => ' zuò làng xīng fēng', + '法轮常转' => ' fǎ lún cháng zhuàn', + '江郎才尽' => ' jiāng láng cái jìn', + '兴风作浪' => ' xīng fēng zuò làng', + '少先队员' => ' shào xiān duì yuán', + '知疼着痒' => ' zhī téng zháo yǎng', + '专家评价' => ' zhuān jiā píng jià', + '更型换代' => ' gēng xíng huàn dài', + '少管闲事' => ' shào guǎn xián shì', + '论短道长' => ' lùn duǎn dào cháng', + '尝尽心酸' => ' cháng jìn xīn suān', + '乱箭攒心' => ' luàn jiàn cuán xīn', + '旧调重弹' => ' jiù diào chóng tán', + '应用卫星' => ' yìng yòng wèi xīng', + '贞松劲柏' => ' zhēn sōng jìng bǎi', + '羊瘙痒病' => ' yáng sào yǎng bìng', + '了若指掌' => ' liǎo ruò zhǐ zhǎng', + '条件反应' => ' tiáo jiàn fǎn yìng', + '管弦乐团' => ' guǎn xián yuè tuán', + '朝梁暮陈' => ' zhāo liáng mù chén', + '朝梁暮周' => ' zhāo liáng mù zhōu', + '横行天下' => ' héng xíng tiān xià', + '空中少爷' => ' kōng zhōng shào ye', + '简单明了' => ' jiǎn dān míng liǎo', + '中国银行' => ' zhōng guó yín háng', + '应用程序' => ' yìng yòng chéng xù', + '应天顺人' => ' yìng tiān shùn rén', + '语长心重' => ' yǔ cháng xīn zhòng', + '鼋鸣鳖应' => ' yuán míng biē yìng', + '漏尽钟鸣' => ' lòu jìn zhōng míng', + '语短情长' => ' yǔ duǎn qíng cháng', + '拱手而降' => ' gǒng shòu ér xiáng', + '臭名昭著' => ' chòu míng zhāo zhù', + '对称空间' => ' duì chèn kōng jiān', + '聪明绝顶' => ' cōng ming jué dǐng', + '联调联试' => ' lián tiáo lián shì', + '惯用伎俩' => ' guàn yòng jì liǎng', + '广东音乐' => ' guǎng dōng yīn yuè', + '应天顺民' => ' yìng tiān shùn mín', + '广陵散绝' => ' guǎng líng sǎn jué', + '遇难成祥' => ' yù nàn chéng xiáng', + '岳镇渊渟' => ' yuè zhèn yuān tīng', + '有效成分' => ' yǒu xiào chéng fèn', + '交通银行' => ' jiāo tōng yín háng', + '万里长江' => ' wàn lǐ cháng jiāng', + '梗泛萍漂' => ' gěng fàn píng piāo', + '万里长城' => ' wàn lǐ cháng chéng', + '更令明号' => ' gēng lìng míng hào', + '子孙娘娘' => ' zǐ sūn niáng niang', + '毁冠裂裳' => ' huǐ guān liè cháng', + '横征苛敛' => ' hèng zhēng kē liǎn', + '词长效应' => ' cí cháng xiào yìng', + '名声籍甚' => ' míng shēng jí shèn', + '封豕长蛇' => ' fēng shǐ cháng shé', + '语重心长' => ' yǔ zhòng xīn cháng', + '灭景追风' => ' miè yǐng zhuī fēng', + '常年累月' => ' cháng nián lěi yuè', + '风尘肮脏' => ' fēng chén āng zāng', + '丰草长林' => ' fēng cǎo cháng lín', + '月亮女神' => ' yuè liang nǔ: shén', + '以升量石' => ' yǐ shēng liáng dàn', + '望子成龙' => ' wàng zǐ chéng lóng', + '槁项黄馘' => ' gǎo xiàng huáng xù', + '讯框中继' => ' xùn kuàng zhōng jì', + '粉妆玉琢' => ' fěn zhuāng yù zhuó', + '饮马长江' => ' yǐn mǎ cháng jiāng', + '飞流短长' => ' fēi liú duǎn cháng', + '改姓更名' => ' gǎi xìng gēng míng', + '粘胶纤维' => ' nián jiāo xiān wéi', + '更深人静' => ' gēng shēn rén jìng', + '装模作样' => ' zhuāng mú zuò yàng', + '望洋兴叹' => ' wàng yáng xīng tàn', + '发踊冲冠' => ' fà yǒng chōng guān', + '阴凝坚冰' => ' yīn níng jiān bīng', + '山公倒载' => ' shān gōng dǎo zǎi', + '多难兴邦' => ' duō nàn xīng bāng', + '豆重榆瞑' => ' dòu chóng yú míng', + '乜斜缠帐' => ' niè xié chán zhàng', + '藤野先生' => ' téng yě xiān sheng', + '孽障种子' => ' niè zhàng zhǒng zǐ', + '蒸发空调' => ' zhēng fā kōng tiáo', + '恩甚怨生' => ' ēn shèn yuàn shēng', + '转变抹角' => ' zhuǎn biàn mò jiǎo', + '明白了当' => ' míng bái liǎo dàng', + '藏传佛教' => ' zàng chuán fó jiào', + '告老还乡' => ' gào lǎo huán xiāng', + '藏茴香果' => ' zàng huí xiāng guǒ', + '袅娜娉婷' => ' niǎo nuó pīng tíng', + '转来转去' => ' zhuàn lái zhuàn qù', + '夜长梦短' => ' yè cháng mèng duǎn', + '商业应用' => ' shāng yè yìng yòng', + '捻捻转儿' => ' niǎn niǎn zhuàn ér', + '说来话长' => ' shuō lái huà cháng', + '借面吊丧' => ' jiè miàn diào sāng', + '更高性能' => ' gēng gāo xìng néng', + '扛鼎抃牛' => ' gāng dǐng biàn niú', + '钧天广乐' => ' jūn tiān guǎng yuè', + '生意兴隆' => ' shēng yì xīng lóng', + '拘神遣将' => ' jū shén qiǎn jiàng', + '率兽食人' => ' shuài shòu shí rén', + '正义斗争' => ' zhèng yì dòu zhēng', + '简截了当' => ' jiǎn jié liǎo dàng', + '旧梦重温' => ' jiù mèng chóng wēn', + '产前检查' => ' chǎn qián jiǎn chá', + '钟鼎人家' => ' zhōng dǐng rén jia', + '矿物成分' => ' kuàng wù chéng fèn', + '众好众恶' => ' zhòng hào zhòng wù', + '旷日长久' => ' kuàng rì cháng jiǔ', + '破镜重圆' => ' pò jìng chóng yuán', + '化零为整' => ' huà líng wéi zhěng', + '子癫前症' => ' zǐ diān qián zhèng', + '睡眠失调' => ' shuì mián shī tiáo', + '睡回笼觉' => ' shuì huí lóng jiào', + '盈箱累箧' => ' yíng xiāng lěi qiè', + '黄发垂髫' => ' huáng fà chuí tiáo', + '妙语横生' => ' miào yǔ hèng shēng', + '倦鸟知还' => ' juàn niǎo zhī huán', + '反戈相向' => ' fǎn gē xiāng xiàng', + '商人银行' => ' shāng rén yín háng', + '贫穷潦倒' => ' pín qíong liáo dǎo', + '繁荣兴旺' => ' fán róng xīng wàng', + '差分方程' => ' chā fēn fāng chéng', + '变徵之声' => ' biàn zhǐ zhī shēng', + '论长道短' => ' lùn cháng dào duǎn', + '膏场绣浍' => ' gāo cháng xiù kuài', + '蜚短流长' => ' fēi duǎn liú cháng', + '虾兵蟹将' => ' xiā bīng xiè jiàng', + '方寸万重' => ' fāng cùn wàn chóng', + '昼长夜短' => ' zhòu cháng yè duǎn', + '干劲冲天' => ' gān jìn chōng tiān', + '朝鲜太宗' => ' cháo xiǎn tài zōng', + '长鸣都尉' => ' cháng míng dōu wèi', + '敛声屏气' => ' liǎn shēng bǐng qì', + '应付账款' => ' yìng fù zhàng kuǎn', + '众口难调' => ' zhòng kǒu nán tiáo', + '众毛攒裘' => ' zhòng máo cuán qiú', + '大中学生' => ' dà zhōng xué sheng', + '种植园主' => ' zhòng zhí yuán zhǔ', + '中信银行' => ' zhōng xìn yín háng', + '学生运动' => ' xué sheng yùn dòng', + '数短论长' => ' shǔ duǎn lùn cháng', + '攒零合整' => ' cuán líng hé zhěng', + '掸邦高原' => ' shàn bāng gāo yuán', + '出将入相' => ' chū jiàng rù xiàng', + '撩是生非' => ' liáo shì shēng fēi', + '败将残兵' => ' bài jiàng cán bīng', + '颠鸾倒凤' => ' diān luán dǎo fèng', + '王贡弹冠' => ' wáng gòng dàn guàn', + '挑唇料嘴' => ' tiǎo chún liào zuǐ', + '清都绛阙' => ' qīng dōu jiàng què', + '从容应对' => ' cóng róng yìng duì', + '全息照相' => ' quán xī zhào xiàng', + '青藏高原' => ' qīng zàng gāo yuán', + '调唇弄舌' => ' tiáo chún nòng shé', + '调风弄月' => ' tiáo fēng nòng yuè', + '软件调试' => ' ruǎn jiàn tiáo shì', + '从长计较' => ' cóng cháng jì jiào', + '扬长避短' => ' yáng cháng bì duǎn', + '调良稳泛' => ' tiáo liáng wěn fàn', + '拔刀相向' => ' bá dāo xiāng xiàng', + '人丁兴旺' => ' rén dīng xīng wàng', + '全能冠军' => ' quán néng guàn jūn', + '成年累月' => ' chéng nián lěi yuè', + '揣合逢迎' => ' chuǎi hé féng yíng', + '重山复水' => ' chóng shān fù shuǐ', + '铜盘重肉' => ' tóng pán zhòng ròu', + '万里长征' => ' wàn lǐ cháng zhēng', + '川渟岳峙' => ' chuān tīng yuè zhì', + '传授知识' => ' chuán shòu zhī shi', + '穿红着绿' => ' chuān hóng zhuó lǜ', + '重熙累盛' => ' chóng xī lěi shèng', + '飞短流长' => ' fēi duǎn liú cháng', + '忘啜废枕' => ' wàng chuò fèi zhěn', + '公共财产' => ' gōng gòng cái chǎn', + '黄花姑娘' => ' huáng huā gū niang', + '餐桌转盘' => ' cān zhuō zhuàn pán', + '应召女郎' => ' yìng zhào nǔ: láng', + '重裀列鼎' => ' chóng yīn liè dǐng', + '鹰击长空' => ' yīng jī cháng kōng', + '鸟尽弓藏' => ' niǎo jìn gōng cáng', + '应天顺时' => ' yìng tiān shùn shí', + '应用平台' => ' yìng yòng píng tái', + '感应线圈' => ' gǎn yìng xiàn quān', + '危机重重' => ' wēi jī chóng chóng', + '重山复岭' => ' chóng shān fù lǐng', + '长材小试' => ' cháng cái xiǎo shì', + '量子场论' => ' liàng zǐ chǎng lùn', + '率以为常' => ' shuài yǐ wéi cháng', + '剪切形变' => ' jiǎn qiē xíng biàn', + '剂量效应' => ' jì liàng xiào yìng', + '承销价差' => ' chéng xiāo jià chā', + '远渡重洋' => ' yuǎn dù chóng yáng', + '保长对应' => ' bǎo cháng duì yìng', + '对称中心' => ' duì chèn zhōng xīn', + '彷徨失措' => ' páng huáng shī cuò', + '炮凤烹龙' => ' páo fèng pēng lóng', + '重新审视' => ' chóng xīn shěn shì', + '手下败将' => ' shǒu xià bài jiàng', + '前缘未了' => ' qián yuán wèi liǎo', + '旁通曲畅' => ' páng tōng qǔ chàng', + '雕章琢句' => ' diāo zhāng zhuó jù', + '烹龙炮凤' => ' pēng lóng páo fèng', + '断还归宗' => ' duàn huán guī zōng', + '长安道上' => ' cháng ān dào shàng', + '娉婷袅娜' => ' pīng tíng niǎo nuó', + '长蛇封豕' => ' cháng shé fēng shǐ', + '为好成歉' => ' wéi hǎo chéng qiàn', + '传为笑谈' => ' chuán wéi xiào tán', + '刀耕火种' => ' dāo gēng huǒ zhòng', + '调舌弄唇' => ' tiáo shé nòng chún', + '援交小姐' => ' yuán jiāo xiǎo jie', + '顺应不良' => ' shùn yìng bù liáng', + '顺应天时' => ' shùn yìng tiān shí', + '颂声载道' => ' sòng shēng zǎi dào', + '穷困潦倒' => ' qióng kùn liáo dǎo', + '往渚还汀' => ' wǎng zhǔ huán tīng', + '穷年累世' => ' qióng nián lěi shì', + '洞鉴废兴' => ' dòng jiàn fèi xīng', + '穷年累月' => ' qióng nián lěi yuè', + '枉口诳舌' => ' wǎng kǒu kuáng shé', + '床头金尽' => ' chuáng tóu jīn jìn', + '兵微将寡' => ' bīng wēi jiàng guǎ', + '成败兴废' => ' chéng bài xīng fèi', + '春秋三传' => ' chūn qīu sān zhuàn', + '率土同庆' => ' shuài tǔ tóng qìng', + '弹性形变' => ' tán xìng xíng biàn', + '重见天日' => ' chóng jiàn tiān rì', + '戎事倥偬' => ' róng shì kǒng zǒng', + '憨豆先生' => ' hān dòu xiān sheng', + '报应不爽' => ' bào yìng bù shuǎng', + '强死强活' => ' qiǎng sǐ qiǎng huó', + '弓长岭区' => ' gōng cháng lǐng qū', + '万象更新' => ' wàn xiàng gēng xīn', + '傀儡政权' => ' kuǐ lěi zhèng quán', + '强的松龙' => ' qiáng dí sōng lóng', + '陪唱小姐' => ' péi chàng xiǎo jie', + '强弓劲弩' => ' qiáng gōng jìng nǔ', + '天宝当年' => ' tian bao dang nian', + '抢劫银行' => ' qiǎng jié yín háng', + '百舍重趼' => ' bǎi shè chóng jiǎn', + '连蒙带骗' => ' lián mēng dài piàn', + '三旨相公' => ' sān zhǐ xiàng gōng', + '淡妆轻抹' => ' dàn zhuāng qīng mò', + '云窗雾槛' => ' yún chuāng wù jiàn', + '道长论短' => ' dào cháng lùn duǎn', + '电光朝露' => ' diàn guāng zhāo lù', + '代为说项' => ' dài wéi shuō xiàng', + '翘首引领' => ' qiáo shǒu yǐn lǐng', + '持人长短' => ' chí rén cháng duǎn', + '扭转乾坤' => ' niǔ zhuǎn qián kūn', + '黑白相间' => ' hēi bái xiāng jiàn', + '电子工程' => ' diàn zǐ gōng chéng', + '网络应用' => ' wǎng luò yìng yòng', + '福寿绵长' => ' fú shòu mián cháng', + '朗朗乾坤' => ' lǎng lǎng qián kūn', + '泪干肠断' => ' lèi gān cháng duàn', + '佹形僪状' => ' guǐ xíng yù zhuàng', + '中兴新村' => ' zhōng xīng xīn cūn', + '中规中矩' => ' zhòng guī zhòng jǔ', + '织田信长' => ' zhī tián xìn cháng', + '过分强调' => ' guò fèn qiáng diào', + '粮尽援绝' => ' liáng jìn yuán jué', + '朝成暮遍' => ' zhāo chéng mù biàn', + '海水难量' => ' hǎi shuǐ nán liáng', + '九九重阳' => ' jǐu jǐu chóng yáng', + '牛头刨床' => ' niú tóu bào chuáng', + '梁上君子' => ' liáng shàng jūn zǐ', + '斩将搴旗' => ' zhǎn jiàng qiān qí', + '久别重逢' => ' jiǔ bié chóng féng', + '连街倒巷' => ' lián jiē dǎo xiàng', + '张脉偾兴' => ' zhāng mài fèn xīng', + '精灵宝钻' => ' jīng líng bǎo zuàn', + '切中时病' => ' qiè zhòng shí bìng', + '电子手帐' => ' diàn zǐ shǒu zhàng', + '两样东西' => ' liǎng yàng dōng xi', + '五言长城' => ' wǔ yán cháng chéng', + '登台拜将' => ' dēng tái bài jiàng', + '千了百当' => ' qiān liǎo bǎi dàng', + '长生不老' => ' cháng shēng bù lǎo', + '上好下甚' => ' shàng hǎo xià shèn', + '长生禄位' => ' cháng shēng lù wèi', + '山溜穿石' => ' shān liù chuān shí', + '搴旗斩将' => ' qiān qí zhǎn jiàng', + '倒箧倾筐' => ' dǎo qiè qīng kuāng', + '千乘之国' => ' qiān shèng zhī guó', + '长江流域' => ' cháng jiāng líu yù', + '长风破浪' => ' cháng fēng pò làng', + '吃亏上当' => ' chī kuī shàng dàng', + '倒凤颠鸾' => ' dǎo fèng diān luán', + '间谍软件' => ' jiàn dié ruǎn jiàn', + '夏虫朝菌' => ' xià chóng zhāo jūn', + '超超玄著' => ' chāo chāo xuán zhù', + '拜将封侯' => ' bài jiàng fēng hóu', + '拣佛烧香' => ' jiǎn fó shāo xiāng', + '强词夺正' => ' qiǎng cí duó zhèng', + '成都平原' => ' chéng dū píng yuán', + '问长问短' => ' wèn cháng wèn duǎn', + '损兵折将' => ' sǔn bīng zhé jiàng', + '唯唯连声' => ' wěi wěi lián shēng', + '村生泊长' => ' cūn shēng bó cháng', + '百舍重茧' => ' bǎi shè chóng jiǎn', + '百了千当' => ' bǎi liǎo qiān dāng', + '旦种暮成' => ' dàn zhòng mù chéng', + '寸长尺短' => ' cùn cháng chǐ duǎn', + '倒裳索领' => ' dào cháng suǒ lǐng', + '秤平斗满' => ' chèng píng dǒu mǎn', + '升斗小民' => ' shēng dǒu xiǎo mín', + '登坛拜将' => ' dēng tán bài jiàng', + '标准偏差' => ' biāo zhǔn piān chā', + '帝王将相' => ' dì wáng jiàng xiàng', + '相得甚欢' => ' xiāng dé shèn huān', + '想出风头' => ' xiǎng chū fēng tou', + '长柄勺子' => ' cháng bǐng sháo zi', + '表里相应' => ' biǎo lǐ xiāng yìng', + '长荣海运' => ' cháng róng hǎi yùn', + '从严惩处' => ' cóng yán chéng chǔ', + '同侪审查' => ' tóng chái shěn chá', + '更姓改物' => ' gēng xìng gǎi wù', + '失惊倒怪' => ' shī jīng dǎo guài', + '荷枪实弹' => ' hè qiāng shí dàn', + '朝种暮获' => ' zhāo zhǒng mù hù', + '扣壶长吟' => ' kòu hú cháng yín', + '一针见血' => ' yī zhēn jiàn xiě', + '率土之滨' => ' shuài tǔ zhī bīn', + '枕席还师' => ' zhěn xí huán shī', + '三占从二' => ' sān zhān cóng èr', + '世界银行' => ' shì jiè yín háng', + '朝奏夕召' => ' zhāo zòu xī zhào', + '朝奏暮召' => ' zhāo zòu mù zhào', + '率由旧则' => ' shuài yóu jiù zé', + '子宫后倾' => ' zǐ gōng hòu qīng', + '率土归心' => ' shuài tǔ guī xīn', + '朝钟暮鼓' => ' zhāo zhōng mù gǔ', + '子宫脱垂' => ' zǐ gōng tuō chuí', + '会计人员' => ' kuài jì rén yuán', + '正冠纳履' => ' zhèng guàn nà lǚ', + '抒情散文' => ' shū qíng sǎn wén', + '大肠杆菌' => ' dà cháng gǎn jūn', + '知人善任' => ' zhī rén shàn rèn', + '看家本领' => ' kān jiā běn lǐng', + '书缺有间' => ' shū quē yǒu jiàn', + '会计报表' => ' kuài jì bào biǎo', + '缊褐瓢箪' => ' yùn hè piáo dān', + '人造草坪' => ' rén zào cǎo píng', + '日中为市' => ' rì zhōng wéi shì', + '是非颠倒' => ' shì fēi diān dǎo', + '三藏法师' => ' sān zàng fǎ shī', + '毁家纾难' => ' huǐ jiā shū nàn', + '食不重味' => ' shí bù chóng wèi', + '一言为重' => ' yī yán wéi zhòng', + '一走了之' => ' yī zǒu liǎo zhī', + '上半部分' => ' shàng bàn bù fèn', + '诗书发冢' => ' shī shū fà zhǒng', + '生发未燥' => ' shēng fà wèi zào', + '桑户棬枢' => ' sāng hù juàn shū', + '指导思想' => ' zhí dǎo sī xiǎng', + '三十六行' => ' sān shí liù háng', + '开疆拓宇' => ' kāi jiāng tuò yǔ', + '不治之症' => ' bù zhì zhī zhèng', + '斫雕为朴' => ' zhuó diāo wéi pǔ', + '口尚乳臭' => ' kǒu shàng rǔ xiù', + '智尽能索' => ' zhì jìn néng suǒ', + '口燥唇干' => ' kǒu zào chún gān', + '人类行为' => ' rén lèi xíng wéi', + '化为泡影' => ' huà wéi pāo yǐng', + '大洋中脊' => ' dà yáng zhōng jǐ', + '糠豆不赡' => ' kang dou bu shan', + '左支右调' => ' zuǒ zhī yòu tiáo', + '悬首吴阙' => ' xuán shǒu wú què', + '兴妖作孽' => ' xīng yāo zuò niè', + '秀出班行' => ' xiù chū bān háng', + '公共事业' => ' gōng gòng shì yè', + '公共假期' => ' gōng gòng jià qī', + '公共厕所' => ' gōng gòng cè suǒ', + '刑讯逼供' => ' xíng xùn bī gòng', + '悬鼓待椎' => ' xuán gǔ dài zhuī', + '轩鹤冠猴' => ' xuān hè guàn hóu', + '供不应求' => ' gōng bù yìng qiú', + '学生宿舍' => ' xué shēng sù shè', + '片长末技' => ' piàn cháng mò jì', + '埙篪相和' => ' xūn chí xiāng hè', + '公共汽车' => ' gōng gòng qì chē', + '唱高调儿' => ' chàng gāo diào r', + '没收财产' => ' mò shōu cái chǎn', + '万古长存' => ' wàn gǔ cháng cún', + '兴文匽武' => ' xīng wén diào wǔ', + '形孤影只' => ' xíng gū yǐng zhī', + '七长八短' => ' qī cháng bā duǎn', + '王孙公子' => ' wáng sūn gōng zǐ', + '地壳运动' => ' dì qiào yùn dòng', + '理论框架' => ' lǐ lùn kuàng jià', + '指雁为羹' => ' zhǐ yàn wéi gēng', + '咳唾成珠' => ' ké tuò chéng zhū', + '抗颜为师' => ' kàng yán wéi shī', + '直言切谏' => ' zhí yán qiē jiàn', + '厚养薄葬' => ' hòu yǎng bó zàng', + '指天为誓' => ' zhǐ tiān wéi shì', + '知疼着热' => ' zhī téng zháo rè', + '认影为头' => ' rèn yǐng wéi tóu', + '人心丧尽' => ' rén xīn sàng jìn', + '溘然长逝' => ' kè rán cháng shì', + '指树为姓' => ' zhǐ shù wéi xìng', + '数往知来' => ' shǔ wǎng zhī lái', + '三省吾身' => ' sān xǐng wú shēn', + '原子半径' => ' yuán zǐ bàn jìng', + '转灾为福' => ' zhuǎn zāi wéi fú', + '只身孤影' => ' zhī shēn gū yǐng', + '刻章琢句' => ' kè zhāng zhuó jù', + '指山说磨' => ' zhǐ shān shuō mò', + '人烟浩穰' => ' rén yān hào rǎng', + '不揣冒昧' => ' bù chuǎi mào mèi', + '化学反应' => ' huà xué fǎn yìng', + '鸠形鹄面' => ' jiū xíng hú miàn', + '三陪小姐' => ' sān péi xiǎo jie', + '筑舍道傍' => ' zhù shè dào bàng', + '气冲斗牛' => ' qì chōng dǒu niú', + '钻穴逾垣' => ' zuàn xué yú yuán', + '尽入彀中' => ' jìn rù gòu zhōng', + '詹言曲说' => ' zhān yán qǔ shuō', + '埋天怨地' => ' mán tiān yuàn dì', + '累卵之危' => ' lěi luǎn zhī wēi', + '参辰日月' => ' shēn chén rì yuè', + '斩尽杀绝' => ' zhǎn jìn shā jué', + '贼子乱臣' => ' zéi zǐ luàn chén', + '钻头就锁' => ' zuàn tóu jiù suǒ', + '尽美尽善' => ' jìn měi jìn shàn', + '醉山颓倒' => ' zuì shān tuí dǎo', + '善为说辞' => ' shàn wéi shuō cí', + '斩将刈旗' => ' zhǎn jiàng yì qí', + '累瓦结绳' => ' lěi wǎ jié shéng', + '累屋重架' => ' lěi wū chóng jià', + '似箭在弦' => ' sì jiàn zài xián', + '曾不惨然' => ' zeng bu chan ran', + '白喉杆菌' => ' bái hóu gǎn jūn', + '尽其所长' => ' jìn qí suǒ cháng', + '桑土绸缪' => ' sāng tǔ chóu miù', + '人多阙少' => ' rén duō què shǎo', + '思潮澎湃' => ' sī cháo péng pài', + '沾沾自好' => ' zhān zhān zì hào', + '日朘月减' => ' rì juān yuè jiǎn', + '诘屈磝碻' => ' jié qū bìng zhòu', + '一了百了' => ' yī liǎo bǎi liǎo', + '少不经事' => ' shào bù jīng shì', + '死亡枕藉' => ' sǐ wáng zhěn jiè', + '十字转门' => ' shí zì zhuàn mén', + '贼臣乱子' => ' zéi chén luàn zǐ', + '少年得志' => ' shào nián dé zhì', + '少小无猜' => ' shào xiǎo wú cāi', + '励精更始' => ' lì jīng gēng shǐ', + '舍身为国' => ' shě shēn wéi guó', + '揭箧担囊' => ' jiē qiè dān náng', + '丹瑞大将' => ' dān ruì dà jiàng', + '松柏之志' => ' sōng bǎi zhī zhì', + '反应堆芯' => ' fǎn yìng duī xīn', + '月没参横' => ' yuè mò shēn héng', + '折箭为誓' => ' shé jiàn wéi shì', + '作舍道边' => ' zuò shè dào biān', + '累见不鲜' => ' lěi jiàn bù xiǎn', + '立党为公' => ' lì dǎng wèi gōng', + '肆虐横行' => ' sì nüè héng xíng', + '长幼有序' => ' zhǎng yòu yǒu xù', + '旧话重提' => ' jiù huà chóng tí', + '如应斯响' => ' rú yìng sī xiǎng', + '捐身徇义' => ' juān shēn xùn yì', + '印象主义' => ' yìn xiàng zhǔ yì', + '转忧为喜' => ' zhuǎn yōu wéi xǐ', + '一笑了之' => ' yī xiào liǎo zhī', + '识微知著' => ' shí wēi zhī zhuó', + '日引月长' => ' rì yǐn yuè cháng', + '杼柚其空' => ' zhù zhóu qí kōng', + '杼柚空虚' => ' zhù zhóu kōng xū', + '祝咽祝哽' => ' zhù yān zhù gěng', + '世界冠军' => ' shì jiè guàn jūn', + '理性知识' => ' lǐ xìng zhī shi', + '圈牢养物' => ' juàn láo yǎng wù', + '开疆拓土' => ' kāi jiāng tuò tǔ', + '褚小怀大' => ' zhǔ xiǎo huái dà', + '一身两役' => ' yī shēn liǎng yì', + '日月参辰' => ' rì yuè shēn chén', + '祝鮀之佞' => ' zhù tuó zhī nìng', + '尸居龙见' => ' shī jū lóng xiàn', + '什袭珍藏' => ' shí xí zhēn cáng', + '助天为虐' => ' zhù tiān wéi nüè', + '融洽无间' => ' róng qià wú jiàn', + '柱石之坚' => ' zhù shí zhī jiān', + '收旗卷伞' => ' shōu qí juàn sǎn', + '转祸为福' => ' zhuǎn huò wéi fú', + '日久岁长' => ' rì jiǔ suì cháng', + '逐机应变' => ' zhú jī yìng biàn', + '珠宫贝阙' => ' zhū gōng bèi què', + '卷帙浩繁' => ' juàn zhì hào fán', + '助纣为虐' => ' zhù zhòu wéi nüè', + '多钱善贾' => ' duō qián shàn gǔ', + '神武挂冠' => ' shén wǔ guà guàn', + '劳动合同' => ' láo dòng hé tong', + '多重国籍' => ' duō chóng guó jí', + '身无长物' => ' shēn wú cháng wù', + '日长一线' => ' rì cháng yī xiàn', + '适应能力' => ' shì yìng néng lì', + '深切著白' => ' shēn qiè zhù bái', + '不了了之' => ' bù liǎo liǎo zhī', + '人自为政' => ' rén zì wéi zhèng', + '审曲面埶' => ' shěn qǔ miàn xīn', + '人中狮子' => ' rén zhōng shī zǐ', + '种瓜得瓜' => ' zhòng guā dé guā', + '种豆得豆' => ' zhòng dòu dé dòu', + '审曲面势' => ' shěn qǔ miàn shì', + '专横跋扈' => ' zhuān hèng bá hù', + '白苋紫茄' => ' bái xiàn zǐ qié', + '信息灵通' => ' xìn xi líng tōng', + '内线消息' => ' nèi xiàn xiāo xi', + '云兴霞蔚' => ' yún xīng xiá wèi', + '弹斤估两' => ' tán jīn gū liǎng', + '因缘为市' => ' yīn yuán wéi shì', + '应答如响' => ' yìng dá rú xiǎng', + '令原之戚' => ' líng yuán zhī qī', + '陵劲淬砺' => ' líng jìng cuì lì', + '弹丸脱手' => ' tán wán tuō shǒu', + '临难铸兵' => ' lín nàn zhù bīng', + '临难苟免' => ' lín nàn gǒu miǎn', + '临机应变' => ' lín jī yìng biàn', + '应用技术' => ' yìng yòng jì shù', + '应弦而倒' => ' yìng xián ér dǎo', + '流年似水' => ' liú nián sì shuǐ', + '钦差大臣' => ' qīn chāi dà chén', + '挈瓶之知' => ' qiè píng zhī zhì', + '引为鉴戒' => ' yǐn wéi jiàn jiè', + '弹性蛋白' => ' tán xìng dàn bái', + '人间佛教' => ' rén jiān fó jiào', + '人车混行' => ' rén chē hún xíng', + '印累绶若' => ' yìn léi shòu ruò', + '搂头盖脸' => ' lōu tóu gài liǎn', + '淫言媟语' => ' yín yán liǎng yǔ', + '凌云之志' => ' líng yún zhī zhì', + '铜筋铁肋' => ' tóng jīn tiě lèi', + '人满为患' => ' rén mǎn wéi huàn', + '呷醋节帅' => ' xiā cù jié shuài', + '啮血为盟' => ' niè xuè wéi méng', + '煞有介事' => ' shà yǒu jiè shì', + '先睹为快' => ' xiān dǔ wéi kuài', + '一言为定' => ' yī yán wéi dìng', + '破愁为笑' => ' pò chóu wéi xiào', + '攀蟾折桂' => ' pān chán shé guì', + '系统更新' => ' xì tǒng gēng xīn', + '农业银行' => ' nóng yè yín háng', + '倭黑猩猩' => ' wō hēi xīng xing', + '为山止篑' => ' wéi shān zhǐ kuì', + '衣被群生' => ' yì bèi qún shēng', + '熬更守夜' => ' áo gēng shǒu yè', + '牛听弹琴' => ' niú tīng tán qín', + '借尸还魂' => ' jiè shī huán hún', + '显露头角' => ' xiǎn lù tóu jiǎo', + '凶神恶煞' => ' xiōng shén è shà', + '相对误差' => ' xiāng duì wù chā', + '掊斗折衡' => ' pǒu dǒu zhé héng', + '扭亏为盈' => ' niǔ kuī wéi yíng', + '松筠之节' => ' sōng jūn zhī jié', + '漏尽更阑' => ' lòu jìn gēng lán', + '人民银行' => ' rén mín yín háng', + '内助之贤' => ' nèi zhù zhī xián', + '游闲公子' => ' yóu xián gōng zǐ', + '围魏救赵' => ' wéi wèi jiù zhào', + '量才而为' => ' liàng cái ér wéi', + '弹冠振衣' => ' tán guān zhèn yī', + '了无生趣' => ' liǎo wú shēng qù', + '燎如观火' => ' liǎo rú guān huǒ', + '事危累卵' => ' shì wēi lěi luǎn', + '撩火加油' => ' liáo huǒ jiā yóu', + '玉尺量才' => ' yù chǐ liáng cái', + '撩蜂剔蝎' => ' liáo fēng tì xiē', + '优游涵泳' => ' yōu yóu hán yǒng', + '语笑喧哗' => ' yǔ xiào xuān huá', + '情见势屈' => ' qíng xiàn shì qū', + '清静无为' => ' qīng jìng wú wéi', + '索尽枯肠' => ' suǒ jìn kū cháng', + '请假回家' => ' qǐng jià huí jiā', + '痛深恶绝' => ' tòng shēn wù jué', + '因果报应' => ' yīn guǒ bào yìng', + '韬光俟奋' => ' tāo guāng sì fèn', + '调和阴阳' => ' tiáo hé yīn yáng', + '调和鼎鼐' => ' tiáo hé dǐng nài', + '裂裳裹足' => ' liè cháng guǒ zú', + '情绪反应' => ' qíng xù fǎn yìng', + '袒裼裸裎' => ' tǎn xī luǒ chéng', + '情深似海' => ' qíng shēn sì hǎi', + '笼络人心' => ' lǒng luò rén xīn', + '龙举云兴' => ' lóng jǔ yún xīng', + '贪小便宜' => ' tān xiǎo pián yi', + '什一奉献' => ' shí yī fèng xiàn', + '瓮尽杯干' => ' wèng jìn bēi gān', + '埒才角妙' => ' liè cái jué miào', + '连编累牍' => ' lián biān lěi dú', + '团体冠军' => ' tuán tǐ guàn jūn', + '羽扇纶巾' => ' yǔ shàn guān jīn', + '取长补短' => ' qǔ cháng bǔ duǎn', + '渊涓蠖濩' => ' yuān juān huò hù', + '蠡酌管窥' => ' lǐ zhuó guǎn kuī', + '青林黑塞' => ' qīng lín hēi sài', + '例行差事' => ' lì xíng chāi shì', + '玉质金相' => ' yù zhì jīn xiàng', + '燕京啤酒' => ' yān jīng pí jǐu', + '远不间亲' => ' yuǎn bù jiàn qīn', + '燕京大学' => ' yān jīng dà xué', + '送子观音' => ' sòng zǐ guān yīn', + '地方停车' => ' dì fang tíng chē', + '充分就业' => ' chōng fèn jìu yè', + '普天率土' => ' pǔ tiān shuài tǔ', + '万家生佛' => ' wàn jiā shēng fó', + '千差万别' => ' qiān chā wàn bié', + '液晶电视' => ' yè jīng diàn shì', + '咬姜呷醋' => ' yǎo jiāng xiā cù', + '棋输先着' => ' qí shū xiān zhāo', + '明珠弹雀' => ' míng zhū tán què', + '千了万当' => ' qiān le wàn dàng', + '燕处焚巢' => ' yàn chǔ fén cháo', + '燕处危巢' => ' yàn chǔ wēi cháo', + '公共道德' => ' gōng gòng dào dé', + '其应如响' => ' qí yìng rú xiǎng', + '乾端坤倪' => ' qián duān kūn ní', + '民事调解' => ' mín shì tiáo jiě', + '抟沙嚼蜡' => ' tuán shā jiáo là', + '万别千差' => ' wàn bié qiān chā', + '燕骏千金' => ' yān jùn qiān jīn', + '前功尽弃' => ' qián gōng jìn qì', + '磨砻镌切' => ' mó lóng juān qiē', + '乾嘉学派' => ' qián jiā xué pài', + '言笑晏晏' => ' yán xiào yàn yàn', + '严刑逼供' => ' yán xíng bī gòng', + '同型配子' => ' tóng xíng pèi zǐ', + '妖由人兴' => ' yāo yóu rén xīng', + '咬血为盟' => ' yǎo xuè wéi méng', + '夜静更阑' => ' yè jìng gēng lán', + '望闻问切' => ' wàng wén wèn qiē', + '兴亡祸福' => ' xīng wáng huò fú', + '杀彘教子' => ' shā zhì jiào zǐ', + '哭丧着脸' => ' kū sāng zhe liǎn', + '行为不端' => ' xíng wéi bù duān', + '行为不检' => ' xíng wéi bù jiǎn', + '行为规范' => ' xíng wéi guī fàn', + '行为能力' => ' xíng wéi néng lì', + '晚食当肉' => ' wǎn shí dàng ròu', + '兴亡继绝' => ' xīng wáng jì jué', + '公共秩序' => ' gōng gòng zhì xù', + '同恶相恤' => ' tóng wù xiāng xù', + '言必有中' => ' yán bì yǒu zhòng', + '图穷匕见' => ' tú qióng bǐ xiàn', + '燕昭市骏' => ' yān zhāo shì jùn', + '言之过甚' => ' yán zhī guò shèn', + '佛朗机铳' => ' fó lǎng jī chòng', + '头童齿豁' => ' tóu tóng chǐ huò', + '冤家对头' => ' yuān jia duì tóu', + '明月入怀' => ' míng yuè rù huái', + '要价还价' => ' yào jià huán jià', + '无穷无尽' => ' wú qióng wú jìn', + '铺张扬厉' => ' pū zhāng yáng lì', + '免疫反应' => ' miǎn yì fǎn yìng', + '相鼠有皮' => ' xiàng shǔ yǒu pí', + '骈肩累足' => ' pián jiān lěi zú', + '片鳞只甲' => ' piàn lín zhī jiǎ', + '五行并下' => ' wǔ háng bìng xià', + '片文只事' => ' piàn wén zhī shì', + '和平共处' => ' hé píng gòng chǔ', + '傻瓜相机' => ' shǎ guā xiàng jī', + '未艾方兴' => ' wèi ài fāng xīng', + '夕寐宵兴' => ' xī mèi xiāo xīng', + '值得称赞' => ' zhí de chēng zàn', + '小廉曲谨' => ' xiǎo lián qǔ jǐn', + '无量寿佛' => ' wú liàng shòu fó', + '未了公案' => ' wèi liǎo gōng àn', + '孝子顺孙' => ' xiào zǐ shùn sūn', + '孝子贤孙' => ' xiào zǐ xián sūn', + '橡皮钉子' => ' xiàng pí dìng zǐ', + '能者为师' => ' néng zhě wéi shī', + '邪不干正' => ' xié bù gān zhèng', + '协调发展' => ' xié tiáo fā zhǎn', + '心瞻魏阙' => ' xīn zhān wèi què', + '兴邦立国' => ' xīng bāng lì guó', + '骈肩累迹' => ' pián jiān lěi jì', + '皮相之谈' => ' pí xiàng zhī tán', + '兴词构讼' => ' xīng cí gòu sòng', + '五行生克' => ' wǔ háng shēng kè', + '一笑置之' => ' yī xiào zhì zhī', + '温席扇枕' => ' wēn xí shān zhěn', + '偶联反应' => ' ǒu lián fǎn yìng', + '停酒止乐' => ' tíng jǐu zhǐ yuè', + '停车位置' => ' tíng chē wèi zhi', + '闻风而兴' => ' wén fēng ér xīng', + '文行出处' => ' wén xíng chū chǔ', + '贫困潦倒' => ' pín kùn liáo dǎo', + '频率成分' => ' pín lǜ chéng fèn', + '五行相克' => ' wǔ háng xiāng kè', + '命名作业' => ' mìng míng zuò yè', + '雾兴云涌' => ' wù xīng yún yǒng', + '凭几据杖' => ' píng jī jù zhàng', + '味同嚼蜡' => ' wèi tóng jiáo là', + '恶醉强酒' => ' wù zuì qiǎng jiǔ', + '诬良为盗' => ' wū liáng wéi dào', + '温枕扇席' => ' wēn zhěn shān xí', + '片长薄技' => ' piàn cháng bó jì', + '片甲不还' => ' piàn jiǎ bù huán', + '无肠公子' => ' wú cháng gōng zǐ', + '少不更事' => ' shào bù gēng shì', + '取长弃短' => ' qǔ cháng qì duǎn', + '扪参历井' => ' mén shēn lì jǐng', + '洋底地壳' => ' yáng dǐ dì qiào', + '一之为甚' => ' yī zhī wéi shèn', + '百喙莫辩' => ' bǎi huì mò biàn', + '没羞没臊' => ' méi xīu méi sào', + '分毫之差' => ' fēn háo zhī chā', + '一之谓甚' => ' yī zhī wèi shèn', + '提起精神' => ' tí qǐ jīng shen', + '白发红颜' => ' bái fà hóng yán', + '白发苍颜' => ' bái fà cāng yán', + '熟思审处' => ' shú sī shěn chǔ', + '分解代谢' => ' fēn jiě dài xiè', + '泄露天机' => ' xiè lòu tiān jī', + '束身自好' => ' shù shēn zì hào', + '八方呼应' => ' bā fāng hū yìng', + '束戈卷甲' => ' shù gē juàn jiǎ', + '八方风雨' => ' bā fāng fēng yǔ', + '没有关系' => ' méi yǒu guān xi', + '没有什么' => ' méi yǒu shén me', + '调皮捣蛋' => ' tiáo pí dǎo dàn', + '熬姜呷醋' => ' áo jiāng xiā cù', + '作歹为非' => ' zuò dǎi wéi fēi', + '五倍子虫' => ' wǔ bèi zǐ chóng', + '朝攀暮折' => ' zhāo pān mù shé', + '安守本分' => ' ān shǒu běn fèn', + '为时过早' => ' wéi shí guò zǎo', + '为善最乐' => ' wéi shàn zuì lè', + '朝华夕秀' => ' zhāo huá xī xiù', + '朝欢暮乐' => ' zhāo huān mù lè', + '为德不终' => ' wéi dé bù zhōng', + '为时未晚' => ' wéi shí wèi wǎn', + '等差级数' => ' děng chā jí shù', + '竹篮打水' => ' zhú lán dá shuǐ', + '枕曲藉糟' => ' zhěn qū jiè zāo', + '竭尽全力' => ' jié jìn quán lì', + '一念之差' => ' yī niàn zhī chā', + '私生子女' => ' sī shēng zǐ nǔ:', + '一弹指顷' => ' yī tán zhǐ qǐng', + '朝折暮折' => ' zhāo shé mù shé', + '一矢中的' => ' yī shǐ zhòng dì', + '没有差别' => ' méi yǒu chā bié', + '空地导弹' => ' kòng dì dǎo dàn', + '中子俘获' => ' zhōng zǐ fú huò', + '左右为难' => ' zuǒ yòu wéi nán', + '积年累月' => ' jī nián lěi yuè', + '移山倒海' => ' yí shān dǎo hǎi', + '一年半载' => ' yī nián bàn zǎi', + '坐不重席' => ' zuò bù chóng xí', + '炸土豆条' => ' zhá tǔ dòu tiáo', + '一年到头' => ' yì nián dào tóu', + '书本知识' => ' shū běn zhī shi', + '长目飞耳' => ' cháng mù fēi ěr', + '虱胫虮肝' => ' shī jìng jī gān', + '歪歪倒倒' => ' wāi wāi dǎo dǎo', + '脱袍退位' => ' tuō páo tuì wèi', + '拖麻拽布' => ' tuō má zhuài bù', + '魂不守舍' => ' hún bù shǒu shè', + '长虑却顾' => ' cháng lǜ què gù', + '总体要求' => ' zǒng tǐ yāo qiú', + '屯蹶否塞' => ' tún juě fǒu sāi', + '疏不间亲' => ' shū bù jiàn qīn', + '推轮捧毂' => ' tuī lún pěng gū', + '长虑后顾' => ' cháng lǜ hòu gù', + '长虑顾后' => ' cháng lǜ gù hòu', + '渣打银行' => ' zhā dǎ yín háng', + '失马塞翁' => ' shī mǎ sài wēng', + '什围伍攻' => ' shí wéi wǔ gōng', + '兔角龟毛' => ' tu jiao gui mao', + '安时处顺' => ' ān shí chǔ shùn', + '投资银行' => ' tóu zī yín háng', + '淡泊明志' => ' dàn bó míng zhì', + '百载树人' => ' bǎi zǎi shù rén', + '调墨弄笔' => ' tiáo mò nòng bǐ', + '一多对应' => ' yī duō duì yìng', + '树倒根摧' => ' shù dǎo gēn cuī', + '天干地支' => ' tiān gān dì zhī', + '百口莫辩' => ' bǎi kǒu mò biàn', + '参伍错综' => ' cēn wǔ cuò zōng', + '束缊还妇' => ' shù yūn huán fù', + '浪卡子县' => ' làng kǎ zǐ xiàn', + '田父献曝' => ' tián fǔ xiàn pù', + '草头天子' => ' cǎo tóu tiān zǐ', + '浪子回头' => ' làng zǐ huí tóu', + '梳云掠月' => ' shū yún lüě yuè', + '一天到晚' => ' yì tiān dào wǎn', + '沈鱼落雁' => ' chén yú luò yàn', + '苍颜白发' => ' cāng yán bái fà', + '恬淡无为' => ' tián dàn wú wéi', + '百身莫赎' => ' bǎi shēn mò shú', + '刚正不阿' => ' gāng zhèng bù ē', + '切向速度' => ' qiē xiàng sù dù', + '腾腾兀兀' => ' téng téng wū wū', + '白云亲舍' => ' bái yún qīn shè', + '排难解纷' => ' pái nàn jiě fēn', + '炸土豆片' => ' zhá tǔ dòu piàn', + '一无所长' => ' yī wú suǒ cháng', + '鼓鼓囊囊' => ' gǔ gǔ nāng nāng', + '与世长辞' => ' yǔ shì cháng cí', + '考试卷子' => ' kǎo shì juàn zi', + '优游不断' => ' yōu yóu bù duàn', + '优游岁月' => ' yōu yóu suì yuè', + '腹笥便便' => ' fù sì pián pián', + '圣德太子' => ' shèng dé tài zǐ', + '兴灭继绝' => ' xīng miè jì jué', + '有志之士' => ' yǒu zhì zhī shì', + '兴旺发达' => ' xīng wàng fā dá', + '与人为善' => ' yǔ rén wéi shàn', + '原始积累' => ' yuán shǐ jī lěi', + '老少皆宜' => ' lǎo shào jiē yí', + '与世无争' => ' yú shì wú zhēng', + '果实累累' => ' guǒ shí lěi lěi', + '自视甚高' => ' zì shì shèn gāo', + '与民更始' => ' yǔ rén gēng shǐ', + '胆大妄为' => ' dǎn dà wàng wéi', + '冤家路狭' => ' yuān jia lù xiá', + '不可胜数' => ' bù kě shèng shǔ', + '九齿钉耙' => ' jǐu chǐ dīng pá', + '予智予雄' => ' yú zhì yú xióng', + '有以善处' => ' yǒu yǐ shàn chǔ', + '老两口儿' => ' lǎo liǎng kǒu r', + '组成部分' => ' zǔ chéng bù fen', + '曾母投杼' => ' zēng mǔ tóu zhù', + '习以为常' => ' xí yǐ wéi cháng', + '置之脑后' => ' zhì zhī nǎo hòu', + '匀速运动' => ' yún sù yùn dòng', + '核反应堆' => ' hé fǎn yìng duī', + '月晕而风' => ' yuè yùn ér fēng', + '早占勿药' => ' zǎo zhān wù yào', + '网络铁路' => ' wǎng luò tiě lù', + '干净俐落' => ' gān jìng lì luò', + '月夜花朝' => ' yuè yè huā zhāo', + '脱骨成佛' => ' tuō gǔ chéng fó', + '东倒西歪' => ' dōng dǎo xī wāi', + '干净利落' => ' gān jìng lì luò', + '腹笥甚宽' => ' fù sì shèn kuān', + '于无声处' => ' yú wú shēng chǔ', + '芝麻小事' => ' zhī ma xiǎo shì', + '褎如充耳' => ' yòu rú chōng ěr', + '月夕花朝' => ' yuè xī huā zhāo', + '事怕行家' => ' shì pà háng jiā', + '事在人为' => ' shì zài rén wéi', + '有所作为' => ' yǒu suǒ zuò wéi', + '脊椎动物' => ' jǐ zhuī dòng wù', + '指不胜偻' => ' zhǐ bù shèng lǚ', + '澎湖群岛' => ' péng hú qún dǎo', + '子丑寅卯' => ' zǐ chǒu yín mǎo', + '绝食抗议' => ' jué shí kàng yì', + '溘先朝露' => ' kè xiān zhāo lù', + '氢氧离子' => ' qīng yǎng lí zǐ', + '氢原子核' => ' qīng yuán zǐ hé', + '灭此朝食' => ' miè cǐ zhāo shí', + '神出鬼没' => ' shén chū guǐ mò', + '一技之长' => ' yī jì zhī cháng', + '礼废乐崩' => ' lǐ fèi yuè bēng', + '氧乙炔焊' => ' yǎng yǐ quē hàn', + '绍兴地区' => ' shào xīng dì qū', + '置锥之地' => ' zhì zhuī zhī dì', + '漏脯充饥' => ' lòu fǔ chōng jī', + '细嚼慢咽' => ' xì jiáo màn yàn', + '瞻予马首' => ' zhān yú mǎ shǒu', + '朝歌暮弦' => ' zhāo gē mù xián', + '追趋逐耆' => ' zhuī qū zhú shì', + '洁身自好' => ' jié shēn zì hào', + '朝歌夜弦' => ' zhāo gē yè xián', + '子孙满堂' => ' zǐ sūn mǎn táng', + '澎湖列岛' => ' péng hú liè dǎo', + '知识产业' => ' zhī shi chǎn yè', + '橡子面儿' => ' xiàng zi miàn r', + '尽人皆知' => ' jìn rén jiē zhī', + '花朝月夕' => ' huā zhāo yuè xī', + '百废具兴' => ' bǎi fèi jù xīng', + '石头火锅' => ' shí tou huǒ guō', + '不省人事' => ' bù xǐng rén shì', + '乐善好施' => ' lè shàn hào shī', + '栉风酾雨' => ' zhì fēng shī yǔ', + '止戈兴仁' => ' zhǐ gē xīng rén', + '瞎子摸象' => ' xiā zǐ mō xiàng', + '不为所动' => ' bù wéi suǒ dòng', + '不求甚解' => ' bù qiú shèn jiě', + '相体裁衣' => ' xiàng tǐ cái yī', + '尽收眼底' => ' jìn shōu yǎn dǐ', + '不着边际' => ' bù zhuó biān jì', + '指山卖磨' => ' zhǐ shān mài mò', + '民间故事' => ' mín jiān gù shi', + '知识结构' => ' zhī shi jié gòu', + '直系血亲' => ' zhí xì xuè qìng', + '不定冠词' => ' bù dìng guàn cí', + '目下十行' => ' mù xià shí háng', + '指皂为白' => ' zhǐ zào wéi bái', + '尽欢而散' => ' jìn huān ér sàn', + '黎明时分' => ' lí míng shí fèn', + '爱出风头' => ' ài chū fēng tou', + '扇枕温席' => ' shān zhěn wēn xí', + '欺人太甚' => ' qī rén tài shèn', + '神号鬼哭' => ' shén háo guǐ kū', + '玩儿不转' => ' wán ér bù zhuàn', + '畏首畏尾' => ' wèi shǒu wèi wěi', + '为国为民' => ' wéi guó wéi mín', + '首要任务' => ' shǒu yào rèn wu', + '为蛇添足' => ' wéi shé tiān zú', + '饶舌音乐' => ' ráo shé yīn yuè', + '番茄红素' => ' fān qié hóng sù', + '为数不少' => ' wéi shù bù shǎo', + '电动葫芦' => ' diàn dòng hú lu', + '神至之笔' => ' shén zhì zhī bǐ', + '北斗七星' => ' běi dǒu qī xīng', + '超然独处' => ' chāo rán dú chǔ', + '异曲同工' => ' yì qǔ tóng gōng', + '巢居穴处' => ' cháo jū xué chǔ', + '畏威怀德' => ' wèi wēi huái dé', + '化险为夷' => ' huà xiǎn wéi yí', + '权利要求' => ' quán lì yāo qíu', + '昼乾夕惕' => ' zhòu qián xī tì', + '违法行为' => ' wéi fǎ xíng wéi', + '故地重游' => ' gù dì chóng yóu', + '操作系统' => ' cāo zuō xì tǒng', + '诗肠鼓吹' => ' shī chá gǔ chuì', + '电磁干扰' => ' diàn cí gān rǎo', + '电子器件' => ' diàn zǐ qì jiàn', + '威望素著' => ' wēi wàng sù zhù', + '长揖不拜' => ' cháng yī bù bài', + '光禄大夫' => ' guāng lù dài fu', + '鞠躬尽瘁' => ' jū gōng jìn cuì', + '长驱直入' => ' cháng qū zhí rù', + '文过遂非' => ' wén guò suí fēi', + '男性厌恶' => ' nán xìng yàn wù', + '颠三倒四' => ' diān sān dǎo sì', + '深文曲折' => ' shēn wén qǔ shé', + '枉曲直凑' => ' wǎng qǔ zhí còu', + '汇业银行' => ' huì yè yín háng', + '颠来倒去' => ' diān lái dǎo qù', + '放不下心' => ' fàng bu xià xīn', + '放下包袱' => ' fàng xia bāo fu', + '文房四侯' => ' wén fáng sì hòu', + '先入为主' => ' xiān rù wéi zhǔ', + '危于累卵' => ' wēi yú lěi luǎn', + '望风披靡' => ' wàng fēng pī mǐ', + '长途奔袭' => ' cháng tú bēn xí', + '擢发难数' => ' zhuó fà nán shǔ', + '深厉浅揭' => ' shēn lì qiǎn qì', + '委委佗佗' => ' wēi wēi tuó tuó', + '裙屐少年' => ' qún jī shào nián', + '旷日累时' => ' kuàng rì lěi shí', + '瑟调琴弄' => ' sè tiáo qín nòng', + '前半晌儿' => ' qián bàn shǎng r', + '久安长治' => ' jiǔ ān cháng zhì', + '尽忠竭力' => ' jìn zhōng jié lì', + '深谷为陵' => ' shēn gǔ wéi líng', + '朝生暮死' => ' zhāo shēng mù sǐ', + '朝升暮合' => ' zhāo shēng mù gě', + '总会计师' => ' zǒng kuài jì shī', + '狼子兽心' => ' láng zǐ shòu xīn', + '款曲周至' => ' kuǎn qǔ zhōu zhì', + '杀妻求将' => ' shā qī qiú jiàng', + '曲终人散' => ' qǔ zhōng rén sàn', + '朝令暮改' => ' zhāo lìng mù gǎi', + '朝经暮史' => ' zhāo jīng mù shǐ', + '朝生夕死' => ' zhāo shēng xī sǐ', + '朝趁暮食' => ' zhāo chèn mù shí', + '施工合同' => ' shī gōng hé tong', + '太古洋行' => ' tài gǔ yáng háng', + '似水流年' => ' sì shuǐ liú nián', + '子夏悬鹑' => ' zǐ xià xuán chún', + '朝衣东市' => ' zhāo yī dōng shì', + '十夫楺椎' => ' shí fū zhī zhuī', + '朝更暮改' => ' zhāo gèng mù gǎi', + '生死未卜' => ' shēng sǐ wèi bǔ', + '长途直拨' => ' cháng tú zhí bō', + '十夫桡椎' => ' shí fū ráo zhuī', + '审己度人' => ' shěn jǐ duó rén', + '食物中毒' => ' shí wù zhòng dú', + '十四行诗' => ' shí sì háng shī', + '收拾残局' => ' shōu shi cán jú', + '进退中度' => ' jìn tuì zhòng dù', + '善财童子' => ' shàn cái tóng zǐ', + '周郎顾曲' => ' zhōu láng gù qǔ', + '瑟弄琴调' => ' sè nòng qín tiáo', + '曲意承迎' => ' qǔ yì chéng yíng', + '累牍连篇' => ' lěi dú lián piān', + '硕望宿德' => ' shuò wàng xiǔ dé', + '金相玉质' => ' jīn xiàng yù zhì', + '钻冰取火' => ' zuàn bīng qǔ huǒ', + '纵曲枉直' => ' zòng qǔ wǎng zhí', + '走为上策' => ' zǒu wéi shàng cè', + '孀妻弱子' => ' shuāng qī ruò zǐ', + '博闻强识' => ' bó wén qiáng zhì', + '风尘仆仆' => ' fēng chén pú pú', + '优哉游哉' => ' yōu zāi yóu zāi', + '长恶靡悛' => ' cháng è mǐ quān', + '男孩乐队' => ' nán hái yuè duì', + '不了而了' => ' bù liǎo ér liǎo', + '拜人为师' => ' bài rén wéi shī', + '不了不当' => ' bù liǎo bù dàng', + '变危为安' => ' biàn wēi wéi ān', + '遂心快意' => ' suí xīn kuài yì', + '遂迷忘反' => ' suí mí wàng fǎn', + '拾级而上' => ' shè jí ér shàng', + '薄弱环节' => ' bó ruò huán jié', + '薄命佳人' => ' bó mìng jiā rén', + '挑拨离间' => ' tiǎo bō lí jiàn', + '泰然处之' => ' tài rán chǔ zhī', + '理性认识' => ' lǐ xìng rèn shi', + '挑拨是非' => ' tiǎo bō shì fēi', + '屏气敛息' => ' bǐng qì liǎn xī', + '遂非文过' => ' suí fēi wén guò', + '拨乱为治' => ' bō luàn wéi zhì', + '肆意横行' => ' sì yì héng xíng', + '夙兴昧旦' => ' sù xīng mèi dàn', + '宋词元曲' => ' sòng cí yuán qǔ', + '初露头角' => ' chū lù tóu jiǎo', + '标记原子' => ' biāo jì yuán zǐ', + '泰来否往' => ' tài lái pǐ wǎng', + '韬戈卷甲' => ' tāo gē juàn jiǎ', + '投传而去' => ' tóu zhuàn ér qù', + '通同一气' => ' tōng tóng yī yì', + '长恶不悛' => ' cháng è bù quān', + '实验研究' => ' shí yàn yán jiū', + '同工异曲' => ' tóng gōng yì qǔ', + '为非作歹' => ' wéi fēi zuò dǎi', + '调朱傅粉' => ' tiáo zhū fù fěn', + '力尽神危' => ' lì jìn shén wēi', + '出没无常' => ' chū mò wú cháng', + '并为一谈' => ' bìng wéi yī tán', + '无以为生' => ' wú yǐ wéi shēng', + '嗒然若丧' => ' tà rán ruò sàng', + '首都领地' => ' shǒu dū lǐng dì', + '排忧解难' => ' pái yōu jiě nàn', + '隋珠弹雀' => ' suí zhū tán què', + '别无长物' => ' bié wú cháng wù', + '百废俱兴' => ' bǎi fèi jù xīng', + '别开蹊径' => ' bié kāi xī jìng', + '别别扭扭' => ' biè biè niú niu', + '标题音乐' => ' biāo tí yīn yuè', + '岁序更新' => ' suì xù gēng xīn', + '不遑启处' => ' bù huáng qǐ chǔ', + '叹观止矣' => ' tàn guān zhǐ yǐ', + '男女老少' => ' nán nǚ lǎo shào', + '鞭辟着里' => ' biān bì zhuó lǐ', + '捏脊治疗' => ' niē jǐ zhì liáo', + '切磋琢磨' => ' qiē cuō zhuó mó', + '李氏朝鲜' => ' lǐ shì cháo xiǎn', + '本本分分' => ' běn běn fèn fēn', + '本相毕露' => ' běn xiàng bì lù', + '碧波荡漾' => ' bì bō dàng yàng', + '蔽聪塞明' => ' bì cōnɡ sè mínɡ', + '闭明塞聪' => ' bì míng sè cōng', + '四方之志' => ' sì fāng zhī zhì', + '北门锁钥' => ' běi mén suǒ yuè', + '欧阳予倩' => ' oū yáng yú qiàn', + '十行俱下' => ' shí háng jù xià', + '调丝品竹' => ' tiáo sī pǐn zhú', + '用尽心机' => ' yòng jìn xīn jī', + '包皮环切' => ' bāo pí huán qiē', + '电子工业' => ' diàn zǐ gōng yè', + '蛇蝎为心' => ' shé xiē wéi xīn', + '唯唯否否' => ' wěi wěi fǒu fǒu', + '兆电子伏' => ' zhào diàn zǐ fú', + '深恶痛疾' => ' shēn wù tòng jí', + '铄古切今' => ' shuò gǔ qiē jīn', + '顺蔓摸瓜' => ' shùn wàn mō guā', + '弹剑作歌' => ' tán jiàn zuò gē', + '卑辞重币' => ' bēi cí zhòng bì', + '暴露文学' => ' bào lòu wén xué', + '唐虞之治' => ' táng yú zhī zhì', + '才占八斗' => ' cái zhān bā dǒu', + '主客颠倒' => ' zhǔ kè diān dǎo', + '助桀为虐' => ' zhù jié wéi nüè', + '采购合同' => ' cǎi gòu hé tong', + '抱柱之信' => ' bào zhù zhī xìn', + '抱子弄孙' => ' bào zǐ nòng sūn', + '悲悲切切' => ' bēi bēi qiè qiē', + '助桀为暴' => ' zhù jié wéi bào', + '削职为民' => ' xuē zhí wéi mín', + '北斗之尊' => ' běi dǒu zhī zūn', + '甚感诧异' => ' shèn gǎn chà yì', + '前半天儿' => ' qián bàn tiān r', + '才薄智浅' => ' cāi bó zhì qiǎn', + '不知颠倒' => ' bù zhī diān dǎo', + '不着疼热' => ' bù zhuó téng rè', + '削铅笔机' => ' xiāo qiān bǐ jī', + '铸剑为犁' => ' zhù jiàn wéi lí', + '背曲腰躬' => ' bèi qǔ yāo gōng', + '捧腹绝倒' => ' pěng fù jué dǎo', + '丧失殆尽' => ' sàng shī dài jìn', + '束缊请火' => ' shù yùn qǐng huǒ', + '调词架讼' => ' tiáo cí jià sòng', + '苍劲有力' => ' cāng jìng yǒu lì', + '目挑心招' => ' mù tiǎo xīn zhāo', + '拓扑空间' => ' tuò pū kōng jiān', + '流行音乐' => ' liú xíng yīn yuè', + '拐弯抹角' => ' guǎi wān mò jiǎo', + '长舌之妇' => ' cháng shé zhī fù', + '找不着北' => ' zhǎo bu zháo běi', + '惨绿少年' => ' cǎn lǜ shào nián', + '长驱直进' => ' cháng qū zhí jìn', + '称心快意' => ' chèn xīn kuài yì', + '沦没丧亡' => ' lún mò sàng wáng', + '材高知深' => ' cái gāo zhì shēn', + '长驱深入' => ' cháng qū shēn rù', + '抽丝剥茧' => ' chōu sī bāo jiǎn', + '长此以往' => ' cháng cǐ yǐ wǎng', + '长短不一' => ' cháng duǎn bù yī', + '长歌当哭' => ' cháng gē dàng kū', + '奉为圭臬' => ' fèng wéi guī niè', + '牢靠妥当' => ' láo kào tuǒ dàng', + '晨兴夜寐' => ' chén xīng yè mèi', + '矫国更俗' => ' jiǎo guó gēng sú', + '冲冠怒发' => ' chōng guàn nù fà', + '重规累矩' => ' chóng guī lèi jǔ', + '吃斋念佛' => ' chī zhāi niàn fó', + '霸道横行' => ' bà dào héng xíng', + '疾风劲草' => ' jí fēng jìng cǎo', + '当行出色' => ' dāng háng chū sè', + '拜倒辕门' => ' bài dǎo yuán mén', + '饥冻交切' => ' jī dòng jiāo qiē', + '白发苍苍' => ' bái fà cāng cāng', + '冲冠发怒' => ' chōng guàn fā nù', + '槟榔西施' => ' bīng lang xī shī', + '威慑力量' => ' wēi shè lì liang', + '威化饼干' => ' wēi huà bǐng gān', + '白发青衫' => ' bái fà qīng shān', + '机关算尽' => ' jī guān suàn jìn', + '白黑颠倒' => ' bái hēi diān dǎo', + '手不释卷' => ' shǒu bù shì juàn', + '扬长而去' => ' yáng cháng ér qù', + '舂容大雅' => ' chōng róng dà yá', + '长戟高门' => ' cháng jǐ gāo mén', + '长计远虑' => ' cháng jì yuǎn lǜ', + '重迹屏气' => ' chóng jì bǐng qì', + '招人喜欢' => ' zhāo rén xǐ huan', + '屏退左右' => ' bǐng tuì zuǒ yòu', + '活神仙似' => ' huó shén xiān sì', + '泰山北斗' => ' tài shān běi dǒu', + '薄寒中人' => ' bó hán zhòng rén', + '长夜之饮' => ' cháng yè zhī yǐn', + '长吁短气' => ' cháng xū duǎn qì', + '不差上下' => ' bù chā shàng xià', + '法国长棍' => ' fǎ guó cháng gùn', + '倡条冶叶' => ' chāng tiáo yě yè', + '不相为谋' => ' bù xiāng wéi móu', + '夹脚拖鞋' => ' jiá jiǎo tuō xié', + '深恶痛绝' => ' shēn wù tòng jué', + '长斋礼佛' => ' cháng zhāi lǐ fó', + '隔行扫描' => ' gé háng sǎo miáo', + '恶语中伤' => ' è yǔ zhòng shāng', + '收购要约' => ' shōu gòu yāo yuē', + '电磁感应' => ' diàn cí gǎn yìng', + '测量工具' => ' cè liáng gōng jù', + '没完没了' => ' méi wán méi liǎo', + '派拉蒙影' => ' pài lā měng yǐng', + '液晶显示' => ' yè jīng xiǎn shì', + '别创一格' => ' bié chuàng yí gé', + '长久之计' => ' cháng jiǔ zhī jì', + '层台累榭' => ' céng tái lěi xiè', + '表面处理' => ' biǎo miàn chǔ lǐ', + '材薄质衰' => ' cái bó zhì shuāi', + '畅所欲为' => ' chàng suǒ yù wéi', + '消息来源' => ' xiāo xi lái yuán', + '角巾东路' => ' jiǎo jīn dōng lù', + '海盗行为' => ' hǎi dào xíng wéi', + '矫情干誉' => ' jiǎo qíng gān yù', + '屏声息气' => ' bǐng shēng xī qì', + '趁水和泥' => ' chèn shuǐ huò ní', + '宾主尽欢' => ' bīn zhǔ jìn huān', + '挑灯拨火' => ' tiǎo dēng bō huǒ', + '深更半夜' => ' shēn gēng bàn yè', + '扯篷拉纤' => ' chě péng lā qiàn', + '超今冠古' => ' chāo jīn guàn gǔ', + '冰山易倒' => ' bīng shān yì dǎo', + '胶柱调瑟' => ' jiāo zhù tiáo sè', + '戳脊梁骨' => ' chuō jǐ liang gǔ', + '安老怀少' => ' ān lǎo huái shào', + '电邮位置' => ' diàn yóu wèi zhi', + '百中百发' => ' bǎi zhòng bǎi fā', + '词优效应' => ' cí yōu xiào yìng', + '万古长新' => ' wàn gǔ cháng xīn', + '阶级成分' => ' jiē jí chéng fèn', + '随机应变' => ' suí jī yìng biàn', + '缓发中子' => ' huǎn fā zhōng zǐ', + '排起长队' => ' pái qǐ cháng duì', + '购销合同' => ' gòu xiāo hé tong', + '长眠不起' => ' cháng mián bù qǐ', + '紧急应变' => ' jǐn jí yìng biàn', + '宽大为怀' => ' kuān dà wéi huái', + '好整以暇' => ' hào zhěng yǐ xiá', + '排山倒海' => ' pái shān dǎo hǎi', + '甲壳动物' => ' jiǎ qiào dòng wù', + '积微成著' => ' jī wēi chéng zhù', + '正人君子' => ' zhèng rén jūn zǐ', + '击中要害' => ' jī zhòng yào hài', + '抱关执钥' => ' bào guān zhí yuè', + '聚变反应' => ' jù biàn fǎn yìng', + '调味肉汁' => ' tiáo wèi ròu zhī', + '价格行情' => ' jià gé háng qíng', + '认知失调' => ' rèn zhī shī tiáo', + '亲朋好友' => ' qīn péng háo you', + '亲生子女' => ' qīn shēng zǐ nǔ:', + '兴高彩烈' => ' xīng gāo cǎi liè', + '有两下子' => ' yǒu liǎng xià zǐ', + '解铃系铃' => ' jiě líng jì líng', + '调嘴学舌' => ' tiáo zuǐ xué shé', + '讨价还价' => ' tǎo jià huán jià', + '词频效应' => ' cí pín xiào yìng', + '飞灾横祸' => ' fēi zāi hèng huò', + '国难当头' => ' guó nàn dāng tóu', + '良家女子' => ' liáng jiā nǔ: zǐ', + '非意相干' => ' fēi yì xiāng gān', + '谁人乐队' => ' shéi rén yuè duì', + '草草了事' => ' cǎo cǎo liǎo shì', + '贡禹弹冠' => ' gòng yǔ tán guān', + '小缸缸儿' => ' xiǎo gāng gang r', + '公子王孙' => ' gōng zǐ wáng sūn', + '柔情似水' => ' róu qíng sì shuǐ', + '包皮过长' => ' bāo pí guò cháng', + '现代音乐' => ' xiàn dài yīn yuè', + '重门击柝' => ' chóng mén jī tuò', + '机关用尽' => ' jī guān yòng jìn', + '捋臂揎拳' => ' luō bì xuān quán', + '婚姻调解' => ' hūn yīn tiáo jiě', + '火尽灰冷' => ' huǒ jìn huī lěng', + '百般刁难' => ' bǎi bān diāo nàn', + '百发百中' => ' bǎi fā bǎi zhòng', + '成分分析' => ' chéng fèn fēn xī', + '百废待兴' => ' bǎi fèi dài xīng', + '沙加缅度' => ' shā jiā miǎn duó', + '鞭辟向里' => ' biān bì xiàng lǐ', + '成风尽垩' => ' chéng fēng jìn è', + '发言中肯' => ' fā yán zhòng kěn', + '鞭长不及' => ' biān cháng bù jí', + '变废为宝' => ' biàn fèi wéi bǎo', + '北门管钥' => ' běi mén guǎn yuè', + '表里为奸' => ' biǎo lǐ wéi jiān', + '百下百着' => ' bǎi xià bǎi zháo', + '充分发挥' => ' chōng fèn fā huī', + '重葩累藻' => ' chóng pā lèi zǎo', + '尽善尽美' => ' jìn shàn jìn měi', + '霍尔效应' => ' huò ěr xiào yìng', + '正月初一' => ' zhēng yuè chū yī', + '舍车保帅' => ' shě jū bǎo shuài', + '夹心饼干' => ' jiā xīn bǐng gān', + '赍粮藉寇' => ' jī liáng jiè kòu', + '俭不中礼' => ' jiǎn bù zhòng lǐ', + '抱蔓摘瓜' => ' bào wàn zhāi guā', + '正确处理' => ' zhèng què chǔ lǐ', + '正负电子' => ' zhèng fù diàn zǐ', + '犯罪行为' => ' fàn zuì xíng wéi', + '武装分子' => ' wǔ zhuāng fèn zǐ', + '卑宫菲食' => ' bēi gōng fěi shí', + '贝阙珠宫' => ' bèi què zhū gōng', + '见哭兴悲' => ' jiàn kū xīng bēi', + '简能而任' => ' jiǎn néng ér rèn', + '歃血为盟' => ' shà xuè wéi méng', + '槛花笼鹤' => ' jiàn huā lóng hè', + '见义当为' => ' jiàn yì dāng wéi', + '白衣宰相' => ' bái yī zǎi xiàng', + '横行霸道' => ' héng xíng bà dào', + '乘间伺隙' => ' chéng jiān sì xì', + '催化反应' => ' cuī huà fǎn yìng', + '摧刚为柔' => ' cuī gāng wéi róu', + '亲子鉴定' => ' qīn zǐ jiàn dìng', + '审时度势' => ' shěn shí duó shì', + '香榭丽舍' => ' xiāng xiè lì shè', + '科教兴国' => ' kē jiào xīng guó', + '毫发不爽' => ' háo fà bù shuǎng', + '桁杨刀锯' => ' háng yáng dāo jù', + '揣时度力' => ' chuǎi shí duó lì', + '华夏银行' => ' huá xià yín háng', + '转悲为喜' => ' zhuǎn bēi wéi xǐ', + '累加总数' => ' lěi jiā zǒng shù', + '好勇斗狠' => ' hào yǒng dòu hěn', + '鹄面鸠形' => ' hú miàn jiū xíng', + '化枭为鸠' => ' huà xiāo wéi jiū', + '哗世取宠' => ' huá shì qǔ chǒng', + '红颜薄命' => ' hóng yán bó mìng', + '化鸱为凤' => ' huà chī wéi fèng', + '孔子学院' => ' kǒng zǐ xué yuàn', + '鹄峙鸾停' => ' hú zhì luán tíng', + '审度时势' => ' shěn duó shí shì', + '触类而长' => ' chù lèi ér cháng', + '鹤短凫长' => ' hè duǎn fú cháng', + '行伍出身' => ' háng wǔ chū shēn', + '飞来横祸' => ' fēi lái hèng huò', + '颠倒是非' => ' diān dǎo shì fēi', + '颠倒过来' => ' diān dǎo guò lái', + '颠倒黑白' => ' diān dǎo hēi bái', + '风土人情' => ' fēng tú rén qíng', + '站不住脚' => ' zhàn bu zhù jiǎo', + '穷于应付' => ' qióng yú yìng fù', + '风雨漂摇' => ' fēng yǔ piāo yáo', + '完全懂得' => ' wán quán dǒng de', + '横行逆施' => ' héng xíng nì shī', + '首都机场' => ' shǒu dū jī chǎng', + '骄阳似火' => ' jiāo yáng sì huǒ', + '养尊处优' => ' yǎng zūn chǔ yōu', + '棺材瓤子' => ' guān cai ráng zi', + '空气调节' => ' kōng qì tiáo jié', + '横灾飞祸' => ' hèng zāi fēi huò', + '养虎为患' => ' yǎng hǔ wéi huàn', + '触物兴怀' => ' chù wù xīng huái', + '首都剧场' => ' shǒu dū jù chǎng', + '矮杆品种' => ' ǎi gǎn pǐn zhǒng', + '石膏绷带' => ' shí gāo bēng dài', + '鹤长凫短' => ' hè cháng fú duǎn', + '横征苛役' => ' hèng zhēng kē yì', + '回山倒海' => ' huí shān dǎo hǎi', + '相夫教子' => ' xiàng fū jiào zǐ', + '相差不多' => ' xiāng chà bu duō', + '好生之德' => ' hào shēng zhī dé', + '桴鼓相应' => ' fú gǔ xiāng yìng', + '稀里光当' => ' xī li guāng dāng', + '黄绵袄子' => ' huáng mián ǎo zǐ', + '相机而动' => ' xiàng jī ér dòng', + '黄冠野服' => ' huáng guàn yě fú', + '回天运斗' => ' huí tiān yùn dǒu', + '换斗移星' => ' huàn dǒu yí xīng', + '患难与共' => ' huàn nàn yǔ gòng', + '瞬发中子' => ' shùn fā zhōng zǐ', + '还年驻色' => ' huán nián zhù sè', + '还淳返朴' => ' huán chún fǎn pǔ', + '众矢之的' => ' zhòng shǐ zhī dì', + '相机而行' => ' xiàng jī ér xíng', + '眼泪横流' => ' yǎn lèi hèng líu', + '寓意深长' => ' yù yì shēn cháng', + '还淳反素' => ' huán chún fǎn sù', + '胡行乱为' => ' hú xíng luàn wéi', + '实际应用' => ' shí jì yìng yòng', + '毫不相干' => ' háo bù xiāng gān', + '知之甚微' => ' zhī zhī shèn wēi', + '硕果累累' => ' shuò guǒ léi léi', + '狐唱枭和' => ' hú chàng xiāo hé', + '孤身只影' => ' gū shēn zhī yǐng', + '红绳系足' => ' hóng shéng jì zú', + '红寡妇鸟' => ' hóng guǎ fu niǎo', + '好为事端' => ' hào wéi shì duān', + '浩气长存' => ' hào qì cháng cún', + '还淳反古' => ' huán chún fǎn gǔ', + '鸿篇巨著' => ' hóng piān jù zhù', + '礼坏乐崩' => ' lǐ huài yuè bēng', + '寥寥可数' => ' liáo liáo kě shǔ', + '察察为明' => ' chá chá wéi míng', + '礼崩乐坏' => ' lǐ bēng yuè huài', + '礼乐崩坏' => ' lǐ yuè bēng huài', + '呼天抢地' => ' hū tiān qiāng dì', + '秀发垂肩' => ' xìu fà chuí jiān', + '频率调制' => ' pín lù: tiáo zhì', + '音乐电视' => ' yīn yuè diàn shì', + '霍乱杆菌' => ' huò luàn gǎn jūn', + '溢出效应' => ' yì chū xiào yìng', + '春色撩人' => ' chūn sè liáo rén', + '青藏铁路' => ' qīng zàng tiě lù', + '气冲牛斗' => ' qì chōng niú dǒu', + '唇干口燥' => ' chún gān kǒu zào', + '恶意中伤' => ' è yì zhòng shāng', + '电子邮件' => ' diàn zǐ yóu jiàn', + '满口脏话' => ' mǎn kǒu zāng huà', + '怒发冲冠' => ' nù fà chōng guān', + '随机效应' => ' suí jī xiào yìng', + '春深似海' => ' chūn shēn sì hǎi', + '随行就市' => ' suí háng jiù shì', + '随风倒柳' => ' suí fēng dǎo lǐu', + '当头棒喝' => ' dāng tóu bàng hè', + '水底相机' => ' shuǐ dǐ xiàng jī', + '浑身解数' => ' hún shēn xiè shù', + '随风倒舵' => ' suí fēng dǎo duò', + '难兄难弟' => ' nàn xiōng nàn dì', + '游说团体' => ' yóu shuì tuán tǐ', + '没颠没倒' => ' méi diān méi dǎo', + '漫漫长夜' => ' màn màn cháng yè', + '树碑立传' => ' shù bēi lì zhuàn', + '热岛效应' => ' rè dǎo xiào yìng', + '历尽沧桑' => ' lì jìn cāng sāng', + '寸长尺技' => ' cùn cháng chǐ jì', + '为人师表' => ' wéi rén shī biǎo', + '青堂瓦舍' => ' qīng táng wǎ shè', + '非分之念' => ' fēi fèn zhī niàn', + '燕雀处堂' => ' yàn què chǔ táng', + '啜英咀华' => ' chuò yīng jǔ huá', + '死海经卷' => ' sǐ hǎi jīng juàn', + '烟熏火燎' => ' yān xūn huǒ liǎo', + '民间音乐' => ' mín jiān yīn yuè', + '煎炸食品' => ' jiān zhá shí pǐn', + '非杠杆化' => ' fēi gàng gǎn huà', + '壳牌公司' => ' qiào pái gōng sī', + '烈火干柴' => ' liè huǒ gān chái', + '炎黄子孙' => ' yán huáng zǐ sūn', + '感性认识' => ' gǎn xìng rèn shi', + '较德焯勤' => ' jiào dé zhuō qín', + '鞭长莫及' => ' biān cháng mò jí', + '敢为人先' => ' gǎn wéi rén xiān', + '改善关系' => ' gǎi shàn guān xi', + '骆驼祥子' => ' luò tuó xiáng zǐ', + '应试教育' => ' yìng shì jiào yù', + '处变不惊' => ' chǔ biàn bù jīng', + '应有尽有' => ' yīng yǒu jìn yǒu', + '高丽王朝' => ' gāo lí wáng cháo', + '应用科学' => ' yìng yòng kē xué', + '鲜为人知' => ' xiǎn wéi rén zhī', + '除患兴利' => ' chú huàn xīng lì', + '重足屏息' => ' chóng zú bǐng xī', + '重振旗鼓' => ' chóng zhèn qí gǔ', + '重兴旗鼓' => ' chóng xīng qí gǔ', + '等差数列' => ' děng chā shù liè', + '黄毛丫头' => ' huáng máo yā tou', + '黑咕隆咚' => ' hēi gū lōng dōng', + '重熙累洽' => ' chóng xī lěi qià', + '重足屏气' => ' chóng zú bǐng qì', + '韩国银行' => ' hán guó yín háng', + '哼哈二将' => ' hēng hā èr jiàng', + '横科暴敛' => ' hèng kē bào liǎn', + '骨牌效应' => ' gǔ pái xiào yìng', + '处高临深' => ' chǔ gāo lín shēn', + '音乐学院' => ' yīn yuè xué yuàn', + '电子商务' => ' diàn zǐ shāng wù', + '电子文件' => ' diàn zǐ wén jiàn', + '毁舟为杕' => ' huǐ zhōu wéi duò', + '电子层数' => ' diàn zǐ céng shù', + '悄无声息' => ' qiǎo wú shēng xī', + '绞尽脑汁' => ' jiǎo jìn nǎo zhī', + '横祸飞灾' => ' hèng huò fēi zāi', + '家常豆腐' => ' jiā cháng dòu fu', + '河清云庆' => ' hé qīng yún qìng', + '黑白颠倒' => ' hēi bái diān dǎo', + '横倒竖歪' => ' héng dǎo shù wāi', + '吹叶嚼蕊' => ' chuī yè jiáo ruǐ', + '措置失当' => ' cuò zhì shī dàng', + '电子网络' => ' diàn zǐ wǎng luò', + '电子警察' => ' diàn zǐ jǐng chá', + '简单地说' => ' jiǎn dān de shuō', + '政治避难' => ' zhèng zhì bì nàn', + '放热反应' => ' fàng rè fǎn yìng', + '待时守分' => ' dài shí shǒu fèn', + '矮子观场' => ' ǎi zǐ guān cháng', + '兴师问罪' => ' xīng shī wèn zuì', + '眼见为实' => ' yǎn jiàn wéi shí', + '花不棱登' => ' huā bù lēng dēng', + '重蹈覆辙' => ' chóng dǎo fù zhé', + '强凫变鹤' => ' qiǎng fú biàn hè', + '颠唇簸嘴' => ' diān chún bò zuǐ', + '竭尽所能' => ' jié jìn suǒ néng', + '弓折刀尽' => ' gōng zhé dāo jìn', + '绷扒吊拷' => ' bēng bā diào kǎo', + '绷爬吊拷' => ' bēng pá diào kǎo', + '斗筲之材' => ' dǒu shāo zhī cái', + '翘首以待' => ' qiáo shóu yǐ dài', + '军事力量' => ' jūn shì lì liang', + '纶巾羽扇' => ' guān jīn yǔ shàn', + '绷巴吊拷' => ' bèng bā diào kǎo', + '动画片儿' => ' dòng huà piān er', + '灯尽油干' => ' dēng jìn yóu gàn', + '长治久安' => ' cháng zhì jiǔ ān', + '东横西倒' => ' dōng héng xī dǎo', + '切中时弊' => ' qiè zhòng shí bì', + '颠倒干坤' => ' diān dǎo gàn kūn', + '金山屯区' => ' jīn shān zhūn qū', + '方兴未已' => ' fāng xīng wèi yǐ', + '得心应手' => ' dé xīn yìng shǒu', + '新都桥镇' => ' xīn dū qiáo zhèn', + '倒执手版' => ' dǎo zhí shǒu bǎn', + '弹尽援绝' => ' dàn jìn yuán jué', + '新兴产业' => ' xīn xīng chǎn yè', + '地方风味' => ' dì fāng fēng wèi', + '兴业银行' => ' xīng yè yín háng', + '长度单位' => ' cháng dù dān wèi', + '量化逻辑' => ' liàng huà luó ji', + '量入为出' => ' liàng rù wéi chū', + '地狱变相' => ' dì yù biàn xiàng', + '从长计议' => ' cóng cháng jì yì', + '听墙根儿' => ' tīng qiáng gēn r', + '阴错阳差' => ' yīn cuò yáng chā', + '丢车保帅' => ' diū jū bǎo shuài', + '过从甚密' => ' guò cóng shèn mì', + '差强人意' => ' chā qiáng rén yì', + '卖文为生' => ' mài wén wéi shēn', + '以慎为键' => ' yǐ shèn wéi jiàn', + '抢地呼天' => ' qiāng dì hū tiān', + '一天星斗' => ' yī tiān xīng dǒu', + '血债累累' => ' xuè zhài lěi lěi', + '单眼相机' => ' dān yǎn xiàng jī', + '差动齿轮' => ' chā dòng chǐ lún', + '望而兴叹' => ' wàng ér xīng tàn', + '天王老子' => ' tiān wáng lǎo zǐ', + '意义深长' => ' yì yì shēn cháng', + '伏虎降龙' => ' fú hǔ xiáng lóng', + '凫短鹤长' => ' fú duǎn hè cháng', + '佛眼相看' => ' fó yǎn xiāng kàn', + '缝衣工人' => ' féng yī gōng rén', + '强文假醋' => ' qiǎng wén jiǎ cù', + '强死赖活' => ' qiǎng sǐ lài huó', + '衣绣昼行' => ' yì xiù zhòu xíng', + '对薄公堂' => ' duì bù gōng táng', + '游说集团' => ' yóu shuì jí tuán', + '脱不了身' => ' tuō bù liǎo shēn', + '血流漂杵' => ' xuè liú piāo chǔ', + '行家里手' => ' háng jiā lǐ shǒu', + '老少咸宜' => ' lǎo shào xián yí', + '干柴烈火' => ' gān chái liè huǒ', + '田父之功' => ' tián fǔ zhī gōng', + '行为准则' => ' xíng wéi zhǔn zé', + '朝思暮想' => ' zhāo sī mù xiǎng', + '朝令夕改' => ' zhāo lìng xī gǎi', + '叱吒风云' => ' chì zhà fēng yún', + '重睹天日' => ' chóng dǔ tiān rì', + '强聒不舍' => ' qiǎng guō bù shě', + '旧地重游' => ' jiù dì chóng yóu', + '钉头磷磷' => ' ding tou lin lin', + '佛性禅心' => ' fó xìng chán xīn', + '重操旧业' => ' chóng cāo jiù yè', + '都铎王朝' => ' dū duó wáng cháo', + '短见薄识' => ' duǎn jiàn bó shí', + '长途网路' => ' cháng tú wǎng lù', + '酌情处理' => ' zhuó qíng chǔ lǐ', + '间接选举' => ' jiàn jiē xuǎn jǔ', + '道尽途穷' => ' dào jìn tú qióng', + '断发文身' => ' duàn fà wén shēn', + '弦鸣乐器' => ' xián míng yuè qì', + '数九寒天' => ' shǔ jiǔ hán tiān', + '柏林围墙' => ' bó lín wéi qiáng', + '都中纸贵' => ' dū zhōng zhǐ guì', + '弧长参数' => ' hú cháng cān shù', + '重复语境' => ' chóng fù yǔ jìng', + '酩酊大醉' => ' mǐng dǐng dà zuì', + '倒廪倾囷' => ' dǎo lǐn qīng qūn', + '弹射座舱' => ' tán shè zuò cāng', + '电子产品' => ' diàn zǐ chǎn pǐn', + '重提旧事' => ' chóng tí jìu shì', + '都江堰市' => ' dū jiāng yàn shì', + '电子文档' => ' diàn zǐ wén dàng', + '重温旧业' => ' chóng wēn jiù yè', + '重历旧游' => ' chóng lì jìu yóu', + '重新开机' => ' chóng xīn kāi jī', + '斗酒百篇' => ' dǒu jiǔ bǎi piān', + '数典忘祖' => ' shǔ diǎn wàng zǔ', + '长途话费' => ' cháng tú huà fèi', + '自力更生' => ' zì lì gēng shēng', + '心广体胖' => ' xīn guǎng tǐ pán', + '斗折蛇行' => ' dǒu zhé shé xíng', + '心拙口夯' => ' xīn zhuō kǒu bèn', + '更弦易辙' => ' gēng xián yì zhé', + '断线鹞子' => ' duàn xiàn yào zǐ', + '数不胜数' => ' shǔ bù shèng shǔ', + '铜管乐器' => ' tóng guǎn yuè qì', + '莘莘学子' => ' shēn shēn xué zǐ', + '雕肝琢膂' => ' diāo gān zhuó lǚ', + '阴差阳错' => ' yīn chā yáng cuò', + '倒街卧巷' => ' dǎo jiē wò xiàng', + '公共积累' => ' gōng gòng jī lěi', + '钻头卡盘' => ' zuàn tóu qiǎ pán', + '盗嫂受金' => ' dào sǎo shòu jīn', + '斗筲之人' => ' dǒu shāo zhī rén', + '将伯之助' => ' qiāng bó zhī zhù', + '点石为金' => ' diǎn shí wéi jīn', + '钻头夹盘' => ' zuàn tóu jiá pán', + '铤而走险' => ' tǐng ér zǒu xiǎn', + '长夜漫漫' => ' cháng yè màn màn', + '方兴未艾' => ' fāng xīng wèi ài', + '刁钻促狭' => ' diāo zuàn cù xiá', + '刁斗森严' => ' diāo dǒu sēn yán', + '经济困境' => ' jīng jì kùn jìng', + '鼓乐喧天' => ' gǔ yuè xuān tiān', + '彰善瘅恶' => ' zhāng shàn dàn è', + '弹指之间' => ' tán zhǐ zhī jiān', + '鼎鼐调和' => ' dǐng nai tiáo hé', + '倒冠落佩' => ' dǎo guān luò pèi', + '早期效应' => ' zǎo qī xiào yìng', + '早生贵子' => ' zǎo shēng guì zǐ', + '倒持手板' => ' dǎo chí shǒu bǎn', + '东亚银行' => ' dōng yà yín háng', + '除旧更新' => ' chú jiù gēng xīn', + '鬼魅伎俩' => ' guǐ mèi jì liǎng', + '间隔摄影' => ' jiàn gé shè yǐng', + '防毒斗篷' => ' fáng dú dǒu péng', + '降龙伏虎' => ' xiáng lóng fú hǔ', + '道傍之筑' => ' dào bàng zhī zhù', + '罪行累累' => ' zuì xíng lěi lěi', + '强直自遂' => ' qiáng zhí zì suí', + '强识博闻' => ' qiǎng shí bó wén', + '单方恐吓' => ' dān fāng kǒng hè', + '返朴还淳' => ' fǎn pǔ huán chún', + '绕梁三日' => ' rǎo liáng sān rì', + '发人深省' => ' fā rén shēn xǐng', + '军团杆菌' => ' jūn tuán gǎn jūn', + '一长二短' => ' yī cháng èr duǎn', + '勉强及格' => ' miǎn qiǎng jí gé', + '面南背北' => ' miàn nán bèi běi', + '反躬自省' => ' fǎn gōng zì xǐng', + '一长一短' => ' yī cháng yī duǎn', + '豁然开朗' => ' huò rán kāi lǎng', + '移动平台' => ' yí dòng píng tái', + '一了百当' => ' yī liǎo bǎi dàng', + '似笑非笑' => ' sì xiào fēi xiào', + '似水年华' => ' sì shuǐ nián huá', + '以攻为守' => ' yǐ gōng wéi shǒu', + '返朴还真' => ' fǎn pǔ huán zhēn', + '搴旗取将' => ' qiān qí qǔ jiàng', + '靡然从风' => ' mǐ rán cóng fēng', + '慢性咽炎' => ' màn xìng yān yán', + '可共患难' => ' kě gòng huàn nàn', + '谦谦君子' => ' qiān qiān jūn zǐ', + '朝鲜日报' => ' cháo xiǎn rì bào', + '朝鲜八道' => ' cháo xiǎn bā dào', + '杠杆原理' => ' gàng gǎn yuán lǐ', + '一哄而上' => ' yì hōng ér shàng', + '似懂非懂' => ' sì dǒng fēi dǒng', + '崭露头角' => ' zhǎn lù tóu jiǎo', + '薄养厚葬' => ' bó yǎng hòu zàng', + '过敏反应' => ' guò mǐn fǎn yìng', + '奉为至宝' => ' fèng wéi zhì bǎo', + '有为有守' => ' yǒu wéi yǒu shǒu', + '反应迟钝' => ' fǎn yìng chí dùn', + '翘居群首' => ' qiáo jū qún shǒu', + '庚子国变' => ' gēng zǐ guó biàn', + '以守为攻' => ' yǐ shǒu wéi gōng', + '冠山戴粒' => ' guàn shān dài lì', + '专业知识' => ' zhuān yè zhī shi', + '背景音乐' => ' bèi jǐng yīn yuè', + '天之骄子' => ' tiān zhī jiāo zǐ', + '见义勇为' => ' jiàn yì yǒng wéi', + '年少无知' => ' nián shào wú zhī', + '费尽周折' => ' fèi jìn zhōu zhé', + '风流蕴藉' => ' fēng liú yùn jiè', + '钝学累功' => ' dùn xué lěi gōng', + '小两口儿' => ' xiǎo liǎng kǒu r', + '猫鼠同处' => ' māo shǔ tóng chǔ', + '毛遂堕井' => ' máo suí duò jǐng', + '视微知着' => ' shì wēi zhī zhuó', + '锋镝余生' => ' fēng dí yú shēng', + '调理阴阳' => ' tiáo lǐ yīn yáng', + '发怒冲冠' => ' fà nù chōng guān', + '连篇累牍' => ' lián piān lěi dú', + '兴隆台区' => ' xīng lóng tái qū', + '天下为家' => ' tiān xià wéi jiā', + '一口两匙' => ' yī kǒu liǎng chí', + '泛应曲当' => ' fàn yīng qǔ dāng', + '仰屋兴叹' => ' yǎng wū xīng tàn', + '英语教学' => ' yīng yǔ jiāo xué', + '千载一逢' => ' qiān zǎi yī féng', + '发怒穿冠' => ' fà nù chuān guān', + '天造草昧' => ' tiān zào cǎo mèi', + '通讯行业' => ' tōng xùn háng yè', + '儿女情长' => ' ér nǚ qíng cháng', + '单反相机' => ' dān fǎn xiàng jī', + '转危为安' => ' zhuǎn wēi wéi ān', + '儿女成行' => ' ér nǚ chéng háng', + '任人唯贤' => ' rèn rén wéi xián', + '阿党相为' => ' ē dǎng xiāng wéi', + '膏唇贩舌' => ' gào chún fàn shé', + '眠花藉柳' => ' mián huā jiè liǔ', + '天差地远' => ' tiān chā dì yuǎn', + '葛莱美奖' => ' gě lái měi jiǎng', + '高情逸兴' => ' gāo qíng yì xīng', + '朝饔夕飧' => ' zhāo yōng xī sūn', + '鸾孤凤只' => ' luán gū fèng zhī', + '营业时候' => ' yíng yè shí hou', + '巧发奇中' => ' qiǎo fā qí zhòng', + '朋比为奸' => ' péng bǐ wéi jiān', + '朱云折槛' => ' zhū yún shé jiàn', + '恬不为怪' => ' tián bù wéi guài', + '各有所长' => ' gè yǒu suǒ cháng', + '缝衣浅带' => ' féng yī qiǎn dài', + '叹为观止' => ' tàn wéi guān zhǐ', + '鸾停鹄峙' => ' luán tíng hú zhì', + '蝴蝶效应' => ' hú dié xiào yìng', + '一朝之患' => ' yī zhāo zhī huàn', + '鸾鹄在庭' => ' luán hú zài tíng', + '叶公好龙' => ' yè gōng hào lóng', + '告老还家' => ' gào lǎo huán jiā', + '未竟之志' => ' wèi jìng zhī zhì', + '风云之志' => ' fēng yún zhī zhì', + '处堂燕雀' => ' chǔ táng yàn què', + '鸾鹄停峙' => ' luán hú tíng zhì', + '绿林强盗' => ' lù lín qiáng dào', + '朝鲜核谈' => ' cháo xiǎn hé tán', + '见微知著' => ' jiàn wēi zhī zhù', + '剔抽秃揣' => ' tī chōu tū chuǎi', + '著称于世' => ' zhù chēng yú shì', + '凤子龙孙' => ' fèng zǐ lóng sūn', + '陆相沉积' => ' lù xiàng chén jī', + '凤只鸾孤' => ' fèng zhī luán gū', + '乱臣贼子' => ' luàn chén zéi zǐ', + '缟纻之交' => ' gāo zhù zhī jiāo', + '剔蝎撩蜂' => ' tī xiē liáo fēng', + '裸裎袒裼' => ' luǒ chéng tǎn xī', + '翘足引领' => ' qiáo zú yǐn lǐng', + '内切球' => ' nèi qiē qíu', + '粘合剂' => ' nián hé jì', + '血豆腐' => ' xiě dòu fǔ', + '打擂台' => ' dǎ lèi tái', + '背带裤' => ' bēi dài kù', + '增压比' => ' zēng yà bǐ', + '脑门子' => ' nǎo mén zǐ', + '碎块儿' => ' suì kuài r', + '简帖儿' => ' jiǎn tiě r', + '大兴区' => ' dà xīng qū', + '增压机' => ' zēng yà jī', + '那时候' => ' nà shí hou', + '碌曲县' => ' lù qǔ xiàn', + '都安县' => ' dū an xiàn', + '剃发令' => ' tì fà lìng', + '奔头儿' => ' bèn tou er', + '打不通' => ' dǎ bu tōng', + '快板儿' => ' kuài bǎn r', + '兴安区' => ' xīng ān qū', + '喝倒彩' => ' hè dào cǎi', + '碎嘴子' => ' suì zuǐ zǐ', + '这下子' => ' zhè xià zǐ', + '拿不动' => ' ná bu dòng', + '亚原子' => ' yà yuán zǐ', + '干旱土' => ' gān hàn tǔ', + '脑瓢儿' => ' nǎo piáo r', + '舔屁股' => ' tiǎn pì gu', + '狗骨头' => ' gǒu gú tou', + '打呵欠' => ' dǎ hē qiàn', + '压轴子' => ' yā zhòu zǐ', + '三拗汤' => ' sān ào tāng', + '脑瓜子' => ' nǎo guā zǐ', + '趁早儿' => ' chèn zǎo r', + '遛弯儿' => ' liù wān ér', + '少不得' => ' shǎo bu dé', + '划不来' => ' huá bù lái', + '西门町' => ' xī mén dīng', + '保不住' => ' bǎo bú zhù', + '增压器' => ' zēng yà qì', + '那倒是' => ' nà dào shi', + '脯氨酸' => ' fǔ ān suān', + '南边儿' => ' nán bian r', + '近似解' => ' jìn sì jiě', + '佛甲草' => ' fó jiǎ cǎo', + '干梅子' => ' gān méi zi', + '近似值' => ' jìn sì zhí', + '与会国' => ' yù huì guó', + '女公子' => ' nǚ gōng zǐ', + '小菜儿' => ' xiǎo cài r', + '脖颈儿' => ' bó gěng ér', + '拌和机' => ' bàn huò jī', + '那曲县' => ' nǎ qū xiàn', + '部分值' => ' bù fèn zhí', + '架不住' => ' jià bú zhù', + '逗闷子' => ' dòu mèn zǐ', + '柞丝绸' => ' zuò sī chóu', + '出主意' => ' chū zhǔ yi', + '云头儿' => ' yún tou er', + '喀什市' => ' kā shí shì', + '石头记' => ' shí tou jì', + '都匀市' => ' dū yún shì', + '了不得' => ' liǎo bù dé', + '亚粘土' => ' yà nián tǔ', + '尽可能' => ' jìn kě néng', + '四公子' => ' sì gōng zǐ', + '氨哮素' => ' ān xiào sù', + '脏乎乎' => ' zāng hū hū', + '排子车' => ' pǎi zǐ chē', + '佛出世' => ' fó chū shì', + '迫击炮' => ' pǎi jī pào', + '都柏林' => ' dōu bó lín', + '脚丫子' => ' jiǎo yā zǐ', + '乌什县' => ' wū shí xiàn', + '知识库' => ' zhī shi kù', + '大舌头' => ' dà shé tou', + '豆腐羹' => ' dòu fu gēng', + '乌良哈' => ' wū liáng hǎ', + '倒好儿' => ' dǎo hǎo ér', + '鼻咽炎' => ' bí yān yán', + '逻辑学' => ' luó ji xué', + '梭子蟹' => ' suō zǐ xiè', + '石榴子' => ' shí liu zǐ', + '贾似道' => ' jiǎ sì dào', + '眼底下' => ' yǎn dǐ xia', + '脊髓炎' => ' jǐ suǐ yán', + '锁子甲' => ' suǒ zǐ jiǎ', + '避难所' => ' bì nàn suǒ', + '都更案' => ' dū gēng àn', + '南海子' => ' nán hǎi zǐ', + '混名儿' => ' hùn míng r', + '刨笔刀' => ' bào bǐ dāo', + '吴兴区' => ' wú xīng qū', + '文曲星' => ' wén qǔ xīng', + '佛法僧' => ' fó fǎ sēng', + '漂洗机' => ' piǎo xǐ jī', + '划得来' => ' huá de lái', + '粘乎乎' => ' nián hū hū', + '大轴戏' => ' dà zhòu xì', + '节骨眼' => ' jiē gǔ yǎn', + '弹拨乐' => ' tán bō yuè', + '随大溜' => ' suí dà liù', + '套马杆' => ' tào mǎ gǎn', + '空白符' => ' kòng bái fú', + '调速器' => ' tiáo sù qì', + '嘴把式' => ' zuǐ bǎ shi', + '赔不是' => ' péi bú shì', + '螺丝钻' => ' luó sī zuàn', + '二愣子' => ' èr lèng zǐ', + '电喇叭' => ' diàn lǎ ba', + '走门子' => ' zǒu mén zǐ', + '备不住' => ' bèi bú zhù', + '笑呵呵' => ' xiào hē hē', + '老莱子' => ' lǎo lái zǐ', + '塔什干' => ' tǎ shí gàn', + '小姑子' => ' xiǎo gū zǐ', + '走样儿' => ' zǒu yàng r', + '痱子粉' => ' fèi zǐ fěn', + '小肚子' => ' xiǎo dǔ zi', + '质子数' => ' zhì zǐ shù', + '没空儿' => ' méi kòng r', + '书记处' => ' shū ji chù', + '书馆儿' => ' shū guǎn r', + '乔答摩' => ' qiáo dā mó', + '包园儿' => ' bāo yuán r', + '补给线' => ' bú jǐ xiàn', + '调色剂' => ' tiáo sè jì', + '摇滚乐' => ' yáo gǔn yuè', + '童子鸡' => ' tóng zǐ jī', + '空心儿' => ' kòng xīn r', + '空壳子' => ' kōng ké zǐ', + '有益处' => ' yǒu yì chu', + '配给品' => ' pèi jǐ pǐn', + '歌片儿' => ' gē piān er', + '有道理' => ' yǒu dào li', + '做什么' => ' zuò shí mǒ', + '有边儿' => ' yǒu biān r', + '要样儿' => ' yào yàng r', + '扩散剂' => ' kuò sǎn jì', + '调和漆' => ' tiáo hé qī', + '责任人' => ' zé rèn rén', + '老鸨子' => ' lǎo bǎo zǐ', + '北边儿' => ' běi biān r', + '分子式' => ' fèn zǐ shì', + '老大难' => ' lǎo dà nàn', + '贺子珍' => ' hè zǐ zhēn', + '套衫儿' => ' tào shān r', + '落不是' => ' luò bú shì', + '挑头儿' => ' tiǎo tóu r', + '大不了' => ' dà bù liǎo', + '倒计时' => ' dǎo jì shí', + '徐熙媛' => ' xú xī yuán', + '着劲儿' => ' zhuó jìn r', + '子午莲' => ' zǐ wǔ lián', + '蛋卷儿' => ' dàn juǎn r', + '卷发器' => ' juǎn fà qì', + '葵花子' => ' kuí huā zǐ', + '猪尾巴' => ' zhū wěi ba', + '波隆那' => ' bō lōng nà', + '葛法翁' => ' gě fǎ wēng', + '豆角儿' => ' dòu jué ér', + '出漏子' => ' chū lòu zǐ', + '踝子骨' => ' huái zǐ gǔ', + '调压器' => ' tiáo yā qì', + '葡萄干' => ' pú táo gān', + '线坯子' => ' xiàn pī zǐ', + '出岔子' => ' chū chà zǐ', + '电驴子' => ' diàn lǘ zǐ', + '有会子' => ' yǒu huì zǐ', + '懒骨头' => ' lǎn gǔ tou', + '排叉儿' => ' pái chà ér', + '作曲者' => ' zuò qǔ zhě', + '一个样' => ' yī ge yàng', + '柞蚕丝' => ' zuò cán sī', + '一刀切' => ' yī dāo qiē', + '让步地' => ' ràng bù de', + '出月子' => ' chū yuè zǐ', + '拦不住' => ' lán bú zhù', + '毛丫头' => ' máo yā tou', + '出尖儿' => ' chū jiān r', + '血淋淋' => ' xiě lín lín', + '曾祖母' => ' zēng zǔ mǔ', + '高丽纸' => ' gāo lí zhǐ', + '子系统' => ' zǐ xì tǒng', + '后边儿' => ' hòu bian r', + '王世子' => ' wáng shì zǐ', + '曾纪泽' => ' zēng jì zé', + '瞎琢磨' => ' xiā zuó mo', + '烘干机' => ' hōng gān jī', + '蒙古族' => ' měng gǔ zú', + '蒙古语' => ' měng gǔ yǔ', + '混球儿' => ' hún qiú ér', + '曾祖父' => ' zēng zǔ fù', + '来得早' => ' lái de zǎo', + '谈得来' => ' tán de lái', + '未处理' => ' wèi chǔ lǐ', + '莲都区' => ' lián dū qū', + '法隆寺' => ' fǎ lōng sì', + '铜离子' => ' tóng lí zǐ', + '薄荷糖' => ' bò he táng', + '西洲曲' => ' xī zhōu qǔ', + '薄荷醇' => ' bò he chún', + '铜子儿' => ' tóng zǐ er', + '西塞山' => ' xī sài shān', + '搞不懂' => ' gǎo bu dǒng', + '配给制' => ' pèi jǐ zhì', + '外边儿' => ' wài bian r', + '过得去' => ' guò děi qù', + '干燥炉' => ' gān zào lú', + '无名子' => ' wú míng zǐ', + '斗门区' => ' dǒu mén qū', + '傍家儿' => ' bàng jiā r', + '出门子' => ' chū mén zǐ', + '处理品' => ' chǔ lǐ pǐn', + '多任务' => ' duō rèn wu', + '会计课' => ' kuài jì kè', + '试手儿' => ' shì shǒu r', + '托门子' => ' tuō mén zǐ', + '武曲星' => ' wǔ qǔ xīng', + '填塞物' => ' tián sè wù', + '会计科' => ' kuài jì kē', + '处女作' => ' chǔ nǚ zuò', + '处女座' => ' chǔ nǚ zuò', + '处子秀' => ' chǔ zǐ xìu', + '瓦楞子' => ' wǎ léng zǐ', + '红模子' => ' hóng mú zǐ', + '腿腕子' => ' tuǐ wàn zǐ', + '伪君子' => ' wěi jūn zǐ', + '堆垛机' => ' duī duò jī', + '干燥器' => ' gān zào qì', + '多得是' => ' duō de shì', + '傀儡戏' => ' kuǐ lěi xì', + '怪物似' => ' guài wù sì', + '坤角儿' => ' kūn jué ér', + '浪卡子' => ' làng kǎ zǐ', + '过不下' => ' guò bu xià', + '恐吓信' => ' kǒng hè xìn', + '鼻粘膜' => ' bí nián mó', + '包干儿' => ' bāo gān ér', + '背不住' => ' bèi bú zhù', + '遇难者' => ' yù nàn zhě', + '曲沃县' => ' qǔ wò xiàn', + '丑婆子' => ' chǒu pó zǐ', + '分子论' => ' fēn zǐ lùn', + '五斗柜' => ' wǔ dǒu guì', + '叫哥哥' => ' jiào gē ge', + '指事字' => ' zhǐ shì zì', + '应答器' => ' yìng dá qì', + '松巴哇' => ' sōng bā wā', + '忍得住' => ' rěn de zhù', + '锄头雨' => ' chú tou yǔ', + '捧角儿' => ' pěng jué r', + '练习曲' => ' liàn xí qǔ', + '这阵儿' => ' zhè zhèn r', + '这边儿' => ' zhè biān r', + '脊椎骨' => ' jǐ zhuī gǔ', + '老师傅' => ' lǎo shī fū', + '不到家' => ' bú dào jiā', + '薄伽丘' => ' bó jiā qīu', + '弥勒县' => ' mí lè xiàn', + '油炸机' => ' yóu zhá jī', + '有为法' => ' yǒu wéi fǎ', + '窝囊气' => ' wō nāng qì', + '被誉为' => ' bèi yù wéi', + '袖筒儿' => ' xìu tǒng r', + '独不见' => ' dú bú jiàn', + '压轴戏' => ' yā zhòu xì', + '做头发' => ' zuò tóu fà', + '煤黑子' => ' méi hēi zǐ', + '褪套儿' => ' tùn tào ér', + '北碚区' => ' běi bèi qū', + '栖霞市' => ' xī xiá shì', + '趵突泉' => ' bō tū quán', + '窍门儿' => ' qiào mén r', + '要不是' => ' yào bú shì', + '五斗橱' => ' wǔ dǒu chú', + '八爪鱼' => ' bā zhuǎ yú', + '泊头市' => ' bó tóu shì', + '戳份儿' => ' chuō fèn r', + '弹子锁' => ' dàn zǐ suǒ', + '月头儿' => ' yuè tou er', + '孔夫子' => ' kǒng fū zǐ', + '了不起' => ' liǎo bù qǐ', + '炮筒子' => ' pào tǒng zǐ', + '误差值' => ' wù chā zhí', + '克耶邦' => ' kè yē bāng', + '杜松子' => ' dù sōng zǐ', + '君子国' => ' jūn zǐ guó', + '药方儿' => ' yào fāng r', + '喜剧片' => ' xǐ jù piān', + '西藏人' => ' xī zàng rén', + '料头儿' => ' liào tóu r', + '戏馆子' => ' xì guǎn zǐ', + '脖颈子' => ' bó gěng zi', + '塞尔南' => ' sài ěr nán', + '再处理' => ' zài chǔ lǐ', + '延误费' => ' yán wu fèi', + '曾都区' => ' zēng dū qū', + '药师佛' => ' yào shī fó', + '马钱子' => ' mǎ qián zǐ', + '补给站' => ' bǔ jǐ zhàn', + '药水儿' => ' yào shuǐ r', + '看家戏' => ' kān jiā xì', + '戏园子' => ' xì yuán zǐ', + '与会者' => ' yù huì zhě', + '坏包儿' => ' huài bāo r', + '策勒县' => ' cè lè xiàn', + '可的松' => ' kě dì sōng', + '度假者' => ' dù jià zhě', + '啃骨头' => ' kěn gú tou', + '度假村' => ' dù jià cūn', + '河曲县' => ' hé qǔ xiàn', + '走形儿' => ' zǒu xíng r', + '洒狗血' => ' sǎ gǒu xiě', + '皮桶子' => ' pí tǒng zǐ', + '对不住' => ' duì bú zhù', + '注射液' => ' zhù shè yè', + '薄烤饼' => ' bó kǎo bǐng', + '目的港' => ' mù dì gǎng', + '济南市' => ' jǐ nán shì', + '菜子油' => ' cài zǐ yóu', + '那么着' => ' nà me zhāo', + '原子核' => ' yuán zǐ hé', + '饭馆儿' => ' fàn guǎn r', + '败家子' => ' bài jiā zǐ', + '子宫癌' => ' zǐ gōng ái', + '反面儿' => ' fǎn miàn r', + '理发店' => ' lǐ fà diàn', + '理发厅' => ' lǐ fà tīng', + '那些个' => ' nèi xiē gè', + '理发院' => ' lǐ fà yuàn', + '调味剂' => ' tiáo wèi jì', + '韩非子' => ' hán fēi zǐ', + '理发员' => ' lǐ fà yuán', + '不是事' => ' bú shì shì', + '体己钱' => ' tī ji qián', + '狗腿子' => ' gǒu tuǐ zǐ', + '曲射炮' => ' qǔ shè pào', + '热干面' => ' rè gān miàn', + '容不得' => ' róng bu dé', + '欧巴桑' => ' ōu ba sāng', + '安公子' => ' ān gōng zǐ', + '名角儿' => ' míng jué r', + '薄油层' => ' bó yóu céng', + '调制波' => ' tiáo zhì bō', + '分子键' => ' fēn zǐ jiàn', + '没得说' => ' méi de shuō', + '过家家' => ' guō jiā jiā', + '馊点子' => ' sōu diǎn zǐ', + '活塞杆' => ' huó sāi gǎn', + '曲江区' => ' qǔ jiāng qū', + '酸处理' => ' suān chǔ lǐ', + '参与者' => ' cān yù zhě', + '逼供信' => ' bī gòng xìn', + '酸奶子' => ' suān nǎi zǐ', + '进行曲' => ' jìn xíng qǔ', + '拐弯儿' => ' guǎi wān r', + '犯得着' => ' fàn de zháo', + '目的性' => ' mù dì xìng', + '犟劲儿' => ' jiàng jìn r', + '不见得' => ' bú jiàn de', + '分子病' => ' fēn zǐ bìng', + '掷骰子' => ' zhì tóu zǐ', + '钻门子' => ' zuàn mén zǐ', + '娃娃生' => ' wá wa shēng', + '参与制' => ' cān yù zhì', + '核子能' => ' hé zǐ néng', + '着色剂' => ' zhuó sè jì', + '避难权' => ' bì nàn quán', + '除不尽' => ' chú bù jìn', + '滥套子' => ' làn tào zǐ', + '犯不着' => ' fàn bù zháo', + '小家子' => ' xiǎo jiā zǐ', + '黑芝麻' => ' hēi zhī ma', + '男子单' => ' nán zǐ dān', + '折跟头' => ' zhē gēn tou', + '不对头' => ' bú duì tóu', + '上一个' => ' shàng yī ge', + '电子业' => ' diàn zǐ yè', + '炸丸子' => ' zhá wán zǐ', + '无名帖' => ' wú míng tiě', + '满剌加' => ' mǎn là jiā', + '高勾丽' => ' gāo gōu lí', + '窃以为' => ' qiè yǐ wéi', + '不尽根' => ' bù jìn gēn', + '男子汉' => ' nán zǐ hàn', + '切削力' => ' qiē xuē lì', + '会计学' => ' kuài jì xué', + '无患子' => ' wú huàn zǐ', + '拓跋魏' => ' tuò bá wèi', + '高句丽' => ' gāo gōu lí', + '高分子' => ' gāo fèn zǐ', + '白干儿' => ' bái gān ér', + '一次性' => ' yí cì xìng', + '拖尾巴' => ' tuō wěi ba', + '弓弦儿' => ' gōng xián r', + '不干涉' => ' bù gān shè', + '预应力' => ' yù yìng lì', + '后发座' => ' hòu fà zuò', + '白切鸡' => ' bái qiē jī', + '吴堡县' => ' wú bǔ xiàn', + '虎爪派' => ' hǔ zhuǎ pài', + '顺嘴儿' => ' shùn zuǐ r', + '开卷机' => ' kāi juàn jī', + '合得着' => ' hé de zháo', + '的士高' => ' dí shì gāo', + '会计室' => ' kuài jì shì', + '不失为' => ' bù shī wéi', + '姜子牙' => ' jiāng zǐ yá', + '再发见' => ' zài fā xiàn', + '藏族人' => ' zàng zú rén', + '子弟兵' => ' zǐ dì bīng', + '做功夫' => ' zuò gōng fu', + '原子笔' => ' yuán zǐ bǐ', + '回弹力' => ' huí tán lì', + '做工夫' => ' zuò gōng fu', + '一溜烟' => ' yī liú yān', + '说不出' => ' shuō bu chū', + '内应力' => ' nèi yìng lì', + '不应期' => ' bù yìng qī', + '曲艺团' => ' qǔ yì tuán', + '散腿裤' => ' sǎn tuǐ kù', + '小弟弟' => ' xiǎo dì di', + '白芝麻' => ' bái zhī ma', + '这会子' => ' zhè huì zǐ', + '择日子' => ' zhái rì zi', + '粘度计' => ' nián dù jì', + '锭子油' => ' dìng zǐ yóu', + '六环路' => ' lìu huán lù', + '会厌炎' => ' huì yàn yán', + '子宫壁' => ' zǐ gōng bì', + '古惑仔' => ' gǔ huò zǎi', + '脏兮兮' => ' zāng xī xī', + '看得过' => ' kàn de guò', + '不在行' => ' bù zài háng', + '捣练子' => ' dǎo liàn zǐ', + '接泊车' => ' jiē bó chē', + '知识化' => ' zhī shi huà', + '尽全力' => ' jìn quán lì', + '说不到' => ' shuō bú dào', + '九曲桥' => ' jiǔ qǔ qiáo', + '脊梁骨' => ' jǐ liáng gǔ', + '洛子峰' => ' luò zǐ fēng', + '微电子' => ' wēi diàn zǐ', + '弹劾权' => ' tán hé quán', + '弹性体' => ' tán xìng tǐ', + '前奏曲' => ' qián zòu qǔ', + '避风头' => ' bì fēng tou', + '水泡子' => ' shuǐ pāo zi', + '过家伙' => ' guō jiā huo', + '都兰县' => ' dū lán xiàn', + '反间计' => ' fǎn jiàn jì', + '眼不见' => ' yǎn bú jiàn', + '俱舍宗' => ' jù shè zōng', + '颅测量' => ' lú cè liáng', + '狗仔式' => ' gǒu zǎi shì', + '兴凯湖' => ' xīng kǎi hú', + '曲别针' => ' qǔ bié zhēn', + '风信子' => ' fēng xìn zǐ', + '擂台赛' => ' lèi tái sài', + '反知识' => ' fǎn zhī shi', + '不对称' => ' bù duì chèn', + '类似点' => ' lèi sì diǎn', + '修指甲' => ' xīu zhǐ jia', + '择不开' => ' zhái bù kāi', + '瓜子脸' => ' guā zǐ liǎn', + '弹跳力' => ' tán tiào lì', + '怎么着' => ' zěn me zhāo', + '丰子恺' => ' fēng zǐ kǎi', + '畜牧场' => ' xù mù chǎng', + '快点儿' => ' kuài diǎn r', + '量热器' => ' liáng rè qì', + '反诘问' => ' fǎn jié wèn', + '枯萎病' => ' kū wěi bìng', + '激将法' => ' jī jiàng fǎ', + '佛香阁' => ' fó xiāng gé', + '胡涂虫' => ' hú tu chóng', + '旋木雀' => ' xuàn mù què', + '供佛花' => ' gòng fó huā', + '金樱子' => ' jīn yīng zǐ', + '说头儿' => ' shuō tou er', + '甚至于' => ' shèn zhì yú', + '天老爷' => ' tiān lǎo ye', + '暗适应' => ' àn shì yìng', + '说什么' => ' shuō shí mǒ', + '佛光寺' => ' fó guāng sì', + '当家子' => ' dāng jiā zǐ', + '腮帮子' => ' sāi bāng zǐ', + '颠儿面' => ' diān r miàn', + '海陆空' => ' hǎi lù kòng', + '前边儿' => ' qián bian r', + '燕歌行' => ' yān gē xíng', + '反电子' => ' fǎn diàn zǐ', + '佛手柑' => ' fó shǒu gān', + '说得来' => ' shuō de lái', + '古典乐' => ' gǔ diǎn yuè', + '花点子' => ' huā diǎn zǐ', + '辣椒仔' => ' là jiāo zǎi', + '寒山子' => ' hán shān zǐ', + '狗仔队' => ' gǒu zǎi duì', + '曲靖市' => ' qǔ jìng shì', + '国子监' => ' guó zǐ jiàn', + '迷惑龙' => ' mí huo lóng', + '质子泵' => ' zhì zǐ bèng', + '太过分' => ' tài guò fèn', + '抱佛脚' => ' bào fó jiǎo', + '苦差事' => ' kǔ chāi shì', + '不着家' => ' bù zháo jiā', + '寒暑假' => ' hán shǔ jià', + '太子参' => ' tài zǐ shēn', + '卿大夫' => ' qīng dài fū', + '受难日' => ' shòu nàn rì', + '挨时间' => ' ái shí jiān', + '鼻梁子' => ' bí liáng zǐ', + '京油子' => ' jīng yóu zǐ', + '知制诰' => ' zhī zhì gào', + '大麦町' => ' dà mài tǐng', + '风流子' => ' fēng liú zǐ', + '风荷载' => ' fēng hè zài', + '余干县' => ' yú gān xiàn', + '别着急' => ' bié zháo jí', + '理发匠' => ' lǐ fà jiàng', + '兴奋剂' => ' xīng fèn jì', + '切削液' => ' qiē xiāo yè', + '没什么' => ' méi shén me', + '到了儿' => ' dào liǎo er', + '切肉刀' => ' qiē ròu dāo', + '抽嘴巴' => ' chōu zuǐ ba', + '亲家母' => ' qìng jiā mǔ', + '放印子' => ' fàng yìn zǐ', + '自适应' => ' zì shì yìng', + '那程子' => ' nà chéng zǐ', + '芝麻饼' => ' zhī ma bǐng', + '探亲假' => ' tàn qīn jià', + '台面呢' => ' tái miàn ní', + '连轧机' => ' lián zhá jī', + '押运员' => ' yā yùn yuán', + '不值当' => ' bù zhí dàng', + '费工夫' => ' fèi gōng fu', + '兴宾区' => ' xīng bīn qū', + '这么着' => ' zhè me zhāo', + '芫花素' => ' yuán huā sù', + '踏莎行' => ' tà suō xíng', + '德兴市' => ' dé xīng shì', + '转矩臂' => ' zhuàn jǔ bì', + '转速比' => ' zhuàn sù bǐ', + '花旗参' => ' huā qí shēn', + '东阿县' => ' dōng ē xiàn', + '这些个' => ' zhèi xiē gè', + '玄菟郡' => ' xuán tù jùn', + '香胰子' => ' xiāng yí zǐ', + '香薄荷' => ' xiāng bò he', + '东边儿' => ' dōng biān r', + '兴义市' => ' xīng yì shì', + '逆反应' => ' nì fǎn yìng', + '独生子' => ' dú shēng zǐ', + '龈擦音' => ' yín cā yīn', + '无脊椎' => ' wú jǐ zhuī', + '月子病' => ' yuè zǐ bìng', + '娃娃兵' => ' wá wa bīng', + '抄靶子' => ' chāo bǎ zǐ', + '牛脊肉' => ' níu jǐ ròu', + '托勒玫' => ' tuō lè méi', + '小夜曲' => ' xiǎo yè qǔ', + '南瓜子' => ' nán guā zǐ', + '子弹带' => ' zǐ dàn dài', + '筒子楼' => ' tǒng zǐ lóu', + '小伙儿' => ' xiǎo huǒ r', + '隐君子' => ' yǐn jūn zǐ', + '摸不着' => ' mō bù zháo', + '疏勒国' => ' shū lè guó', + '吧托女' => ' bā tuō nǔ:', + '补给舰' => ' bǔ jǐ jiàn', + '起五更' => ' qǐ wǔ gēng', + '阿华田' => ' a huà tián', + '大堡礁' => ' dà pù jiāo', + '调侃儿' => ' tiáo kǎn ér', + '洗发水' => ' xǐ fà shuǐ', + '苣荬菜' => ' qǔ mǎi cài', + '话篓子' => ' huà lǒu zǐ', + '挨呲儿' => ' ái cī r', + '新乐府' => ' xīn yuè fǔ', + '立合同' => ' lì hé tong', + '平埔族' => ' píng pǔ zú', + '花骨朵' => ' huā gū duǒ', + '理发馆' => ' lǐ fà guǎn', + '死不了' => ' sǐ bù liǎo', + '木头人' => ' mù tou rén', + '倒座儿' => ' dǎo zuò ér', + '吃得开' => ' chī de kāi', + '草甸子' => ' cǎo diàn zǐ', + '热得快' => ' rè de kuài', + '鹤嘴镐' => ' hè zuǐ hào', + '阳离子' => ' yáng lí zǐ', + '不调和' => ' bù tiáo hé', + '不均匀' => ' bù jūn yún', + '童子痨' => ' tóng zǐ láo', + '黄陂区' => ' huáng pí qū', + '扩散板' => ' kuò sǎn bǎn', + '汉乐府' => ' hàn yuè fǔ', + '吃空额' => ' chī kòng é', + '不作为' => ' bù zuò wéi', + '着色力' => ' zhuó sè lì', + '察合台' => ' chá gě tái', + '人贩子' => ' rén fàn zǐ', + '对对子' => ' duì duì zǐ', + '脊索瘤' => ' jǐ suǒ liú', + '子模型' => ' zǐ mó xíng', + '右边儿' => ' yòu bian r', + '切割刀' => ' qiē gē dāo', + '掰腕子' => ' bāi wàn zǐ', + '玩儿坏' => ' wán r huài', + '尉犁县' => ' yù lí xiàn', + '燃灯佛' => ' rán dēng fó', + '瘾君子' => ' yǐn jūn zǐ', + '认不是' => ' rèn bú shì', + '牛仔裤' => ' niú zǎi kù', + '坊子区' => ' fāng zǐ qū', + '卸载器' => ' xiè zǎi qì', + '拇指甲' => ' mǔ zhǐ jia', + '动荷载' => ' dòng hè zài', + '劳什子' => ' láo shí zǐ', + '跑神儿' => ' pǎo shén r', + '吃得消' => ' chī de xiāo', + '万户侯' => ' wàn hù hòu', + '电子流' => ' diàn zǐ liú', + '万柏林' => ' wàn bó lín', + '小胡子' => ' xiǎo hú zǐ', + '超负荷' => ' chāo fù hè', + '小女子' => ' xiǎo nǚ zǐ', + '小曲儿' => ' xiǎo qǔ ér', + '坐月子' => ' zuò yuè zǐ', + '肋膜炎' => ' lèi mó yán', + '得样儿' => ' de yàng ér', + '洋娃娃' => ' yáng wá wa', + '李隆基' => ' lǐ lōng jī', + '卸担子' => ' xiè dàn zi', + '较劲儿' => ' jiào jìn r', + '洋画儿' => ' yáng huà r', + '泪人儿' => ' lèi rén ér', + '莫泊桑' => ' mò bó sāng', + '小姨子' => ' xiǎo yí zǐ', + '小苏打' => ' xiǎo sū dá', + '反弹力' => ' fǎn tán lì', + '身板儿' => ' shēn bǎn r', + '勒索者' => ' lè suǒ zhě', + '蹦高儿' => ' bèng gāo r', + '复兴区' => ' fù xīng qū', + '没准儿' => ' méi zhǔn r', + '汤婆子' => ' tāng pó zǐ', + '芝麻包' => ' zhī ma bāo', + '年画儿' => ' nián huà r', + '坎肩儿' => ' kǎn jiān r', + '芸苔子' => ' yún tái zǐ', + '花边儿' => ' huā biān r', + '冷负荷' => ' lěng fù hè', + '德都县' => ' dé dū xiàn', + '雕刻刀' => ' diāo kè dāo', + '办不到' => ' bàn bú dào', + '弹花机' => ' tán huā jī', + '圆舞曲' => ' yuán wǔ qǔ', + '大氧吧' => ' dà yǎng bā', + '干扰素' => ' gān rǎo sù', + '大月氏' => ' dà ròu zhī', + '莱佛士' => ' lái fó shì', + '脾切除' => ' pí qiē chú', + '离子源' => ' lí zǐ yuán', + '干涉仪' => ' gān shè yí', + '离子能' => ' lí zǐ néng', + '虎而冠' => ' hǔ ér guàn', + '土著人' => ' tǔ zhù rén', + '回旋曲' => ' huí xuán qǔ', + '弹射器' => ' tán shè qì', + '年头儿' => ' nián tóu r', + '茶馆儿' => ' chá guǎn r', + '迎头儿' => ' yíng tóu r', + '老油子' => ' lǎo yóu zǐ', + '小妮子' => ' xiǎo nī zǐ', + '拗不过' => ' niù bù guò', + '烧茄子' => ' shāo qié zi', + '合同法' => ' hé tong fǎ', + '子午线' => ' zǐ wǔ xiàn', + '刮舌子' => ' guā shé zǐ', + '热应力' => ' rè yìng lì', + '行业语' => ' háng yè yǔ', + '斗地主' => ' dòu dì zhǔ', + '雪佛兰' => ' xuě fó lán', + '肉丸子' => ' ròu wán zǐ', + '卜算子' => ' bǔ suàn zǐ', + '下边儿' => ' xià bian r', + '雪佛莱' => ' xuě fó lái', + '牙龈炎' => ' yá yín yán', + '牛仔布' => ' níu zǎi bù', + '盐坨子' => ' yán tuó zǐ', + '拉大便' => ' là dà biàn', + '死难者' => ' sǐ nàn zhě', + '认识论' => ' rèn shi lùn', + '挂不住' => ' guà bú zhù', + '君子兰' => ' jūn zǐ lán', + '史丹佛' => ' shǐ dān fó', + '子公司' => ' zǐ gōng sī', + '冬瓜子' => ' dōng guā zǐ', + '找碴儿' => ' zhǎo chá r', + '娃娃脸' => ' wá wa liǎn', + '云浮市' => ' yún fú shì', + '乳杆菌' => ' rǔ gǎn jūn', + '对工儿' => ' duì gōng r', + '支努干' => ' zhī nǔ gān', + '雅诺什' => ' yǎ nuò shí', + '黏糊糊' => ' nián hū hū', + '直发板' => ' zhí fà bǎn', + '动干戈' => ' dòng gān gē', + '脱不了' => ' tuō bù liǎo', + '打跟头' => ' dǎ gēn tou', + '龈辅音' => ' yín fǔ yīn', + '不对劲' => ' bú duì jìn', + '脱壳机' => ' tuō qiào jī', + '玛曲县' => ' mǎ qǔ xiàn', + '会计师' => ' kuài jì shī', + '不要紧' => ' bú yào jǐn', + '知不道' => ' zhì bù dào', + '宿舍楼' => ' sù shè lóu', + '齿龈炎' => ' chǐ yín yán', + '狗崽子' => ' gǒu zǎi zǐ', + '头脸儿' => ' tóu liǎn r', + '摆阔气' => ' bǎi kuò qi', + '齿龈音' => ' chǐ yín yīn', + '阿物儿' => ' ā wù r', + '看热闹' => ' kàn rè nao', + '每个人' => ' měi ge rén', + '任务书' => ' rèn wu shū', + '看不出' => ' kàn bu chū', + '附着物' => ' fù zhuó wù', + '真子集' => ' zhēn zǐ jí', + '合不着' => ' hé bù zháo', + '脊柱炎' => ' jǐ zhù yán', + '小摊儿' => ' xiǎo tān r', + '拓扑学' => ' tuò pū xué', + '狄更斯' => ' dí gēng sī', + '假招子' => ' jiǎ zhāo zǐ', + '主合同' => ' zhǔ hé tong', + '电子伏' => ' diàn zǐ fú', + '拉个手' => ' lā ge shǒu', + '调酒器' => ' tiáo jǐu qì', + '麻麻亮' => ' mā mɑ liàng', + '蔡甸区' => ' cài diàn qū', + '黄埔区' => ' huáng pǔ qū', + '汉堡包' => ' hàn pù bāo', + '果子酒' => ' guǒ zǐ jiǔ', + '革吉县' => ' gé jí xiàn', + '乐都县' => ' lè dū xiàn', + '测量机' => ' cè liáng jī', + '靠得住' => ' kào de zhù', + '咔哒声' => ' kǎ da shēng', + '卸肩儿' => ' xiè jiān r', + '死胡同' => ' sǐ hú tòng', + '独山子' => ' dú shān zǐ', + '登徒子' => ' dēng tú zǐ', + '死劲儿' => ' sǐ jìng er', + '调色板' => ' tiáo sè bǎn', + '靠不住' => ' kào bú zhù', + '小娃娃' => ' xiǎo wá wa', + '吃得住' => ' chī de zhù', + '吃不住' => ' chī bú zhù', + '氢离子' => ' qīng lí zǐ', + '脑萎缩' => ' nǎo wěi suō', + '调节器' => ' tiáo jié qì', + '子囊菌' => ' zǐ náng jūn', + '牟平区' => ' mù píng qū', + '八斗才' => ' bā dǒu cái', + '马尾松' => ' mǎ yǐ sōng', + '鬼故事' => ' guǐ gù shi', + '瞧得起' => ' qiáo de qǐ', + '燕尾蝶' => ' yān wěi dié', + '卷地皮' => ' juàn dì pí', + '面条儿' => ' miàn tiáo r', + '电子论' => ' diàn zǐ lùn', + '差错率' => ' chā cuò lǜ', + '好得多' => ' hǎo de duō', + '鸡血石' => ' jī xiě shí', + '谐振子' => ' xié zhèn zǐ', + '印古什' => ' yìn gǔ shí', + '瓜子金' => ' guā zǐ jīn', + '猜谜儿' => ' cāi mèi ér', + '离子键' => ' lí zǐ jiàn', + '猜得透' => ' cāi de tòu', + '好得很' => ' hǎo de hěn', + '粘糊糊' => ' nián hū hū', + '电子门' => ' diàn zǐ mén', + '猜不透' => ' cāi bu tòu', + '鲁子敬' => ' lǔ zǐ jìng', + '厦门市' => ' xià mén shì', + '马尾辫' => ' mǎ yǐ biàn', + '捞什子' => ' lāo shí zi', + '粘合机' => ' nián hé jī', + '南蛮子' => ' nán mán zǐ', + '出乱子' => ' chū luàn zǐ', + '不一定' => ' bù yí dìng', + '司天台' => ' sī tiān tāi', + '电子式' => ' diàn zǐ shì', + '纪录片' => ' jì lù piān', + '喇叭形' => ' lǎ ba xíng', + '处理器' => ' chǔ lǐ qì', + '喉塞音' => ' hóu sè yīn', + '安魂曲' => ' ān hún qǔ', + '合得来' => ' hé de lái', + '黑糊糊' => ' hēi hū hū', + '一部分' => ' yī bù fen', + '乌干达' => ' wū gān dá', + '乌拉圭' => ' wù lɑ guī', + '鱼肚白' => ' yú dǔ bái', + '暗门子' => ' àn mén zǐ', + '发小儿' => ' fà xiǎo r', + '牙缝儿' => ' yá fèng r', + '润发露' => ' rùn fà lù', + '润发液' => ' rùn fà yè', + '叭啦狗' => ' bā lā gǒu', + '鼻咽癌' => ' bí yān ái', + '牡丹区' => ' mǔ dan qū', + '牡丹卡' => ' mǔ dan kǎ', + '武都市' => ' wǔ dū shì', + '阿克苏' => ' a kè sū', + '暗楼子' => ' àn lóu zǐ', + '阿克拉' => ' a kè lā', + '不值得' => ' bù zhí de', + '可不是' => ' kě bú shì', + '一甲子' => ' yī jiǎ zǐ', + '撒拉铁' => ' sǎ lā tiě', + '可数集' => ' kě shǔ jí', + '龈腭音' => ' yín è yīn', + '龈颚音' => ' yín è yīn', + '魏都区' => ' wèi dū qū', + '折子戏' => ' zhé zǐ xì', + '反粒子' => ' fǎn lì zǐ', + '欧柏林' => ' oū bó lín', + '挂钩儿' => ' guà gōu r', + '不由得' => ' bù yóu de', + '百子图' => ' bǎi zǐ tú', + '男子气' => ' nán zǐ qì', + '男孩儿' => ' nán hái r', + '番禺区' => ' pān yú qū', + '氨吖啶' => ' ān ā dìng', + '颚龈音' => ' è yín yīn', + '不舒服' => ' bù shū fu', + '殷都区' => ' yīn dū qū', + '齐柏林' => ' qí bó lín', + '麦盖提' => ' mài gě tí', + '丽佳娜' => ' lí jiā nà', + '史密斯' => ' shǐ mì sī', + '盐都区' => ' yán dū qū', + '烟头儿' => ' yān tóu r', + '无支祁' => ' wú zhī qí', + '鹍鸡曲' => ' kūn jī qǔ', + '熟道儿' => ' shú dào r', + '三部曲' => ' sān bù qǔ', + '吉卜赛' => ' jí bǔ sài', + '炸子鸡' => ' zhá zǐ jī', + '热处理' => ' rè chǔ lǐ', + '撒切尔' => ' sā qiē ěr', + '舍利塔' => ' shè lì tǎ', + '没门儿' => ' méi mén r', + '没劲儿' => ' méi jìn r', + '卢塞恩' => ' lú sài ēn', + '没六儿' => ' méi lìu r', + '和得来' => ' hé de lái', + '笔头儿' => ' bǐ tou er', + '零杂儿' => ' líng zá r', + '和散那' => ' hé sǎn nà', + '和稀泥' => ' huò xī ní', + '白麻子' => ' bái má zǐ', + '摆设儿' => ' bǎi she r', + '如来佛' => ' rú lái fó', + '一家子' => ' yī jiā zǐ', + '没奈何' => ' mò nài hé', + '自以为' => ' zì yǐ wéi', + '一个人' => ' yí ge rén', + '瘾头儿' => ' yǐn tóu r', + '阴离子' => ' yīn lí zǐ', + '涂尔干' => ' tú ěr gān', + '阿堵物' => ' ē dǔ wù', + '背搭子' => ' bèi dā zǐ', + '孺子牛' => ' rú zǐ niú', + '杜笃玛' => ' dù dǔ mǎ', + '西耶那' => ' xī yē nà', + '核子力' => ' hé zǐ lì', + '洗发皂' => ' xǐ fà zào', + '洗发粉' => ' xǐ fà fěn', + '诸葛弩' => ' zhū gě nǔ', + '辟支佛' => ' pì zhī fó', + '一水儿' => ' yī shuǐ r', + '切割器' => ' qiē gē qì', + '下一个' => ' xià yī ge', + '切割机' => ' qiē gē jī', + '歌仔戏' => ' gē zǎi xì', + '挨板子' => ' ái bǎn zi', + '海蛤蝓' => ' hǎi gé yú', + '卡脖子' => ' qiǎ bó zi', + '挨边儿' => ' āi biān r', + '没溜儿' => ' méi lìu r', + '挨头子' => ' ái tóu zi', + '笔杆儿' => ' bǐ gǎn ér', + '阿伊努' => ' a yī nǔ', + '杂件儿' => ' zá jiàn r', + '散座儿' => ' sǎn zuò r', + '直发器' => ' zhí fà qì', + '白班儿' => ' bái bān r', + '捋胳膊' => ' luō gē bo', + '波尔干' => ' bō ěr gān', + '一个劲' => ' yí gè jìn', + '捋虎须' => ' luō hǔ xū', + '印把子' => ' yìn bà zi', + '鼻咽部' => ' bí yān bù', + '尽义务' => ' jìn yì wù', + '子弟书' => ' zǐ dì shū', + '铺路石' => ' pū lù shí', + '大姨子' => ' dà yí zǐ', + '续随子' => ' xù suí zǐ', + '猫眼儿' => ' māo yǎn r', + '补给品' => ' bǔ jǐ pǐn', + '微粒子' => ' wēi lì zǐ', + '脱衣服' => ' tuō yī fu', + '畜牧学' => ' xù mù xué', + '臭子儿' => ' chòu zǐ r', + '果子露' => ' guǒ zǐ lù', + '果子狸' => ' guǒ zǐ lí', + '果仁儿' => ' guǒ rén r', + '副书记' => ' fù shū ji', + '枕席儿' => ' zhěn xí r', + '虚套子' => ' xū tào zǐ', + '费劲儿' => ' fèi jìn r', + '剥皮器' => ' bāo pí qì', + '柏克郡' => ' bó kè jùn', + '牙花子' => ' yá huā zǐ', + '使劲儿' => ' shǐ jìn r', + '脑瓜儿' => ' nǎo guā r', + '阿合奇' => ' a hé qí', + '脑脊液' => ' nǎo jǐ yè', + '来得及' => ' lái de jí', + '枸杞子' => ' gǒu qǐ zǐ', + '来火儿' => ' lái huǒ r', + '临月儿' => ' lín yuè r', + '买不起' => ' mǎi bu qǐ', + '费德勒' => ' fèi dé lè', + '花都区' => ' huā dū qū', + '土狗子' => ' tǔ gǒu zǐ', + '薄熙来' => ' bó xī lái', + '软泥儿' => ' ruǎn ní r', + '五倍子' => ' wǔ bèi zǐ', + '记不住' => ' jì bu zhù', + '西边儿' => ' xī biān r', + '西门子' => ' xī mén zǐ', + '内比都' => ' nèi bǐ dū', + '触技曲' => ' chù jì qǔ', + '土粉子' => ' tǔ fěn zǐ', + '里边儿' => ' lǐ bian r', + '拓扑图' => ' tuò pū tú', + '记号笔' => ' jì hao bǐ', + '阿玛尼' => ' a mǎ ní', + '脱模剂' => ' tuō mú jì', + '脱模器' => ' tuō mú qì', + '许廑父' => ' xǔ qín fù', + '脱模机' => ' tuō mú jī', + '认人儿' => ' rèn rén r', + '阿瓦提' => ' a wǎ tí', + '兔崽子' => ' tù zǎi zǐ', + '起小兒' => ' qǐ xiǎo r', + '曼德勒' => ' màn dé lè', + '费米子' => ' fèi mǐ zǐ', + '蜗杆副' => ' wō gǎn fù', + '缩砂密' => ' sù shā mì', + '四君子' => ' sì jūn zǐ', + '抱娃娃' => ' bào wá wa', + '马勃菌' => ' mǎ bó jùn', + '扬子鳄' => ' yáng zǐ è', + '熬头儿' => ' áo tou er', + '理发师' => ' lǐ fà shī', + '武侯祠' => ' wǔ hòu cí', + '不可数' => ' bù kě shǔ', + '器乐曲' => ' qì yuè qǔ', + '一揽子' => ' yī lǎn zǐ', + '马赛曲' => ' mǎ sài qǔ', + '马约卡' => ' mǎ yāo kǎ', + '马塞卢' => ' mǎ sài lú', + '不含糊' => ' bù hán hu', + '熬不住' => ' áo bú zhù', + '取灯儿' => ' qǔ dēng r', + '拨子弹' => ' bō zǐ tán', + '不安分' => ' bù ān fèn', + '扑脸儿' => ' pū liǎn r', + '子母扣' => ' zǐ mǔ kòu', + '半拉子' => ' bàn lǎ zǐ', + '破谜儿' => ' pò mèi ér', + '浑球儿' => ' hún qíu r', + '呀诺达' => ' yā nuò dá', + '呆会儿' => ' dāi huì r', + '字帖儿' => ' zì tiě er', + '剥皮机' => ' bāo pí jī', + '班辈儿' => ' bān bèi r', + '独院儿' => ' dú yuàn r', + '别客气' => ' bié kè qi', + '暾欲谷' => ' tūn yù gǔ', + '血糊糊' => ' xiě hū hū', + '开普勒' => ' kāi pǔ lè', + '朴子市' => ' pò zǐ shì', + '迦叶佛' => ' jiā yè fó', + '曲麻莱' => ' qǔ má lái', + '丝挂子' => ' sī guà zǐ', + '阿卡提' => ' a kǎ dī', + '部落格' => ' bù luò gé', + '俄勒冈' => ' e lè gāng', + '舍利子' => ' shè lì zǐ', + '配对儿' => ' pèi duì r', + '都卜勒' => ' dōu bǔ lè', + '子大夫' => ' zǐ dài fū', + '都伯林' => ' dū bó lín', + '芋头色' => ' yù tou sè', + '那维克' => ' nǎ wéi kè', + '芥子气' => ' jiè zǐ qì', + '那曲市' => ' nà qǔ shì', + '刮胡子' => ' guā hú zǐ', + '泡沫剂' => ' pāo mò jì', + '毗耶娑' => ' pí yē suō', + '不在乎' => ' bù zài hu', + '余甘子' => ' yú gān zǐ', + '玩儿完' => ' wán r wán', + '吐谷浑' => ' tǔ yù hún', + '炮儿局' => ' pào r jú', + '苦活儿' => ' kǔ huó r', + '稀释液' => ' xī shì yè', + '遗腹子' => ' yí fù zǐ', + '杂牌儿' => ' zá pái r', + '阿斯兰' => ' a sī lán', + '泥娃娃' => ' ní wá wa', + '阿勒泰' => ' a lè tài', + '哈喇子' => ' hā lǎ zǐ', + '可劲儿' => ' kě jìn r', + '斗趣儿' => ' dòu qù r', + '四部曲' => ' sì bù qǔ', + '阿图什' => ' a tú shí', + '阿赖耶' => ' ā lài yē', + '扎鲁特' => ' zā lǔ tè', + '阿奎纳' => ' a kuí nà', + '扒头儿' => ' bā tou r', + '刺儿话' => ' cì r huà', + '拉撒路' => ' lā sǎ lù', + '鸭子儿' => ' yā zǐ er', + '阿森斯' => ' a sēn sī', + '阿森纳' => ' a sēn nà', + '阿閦佛' => ' a chù fó', + '阿贝尔' => ' a bèi ěr', + '哥德堡' => ' gē dé pù', + '那话儿' => ' nà huà r', + '合辙儿' => ' hé zhé r', + '打趸儿' => ' dǎ dǔn r', + '鸡子儿' => ' jī zǐ er', + '阿布贾' => ' a bù jiǎ', + '阿兰若' => ' ā lán rě', + '钠离子' => ' nà lí zǐ', + '嘎拉哈' => ' gā lā hà', + '顾不得' => ' gù bu de', + '呕吐物' => ' ǒu tù wù', + '爱玉子' => ' ài yù zǐ', + '头箍儿' => ' tóu gū r', + '氯离子' => ' lǜ lí zǐ', + '马褡子' => ' mǎ dā zǐ', + '努嘴儿' => ' nǔ zuǐ r', + '骨碌碌' => ' gū lù lù', + '刺儿李' => ' cì r lǐ', + '大姑子' => ' dà gū zǐ', + '大衣呢' => ' dà yī ní', + '迂夫子' => ' yū fū zǐ', + '哪一个' => ' nǎ yī ge', + '一拨儿' => ' yī bō r', + '哈巴河' => ' hā bā hé', + '大苏打' => ' dà sū dá', + '努劲儿' => ' nǔ jìn r', + '惹不起' => ' rě bu qǐ', + '拿得起' => ' ná de qǐ', + '吉娃娃' => ' jí wá wa', + '恶名儿' => ' è míng r', + '古希腊' => ' gǔ xī là', + '马服子' => ' mǎ fú zǐ', + '撒播机' => ' sǎ bō jī', + '阿阇梨' => ' ā shé lí', + '玻色子' => ' bō sè zǐ', + '提头儿' => ' tí tóu r', + '拉勾儿' => ' lā gòu r', + '菩提子' => ' pú tí zǐ', + '破衣服' => ' pò yī fu', + '书皮儿' => ' shū pí r', + '鼻洼子' => ' bí wā zǐ', + '地肤子' => ' dì fū zǐ', + '土坎儿' => ' tǔ kǎn r', + '离子膜' => ' lí zǐ mó', + '德勒兹' => ' dé lè zī', + '提篮儿' => ' tí lán r', + '负离子' => ' fù lí zǐ', + '贝壳儿' => ' bèi ké r', + '热负荷' => ' rè fù hè', + '在那儿' => ' zài na r', + '笆篱子' => ' bā lí zǐ', + '锂离子' => ' lǐ lí zǐ', + '护发素' => ' hù fà sù', + '赋格曲' => ' fù gé qǔ', + '土木堡' => ' tǔ mù pù', + '替角儿' => ' tì jué r', + '替班儿' => ' tì bān r', + '弥勒佛' => ' mí lè fó', + '塞席尔' => ' sè xí ěr', + '呼啦啦' => ' hū lā lā', + '模糊集' => ' mó hu jí', + '阿凡达' => ' a fán dá', + '阿阇黎' => ' ā shé lí', + '拿撒勒' => ' ná sǎ lè', + '阿马逊' => ' a mǎ xùn', + '打屁股' => ' dǎ pì gu', + '阿维拉' => ' a wéi lā', + '搭茬儿' => ' dā chá r', + '拉肚子' => ' lā dǔ zi', + '阿尔泰' => ' a ěr tài', + '哥们儿' => ' gē men r', + '撒丫子' => ' sā yā zǐ', + '鱼钩儿' => ' yú gōu r', + '阿瑞斯' => ' a ruì sī', + '雅尔塔' => ' yá ěr tǎ', + '打顿儿' => ' dǎ dùn r', + '阿凡提' => ' a fán tí', + '凹朴皮' => ' āo pò pí', + '阿美族' => ' a měi zú', + '护发乳' => ' hù fà rǔ', + '阿肯色' => ' a kěn sè', + '卡哇伊' => ' kǎ wā yī', + '阿盖达' => ' a gài dá', + '闭子集' => ' bì zǐ jí', + '阿初佛' => ' a chū fó', + '阿依莎' => ' a yī shā', + '阿伊莎' => ' a yī shā', + '阿克伦' => ' a kè lún', + '阿克陶' => ' a kè táo', + '玩艺儿' => ' wán yì r', + '瓦勒他' => ' wǎ lè tā', + '苦不唧' => ' kǔ bu jī', + '兹沃勒' => ' zī wò lè', + '西子湖' => ' xī zǐ hú', + '一对儿' => ' yī duì r', + '虚粒子' => ' xū lì zǐ', + '媳妇子' => ' xí fù zǐ', + '细伢子' => ' xì yá zǐ', + '薄一波' => ' bó yī bō', + '大剌剌' => ' dà là là', + '子不语' => ' zǐ bù yǔ', + '几乎不' => ' jī hū bù', + '莫伯日' => ' mò bó rì', + '洗发露' => ' xǐ fà lù', + '夜盆儿' => ' yè pén r', + '渔钩儿' => ' yú gōu r', + '一面倒' => ' yī miàn dǎo', + '子目录' => ' zǐ mù lù', + '庵摩勒' => ' ān mó lè', + '库尔勒' => ' kù ěr lè', + '蜡坨儿' => ' là tuó r', + '阿法尔' => ' a fǎ ěr', + '栖息地' => ' qī xī dì', + '竹箍儿' => ' zhú gū r', + '奥米伽' => ' ào mǐ gā', + '奥塞梯' => ' aò sè tī', + '歌曲集' => ' gē qǔ jí', + '子午仪' => ' zǐ wǔ yí', + '借字儿' => ' jiè zì r', + '痰盂儿' => ' tán yú r', + '法耶德' => ' fǎ yē dé', + '河西堡' => ' hé xī pù', + '孤立子' => ' gū lì zǐ', + '被窝儿' => ' bèi wō r', + '阿姆河' => ' a mǔ hé', + '克耶族' => ' kè yē zú', + '克蕾儿' => ' kè lěi r', + '洗发乳' => ' xǐ fà rǔ', + '洗发剂' => ' xǐ fà jì', + '大肚子' => ' dà dǔ zi', + '子集合' => ' zǐ jí hé', + '讨底儿' => ' tǎo dǐ r', + '六合区' => ' lù hé qū', + '卧佛寺' => ' wò fó sì', + '法勒斯' => ' fǎ lè sī', + '欧米伽' => ' ōu mǐ gā', + '个头儿' => ' gè tóu r', + '帽箍儿' => ' mào gū r', + '没谱儿' => ' méi pǔ r', + '葫芦科' => ' hú lu kē', + '衣钩儿' => ' yī gōu r', + '子夜歌' => ' zǐ yè gē', + '菟丝子' => ' tù sī zi', + '武把子' => ' wǔ bà zi', + '阿爸父' => ' a bà fù', + '渔歌子' => ' yú gē zǐ', + '五子棋' => ' wǔ zǐ qí', + '瓦都兹' => ' wǎ dū zī', + '不拉几' => ' bù lā jī', + '柏拉图' => ' bó lā tú', + '阿巴斯' => ' a bā sī', + '邷么儿' => ' wǎ mó r', + '密麻麻' => ' mì mā mɑ', + '自个儿' => ' zì gě ér', + '巴勒莫' => ' bā lè mò', + '奇蹄目' => ' jī tí mù', + '纹丝儿' => ' wén sī r', + '窝脖儿' => ' wō bó r', + '纸马儿' => ' zhǐ mǎ r', + '自各儿' => ' zì gě r', + '不答理' => ' bù dā lǐ', + '热剌剌' => ' rè là là', + '畜牧业' => ' xù mù yè', + '娃娃鱼' => ' wá wa yú', + '咖喱鸡' => ' gā lí jī', + '塔塔儿' => ' tǎ tǎ r', + '伊妹儿' => ' yī mèi r', + '伍子胥' => ' wǔ zǐ xū', + '一路哭' => ' yí lù kū', + '伺服器' => ' sì fú qì', + '八哥儿' => ' bā gē r', + '大气儿' => ' dà qì r', + '格子呢' => ' gé zi ní', + '柏柏尔' => ' bò bò ěr', + '希腊语' => ' xī là yǔ', + '箍节儿' => ' gū jie r', + '阿拉尔' => ' a lā ěr', + '乐呵呵' => ' lè hē hē', + '阿提拉' => ' a tí lā', + '姑爷爷' => ' gū yé ye', + '阿拉瓦' => ' a lā wǎ', + '鼓子词' => ' gǔ zǐ cí', + '孤哀子' => ' gū āi zǐ', + '尼勒克' => ' ní lè kè', + '尼泊尔' => ' ní bó ěr', + '屁眼儿' => ' pì yǎn r', + '目的地' => ' mù dì dì', + '耶弗他' => ' yē fú tā', + '阿拉斯' => ' a lā sī', + '希特勒' => ' xī tè lè', + '耶利米' => ' yē lì mǐ', + '阿拉摩' => ' a lā mó', + '阿拉伯' => ' a lā bó', + '胡琴儿' => ' hú qín r', + '一肚子' => ' yī dǔ zi', + '耳挖子' => ' ěr wā zǐ', + '老客儿' => ' lǎo kè r', + '武都区' => ' wǔ dū qū', + '佛骨塔' => ' fó gǔ tǎ', + '比特币' => ' bǐ tè bì', + '阿希姆' => ' a xī mǔ', + '布娃娃' => ' bù wá wa', + '洗发膏' => ' xǐ fà gāo', + '载伯德' => ' zǎi bó dé', + '贼骨头' => ' zéi gú tou', + '花子儿' => ' huā zǐ er', + '伽罗瓦' => ' jiā luó wà', + '伏都教' => ' fú dū jiào', + '附着力' => ' fù zhuó lì', + '节假日' => ' jié jià rì', + '经得起' => ' jīng de qǐ', + '绕弯子' => ' rào wān zǐ', + '夹尾巴' => ' jiā wěi ba', + '捋袖子' => ' luō xìu zi', + '爱好者' => ' ài hào zhě', + '夹当儿' => ' jiā dāng r', + '孙女儿' => ' sūn nǔ: r', + '不舍得' => ' bù shě de', + '翟理斯' => ' zhái lǐ sī', + '哇沙比' => ' wā shā bǐ', + '狐媚子' => ' hú mèi zǐ', + '薄荷脑' => ' bò he nǎo', + '子细胞' => ' zǐ xì bāo', + '哇沙米' => ' wā shā mǐ', + '安南子' => ' ān nán zǐ', + '孙媳夫' => ' sūn xí fu', + '宜都市' => ' yí dū shì', + '安德肋' => ' an dé lèi', + '孙武子' => ' sūn wǔ zǐ', + '挎兜儿' => ' kuà dōu r', + '催干剂' => ' cuī gān jì', + '美发师' => ' měi fà shī', + '不自禁' => ' bù zì jīn', + '使君子' => ' shǐ jūn zǐ', + '罗刹女' => ' luó chà nǚ', + '勒索罪' => ' lè suǒ zuì', + '约维克' => ' yāo wéi kè', + '干扰机' => ' gān rǎo jī', + '人尖儿' => ' rén jiān r', + '红胡子' => ' hóng hú zǐ', + '人为土' => ' rén wéi tǔ', + '于都县' => ' yú dū xiàn', + '干妹子' => ' gàn mèi zǐ', + '奇函数' => ' jī hán shù', + '奇偶性' => ' jī ǒu xìng', + '昆都仑' => ' kūn dū lún', + '美智子' => ' měi zhì zǐ', + '金龟子' => ' jīn guī zǐ', + '夫子庙' => ' fū zǐ miào', + '父子兵' => ' fù zǐ bīng', + '左边儿' => ' zuǒ bian r', + '钓钩儿' => ' diào gōu r', + '罗伯逊' => ' luō bó xùn', + '罗圈儿' => ' luó quān r', + '罗夫诺' => ' luō fū nuò', + '罗姆酒' => ' luō mǔ jǐu', + '片子地' => ' piān zi dì', + '惹乱子' => ' rě luàn zǐ', + '子母弹' => ' zǐ mǔ dàn', + '咖喱粉' => ' gā lí fěn', + '吹胡子' => ' chuī hú zǐ', + '钙离子' => ' gài lí zǐ', + '马扎子' => ' mǎ zhá zǐ', + '马尾藻' => ' mǎ yǐ zǎo', + '马奶子' => ' mǎ nǎi zǐ', + '麻麻黑' => ' mā mɑ hēi', + '处女地' => ' chǔ nǚ dì', + '麻雷子' => ' má léi zǐ', + '喷嘴儿' => ' pēn zuǐ r', + '码垛机' => ' mǎ duò jī', + '哗啦啦' => ' huā lā lā', + '夹肢窝' => ' gā zhī wō', + '嘴巴子' => ' zuǐ ba zi', + '夹塞儿' => ' jiā sāi r', + '满负荷' => ' mǎn fù hè', + '咖喱饭' => ' gā lí fàn', + '路倒儿' => ' lù dǎo er', + '惹麻烦' => ' rě má fan', + '浮头儿' => ' fú tou er', + '惠斯勒' => ' huì sī lè', + '大都市' => ' dà dū shì', + '塞勒姆' => ' sāi lè mǔ', + '夜宵儿' => ' yè xiāo r', + '耳掴子' => ' ěr guó zǐ', + '待会儿' => ' dāi huì r', + '多粒子' => ' duō lì zǐ', + '买得起' => ' mǎi de qǐ', + '高拨子' => ' gāo bō zǐ', + '话把儿' => ' huà bà er', + '禁得起' => ' jīn de qǐ', + '拜把子' => ' bài bà zi', + '柏林寺' => ' bó lín sì', + '牌子曲' => ' pái zi qǔ', + '华达呢' => ' huá dá ní', + '大部分' => ' dà bù fèn', + '拆烂污' => ' cā làn wū', + '回单儿' => ' huí dān r', + '刘阿斗' => ' liú ā dǒu', + '打得好' => ' dǎ de hǎo', + '差速器' => ' chā sù qì', + '大师傅' => ' dà shī fū', + '金缕曲' => ' jīn lǚ qǔ', + '奇蹄类' => ' jī tí lèi', + '太仆寺' => ' tài pú sì', + '富家子' => ' fù jiā zǐ', + '金娃娃' => ' jīn wá wa', + '刀把儿' => ' dāo bà er', + '布什尔' => ' bù shí ěr', + '希思罗' => ' xī sī luō', + '希腊文' => ' xī là wén', + '分子力' => ' fèn zǐ lì', + '打不倒' => ' dǎ bù dǎo', + '打摆子' => ' dǎ bǎi zǐ', + '大都会' => ' dà dū huì', + '懂门儿' => ' dǒng mén r', + '紫坪铺' => ' zǐ píng pū', + '拉呱儿' => ' lā guā er', + '佛得角' => ' fó dé jiǎo', + '漂浮物' => ' piāo fú wù', + '音乐剧' => ' yīn yuè jù', + '耶稣教' => ' yē sū jiào', + '摇篮曲' => ' yáo lán qǔ', + '大袋鼠' => ' dà dài shǔ', + '打哈欠' => ' dǎ hā qian', + '耶烈万' => ' yē liè wàn', + '大家伙' => ' dà jiā huo', + '大姐姐' => ' dà jiě jie', + '大咧咧' => ' dà liē liē', + '是不是' => ' shì bú shì', + '耶诞节' => ' yē dàn jié', + '吃得来' => ' chī de lái', + '佛教徒' => ' fó jiào tú', + '佛教语' => ' fó jiào yǔ', + '佛朗哥' => ' fó lǎng gē', + '数一数' => ' shǔ yī shù', + '耳坠子' => ' ěr zhuì zǐ', + '药引子' => ' yào yǐn zǐ', + '佛朗机' => ' fó lǎng jī', + '老骨头' => ' lǎo gú tou', + '老毛子' => ' lǎo máo zǐ', + '女人家' => ' nǚ rén jia', + '老处女' => ' lǎo chǔ nǚ', + '吃得下' => ' chī de xià', + '干燥机' => ' gān zào jī', + '老头儿' => ' lǎo tou er', + '煤烟子' => ' méi yān zǐ', + '箭靶子' => ' jiàn bǎ zǐ', + '乐谱架' => ' yuè pǔ jià', + '鱼秧子' => ' yú yāng zǐ', + '闷头儿' => ' mèn tou er', + '捎马子' => ' shāo mǎ zǐ', + '保得住' => ' bǎo de zhù', + '数不尽' => ' shù bu jìn', + '榛仁儿' => ' zhēn rén r', + '玉米粥' => ' yù mǐ zhōu', + '煤砟子' => ' méi zhǎ zǐ', + '房牙子' => ' fáng yá zǐ', + '伛偻病' => ' yú lǚ bìng', + '干燥剂' => ' gān zào jì', + '闷子车' => ' mèn zǐ chē', + '榜葛剌' => ' bǎng gé là', + '好奇心' => ' hào qí xīn', + '禁不住' => ' jīn bú zhù', + '乐府诗' => ' yuè fǔ shī', + '阿城区' => ' a chéng qū', + '协奏曲' => ' xié zòu qǔ', + '枪把儿' => ' qiāng bà r', + '票友儿' => ' piào yǒu r', + '尿盆儿' => ' niào pén r', + '溜边儿' => ' līu biān r', + '京都府' => ' jīng dū fǔ', + '呼啦圈' => ' hū lā quān', + '介子推' => ' jiè zǐ tuī', + '累加器' => ' lěi jiā qì', + '肖伯纳' => ' xiāo bó nà', + '消食儿' => ' xiāo shí r', + '镚子儿' => ' bèng zǐ er', + '厚薄规' => ' hòu bó guī', + '柏青哥' => ' bó qīng gē', + '四子王' => ' sì zǐ wáng', + '大轴子' => ' dà zhòu zǐ', + '背包客' => ' bēi bāo kè', + '背包袱' => ' bēi bāo fú', + '背影儿' => ' bèi yǐng r', + '接头儿' => ' jiē tou er', + '慢累积' => ' màn lěi jī', + '大少爷' => ' dà shào yé', + '干酪素' => ' gān lào sù', + '日月晕' => ' rì yuè yùn', + '是非题' => ' shì fēi tí', + '左撇子' => ' zuǒ piě zǐ', + '使绊子' => ' shǐ bàn zǐ', + '愣劲儿' => ' lèng jìn r', + '世家子' => ' shì jiā zǐ', + '什叶派' => ' shí yè pài', + '阿鲁巴' => ' a lǔ bā', + '乱麻麻' => ' luàn mā mɑ', + '结对子' => ' jié duì zǐ', + '大宛马' => ' dà yuān mǎ', + '禁得住' => ' jīn de zhù', + '大包干' => ' dà bāo gān', + '戴假发' => ' dài jiǎ fà', + '溜溜儿' => ' liū liù ér', + '女主角' => ' nǚ zhǔ jué', + '一般般' => ' yì bān bān', + '六君子' => ' liù jūn zǐ', + '大单于' => ' dà chán yú', + '六个月' => ' liù ge yuè', + '洋码子' => ' yáng mǎ zǐ', + '负电子' => ' fù diàn zǐ', + '翻篇儿' => ' fān piān r', + '酒令儿' => ' jǐu lìng r', + '柏辽兹' => ' bó liáo zī', + '胡椒子' => ' hú jiāo zǐ', + '负电荷' => ' fù diàn hè', + '担不是' => ' dān bú shì', + '派不是' => ' pài bú shì', + '酒馆儿' => ' jǐu guǎn r', + '脊柱裂' => ' jǐ zhù liè', + '释迦佛' => ' shì jiā fó', + '烟贩子' => ' yān fàn zǐ', + '烟斗丝' => ' yān dǒu sī', + '后处理' => ' hòu chǔ lǐ', + '作曲家' => ' zuò qǔ jiā', + '水磨机' => ' shuǐ mò jī', + '太子丹' => ' tài zǐ dān', + '佛蒙特' => ' fó méng tè', + '多目的' => ' duō mù dì', + '喇叭口' => ' lǎ ba kǒu', + '蔡李佛' => ' cài lǐ fó', + '碰磁儿' => ' pèng cí r', + '红箍儿' => ' hóng gū r', + '阿斯旺' => ' a sī wàng', + '阿尔山' => ' a ěr shān', + '阿洛菲' => ' a luò fēi', + '在那里' => ' zài nà li', + '干巴巴' => ' gān bā bā', + '阿荣旗' => ' a róng qí', + '阿曼湾' => ' a màn wān', + '碑座儿' => ' bēi zuò r', + '傻呵呵' => ' shǎ hē hē', + '晕乎乎' => ' yùn hū hū', + '阿拉善' => ' a lā shàn', + '耶和华' => ' yē hé huá', + '阿拉丁' => ' a lā dīng', + '磨得开' => ' mó de kāi', + '禁不起' => ' jīn bù qǐ', + '阿多诺' => ' a duō nuò', + '福安市' => ' fú ān shì', + '阿坝县' => ' a bà xiàn', + '石鼓区' => ' dàn gǔ qū', + '阿瑟县' => ' a sè xiàn', + '占地儿' => ' zhàn dì r', + '看得起' => ' kàn de qǐ', + '阿里郎' => ' a lǐ láng', + '加的斯' => ' jiā dì sī', + '耳刮子' => ' ěr guā zǐ', + '阿兰文' => ' a lán wén', + '羞答答' => ' xiū dā dā', + '染发剂' => ' rǎn fà jì', + '加勒比' => ' jiā lè bǐ', + '佛舍利' => ' fó shè lì', + '佛兰德' => ' fó lán dé', + '佛诞日' => ' fó dàn rì', + '胡燕妮' => ' hú yān nī', + '野孩子' => ' yě hái zǐ', + '加劲儿' => ' jiā jìn r', + '耶酥会' => ' yē sū huì', + '铁钩儿' => ' tiě gōu r', + '老妈子' => ' lǎo mā zǐ', + '一竿子' => ' yī gān zǐ', + '耙耳朵' => ' pā ěr duo', + '早班儿' => ' zǎo bān r', + '旱鸭子' => ' hàn yā zǐ', + '老伯伯' => ' lǎo bó bo', + '老佛爷' => ' lǎo fó yé', + '石磨机' => ' shí mò jī', + '耶稣会' => ' yē sū huì', + '阿卢巴' => ' a lú bā', + '一刹那' => ' yī chà nà', + '柳子戏' => ' liǔ zǐ xì', + '老帽儿' => ' lǎo mào r', + '老爷爷' => ' lǎo yé ye', + '眵目糊' => ' chī mu hū', + '阿瑟镇' => ' a sè zhèn', + '扣眼儿' => ' kòu yǎn r', + '处女膜' => ' chǔ nǚ mó', + '开花儿' => ' kāi huā r', + '五味子' => ' wǔ wèi zǐ', + '杰佛兹' => ' jié fó zī', + '做伴儿' => ' zuò bàn r', + '木齿耙' => ' mù chǐ pá', + '车份儿' => ' chē fèn r', + '躲不起' => ' duǒ bu qǐ', + '阿皮亚' => ' a pí yà', + '苏打粉' => ' sū dá fěn', + '乌拉草' => ' wù lɑ cǎo', + '木子美' => ' mù zǐ měi', + '五斗米' => ' wǔ dǒu mǐ', + '笔杆子' => ' bǐ gǎn zi', + '跟屁股' => ' gēn pì gu', + '跑味儿' => ' pǎo wèi r', + '五大夫' => ' wǔ dài fū', + '傻帽儿' => ' shǎ mào r', + '薄荷油' => ' bò he yóu', + '赶得及' => ' gǎn de jí', + '葛缕子' => ' gě lǔ: zi', + '弹力丝' => ' tán lì sī', + '弹力袜' => ' tán lì wà', + '借单儿' => ' jiè dān r', + '戏报子' => ' xì bào zǐ', + '开都河' => ' kāi dū hé', + '第四台' => ' dì sì tái', + '阿苏山' => ' a sū shān', + '亚曼牙' => ' yà màn yá', + '干儿子' => ' gān ér zi', + '阿莱曼' => ' a lái màn', + '阿育王' => ' ā yù wáng', + '阿罗约' => ' a luó yuē', + '阿的平' => ' ā dì píng', + '色差仪' => ' sè chā yí', + '石子儿' => ' shí zǐ er', + '石河子' => ' shí hé zǐ', + '阿坝州' => ' a bà zhōu', + '阿加维' => ' a jiā wéi', + '粒子流' => ' lì zǐ liú', + '梅西耶' => ' méi xī yē', + '一下子' => ' yī xià zǐ', + '六安市' => ' lù ān shì', + '粒子束' => ' lì zǐ shù', + '门坎儿' => ' mén kǎn r', + '秦都区' => ' qín dū qū', + '二流子' => ' èr liú zǐ', + '鱼丸子' => ' yú wán zǐ', + '阿伯丁' => ' a bó dīng', + '预处理' => ' yù chǔ lǐ', + '窝里反' => ' wō li fǎn', + '窝里斗' => ' wō li dòu', + '半分儿' => ' bàn fēn r', + '二里头' => ' er lǐ tou', + '针箍儿' => ' zhēn gū r', + '罗锅儿' => ' luó guō r', + '捆扎机' => ' kǔn zā jī', + '抓子儿' => ' zhuā zǐ r', + '大伯子' => ' dà bǎi zǐ', + '老婆子' => ' lǎo pó zǐ', + '发疟子' => ' fā yào zǐ', + '老夫子' => ' lǎo fū zǐ', + '度假区' => ' dù jià qū', + '得尔塔' => ' děi ěr tǎ', + '卷叶蛾' => ' juàn yè é', + '单子叶' => ' dān zǐ yè', + '宝葫芦' => ' bǎo hú lu', + '宝贝儿' => ' bǎo bèi r', + '积累率' => ' jī lěi lǜ', + '鸡内金' => ' jī nà jīn', + '老爷子' => ' lǎo yé zǐ', + '磨粉机' => ' mò fěn jī', + '找刺儿' => ' zhǎo cì r', + '对劲儿' => ' duì jìn r', + '对味儿' => ' duì wèi r', + '对得起' => ' duì de qǐ', + '对心儿' => ' duì xīn r', + '批处理' => ' pī chǔ lǐ', + '抹得开' => ' mò de kāi', + '娃娃菜' => ' wá wa cài', + '哈巴狗' => ' hǎ bā gǒu', + '哥特式' => ' gē tè shì', + '滴溜儿' => ' dī liù ér', + '尧都区' => ' yáo dū qū', + '活局子' => ' huó jú zǐ', + '地窨子' => ' dì yìn zǐ', + '多普勒' => ' duō pǔ lè', + '夜猫子' => ' yè māo zǐ', + '多佛尔' => ' duō fó ěr', + '墨水儿' => ' mò shuǐ r', + '独奏曲' => ' dú zòu qǔ', + '弹涂鱼' => ' tán tú yú', + '压不碎' => ' yā bu suì', + '都御使' => ' dū yù shǐ', + '克分子' => ' kè fèn zǐ', + '后钩儿' => ' hòu gōu r', + '堵塞费' => ' dǔ sè fèi', + '粪箕子' => ' fèn jī zǐ', + '培勒兹' => ' péi lè zī', + '大袋子' => ' dà dài zi', + '大舅子' => ' dà jiù zǐ', + '酒嗉子' => ' jiǔ sù zǐ', + '阿衣奴' => ' a yī nǔ', + '旧衣服' => ' jiù yī fu', + '离子束' => ' lí zǐ shù', + '离子流' => ' lí zǐ liú', + '大文蛤' => ' dà wén gé', + '在一起' => ' zài yì qǐ', + '类似于' => ' lèi sì yú', + '类似物' => ' lèi sì wù', + '阿萨德' => ' a sà dé', + '闹肚子' => ' nào dǔ zi', + '以此为' => ' yǐ cǐ wéi', + '一路货' => ' yí lù huò', + '打短儿' => ' dǎ duǎn r', + '打旋儿' => ' dǎ xuán r', + '喀喇沁' => ' kā lā qìn', + '打把式' => ' dǎ bǎ shi', + '打嘴巴' => ' dǎ zuǐ ba', + '打不过' => ' dǎ bu guò', + '折刀儿' => ' zhé dāo r', + '腿肚子' => ' tuǐ dǔ zi', + '一路人' => ' yí lù rén', + '勒维夫' => ' lè wéi fu', + '新都区' => ' xīn dū qū', + '一溜儿' => ' yī liù ér', + '启发式' => ' qǐ fà shì', + '继子女' => ' jì zǐ nǔ:', + '十八子' => ' shí bā zǐ', + '勒威耶' => ' lè wēi yē', + '任一个' => ' rèn yī ge', + '纹缕儿' => ' wén lǔ: r', + '纳匝肋' => ' nà zā lèi', + '绝门儿' => ' jué mén r', + '斯坦佛' => ' sī tǎn fó', + '斯宾塞' => ' sī bīn sè', + '以斯帖' => ' yǐ sī tiě', + '阿萨姆' => ' a sà mǔ', + '啦啦队' => ' lā lā duì', + '托勒密' => ' tuō lè mì', + '岔曲儿' => ' chà qǔ er', + '戳个儿' => ' chuō gè r', + '南歌子' => ' nán gē zǐ', + '哪门子' => ' nǎ mén zǐ', + '拿不住' => ' ná bú zhù', + '娃娃车' => ' wá wa chē', + '娃娃亲' => ' wá wa qīn', + '哈什蚂' => ' ha shi mà', + '宝坻区' => ' bǎo dǐ qū', + '折过儿' => ' zhē guò r', + '过去佛' => ' guò qù fó', + '目的论' => ' mù dì lùn', + '墨斗鱼' => ' mò dǒu yú', + '敕勒歌' => ' chì lè gē', + '古乐府' => ' gǔ yuè fǔ', + '奶嘴儿' => ' nǎi zuǐ r', + '奴儿干' => ' nú ér gān', + '门弟子' => ' mén dì zǐ', + '搁得住' => ' gé de zhù', + '镁离子' => ' měi lí zǐ', + '搁不住' => ' gé bú zhù', + '煤核儿' => ' méi hú ér', + '高压脊' => ' gāo yā jǐ', + '压根儿' => ' yà gēn ér', + '骨朵儿' => ' gū duǒ er', + '扳本儿' => ' bān běn r', + '抹不开' => ' mò bù kāi', + '补给船' => ' bǔ jǐ chuán', + '尽人事' => ' jìn rén shì', + '比不上' => ' bǐ bù shǎng', + '订书钉' => ' dìng shū dīng', + '男傧相' => ' nán bīn xiàng', + '番茄酱' => ' fān qié jiàng', + '新兴县' => ' xīn xīng xiàn', + '干燥症' => ' gān zào zhèng', + '应力场' => ' yìng lì chǎng', + '长宁区' => ' cháng níng qū', + '印相纸' => ' yìn xiàng zhǐ', + '长子县' => ' cháng zǐ xiàn', + '方框图' => ' fāng kuàng tú', + '长寿区' => ' cháng shòu qū', + '长沙市' => ' cháng shā shì', + '商都县' => ' shāng dū xiàn', + '传记性' => ' zhuàn jì xìng', + '动画片' => ' dòng huà piān', + '斜长石' => ' xié cháng shí', + '长武县' => ' cháng wǔ xiàn', + '感应圈' => ' gǎn yìng quān', + '长期性' => ' cháng qī xìng', + '干燥箱' => ' gān zào xiāng', + '应景诗' => ' yìng jǐng shī', + '长沙湾' => ' cháng shā wān', + '交响曲' => ' jiāo xiǎng qǔ', + '长治市' => ' cháng zhì shì', + '增温层' => ' zēng wēn céng', + '精校本' => ' jīng jiào běn', + '教学片' => ' jiào xué piān', + '亲家公' => ' qìng jiā gōng', + '海螵蛸' => ' hǎi piāo xiāo', + '校正子' => ' jiào zhèng zǐ', + '美容觉' => ' měi róng jiào', + '首相府' => ' shǒu xiàng fǔ', + '犍为县' => ' qián wèi xiàn', + '量角器' => ' liáng jiǎo qì', + '翟志刚' => ' zhái zhì gāng', + '命中率' => ' mìng zhòng lǜ', + '想倒美' => ' xiǎng dǎo měi', + '缝衣匠' => ' féng yī jiàng', + '弄明白' => ' nòng míng bai', + '一场空' => ' yī cháng kōng', + '长方体' => ' cháng fāng tǐ', + '昌都县' => ' chāng dū xiàn', + '撞运气' => ' zhuàng yùn qi', + '海兴县' => ' hǎi xīng xiàn', + '藏红花' => ' zàng hóng huā', + '请病假' => ' qǐng bìng jià', + '钦天监' => ' qīn tiān jiàn', + '说得上' => ' shuō de shàng', + '弹簧刀' => ' tán huáng dāo', + '瓶塞钻' => ' píng sāi zuàn', + '粘滞性' => ' nián zhì xìng', + '长洲区' => ' cháng zhōu qū', + '感应电' => ' gǎn yìng diàn', + '甩脸子' => ' shuǎi liǎn zǐ', + '犯得上' => ' fàn děi shàng', + '背饥荒' => ' bēi jī huāng', + '曾金燕' => ' zēng jīn yàn', + '招待会' => ' zhāo dāi huì', + '望都县' => ' wàng dū xiàn', + '绷弓子' => ' bēng gōng zi', + '谁知道' => ' shéi zhī dào', + '调酒师' => ' tiáo jǐu shī', + '片儿汤' => ' piān er tāng', + '漂白粉' => ' piǎo bái fěn', + '猪仔馆' => ' zhū zǎi guǎn', + '曾孝谷' => ' zēng xiào gǔ', + '指甲剪' => ' zhǐ jia jiǎn', + '两着儿' => ' liǎng zhāo r', + '偏差值' => ' piān chā zhí', + '空调器' => ' kōng tiáo qì', + '调药刀' => ' tiáo yào dāo', + '彭丽媛' => ' péng lì yuán', + '浪荡子' => ' làng dàng zǐ', + '两下子' => ' liǎng xià zǐ', + '曾繁仁' => ' zēng fán rén', + '挑衅者' => ' tiǎo xìn zhě', + '丰都县' => ' fēng dū xiàn', + '王尽美' => ' wáng jìn měi', + '空调机' => ' kōng tiáo jī', + '电子战' => ' diàn zǐ zhàn', + '土党参' => ' tǔ dǎng shēn', + '诸葛亮' => ' zhū gě liàng', + '宝兴县' => ' bǎo xīng xiàn', + '长清区' => ' cháng qīng qū', + '当世冠' => ' dāng shì guàn', + '长乐宫' => ' cháng lè gōng', + '迎新会' => ' yíng xīn kuài', + '流行性' => ' liú xíng xìng', + '银行券' => ' yín háng juàn', + '不正当' => ' bù zhèng dàng', + '钻牛角' => ' zuàn niú jiǎo', + '孪生子' => ' luán shēng zǐ', + '管弦乐' => ' guǎn xián yuè', + '角斗场' => ' jué dòu chǎng', + '用得上' => ' yòng de shàng', + '施甸县' => ' shī diàn xiàn', + '外行话' => ' wài háng huà', + '陪产假' => ' péi chǎn jià', + '典当业' => ' diǎn dàng yè', + '振兴区' => ' zhèn xīng qū', + '孔子庙' => ' kǒng zǐ miào', + '泼脏水' => ' pō zāng shuǐ', + '谈朋友' => ' tán péng you', + '弹跳板' => ' tán tiào bǎn', + '调节者' => ' tiáo jié zhě', + '有年头' => ' yǒu nián tou', + '电子表' => ' diàn zǐ biǎo', + '壁效应' => ' bì xiào yìng', + '罗甸县' => ' luó diàn xiàn', + '钉书针' => ' dìng shū zhēn', + '冠军赛' => ' guàn jūn sài', + '占星家' => ' zhān xīng jiā', + '相似性' => ' xiāng sì xìng', + '光电子' => ' guāng diàn zǐ', + '对称性' => ' duì chèn xìng', + '瞭望台' => ' liào wàng tái', + '小脏鬼' => ' xiǎo zāng guǐ', + '曾国荃' => ' zēng guó quán', + '对话框' => ' duì huà kuàng', + '蒙蒙黑' => ' mēng mēng hēi', + '占星师' => ' zhān xīng shī', + '衣冠冢' => ' yì guān zhǒng', + '占星术' => ' zhān xīng shù', + '哑终端' => ' yā zhōng duān', + '挑大梁' => ' tiǎo dà liáng', + '占星学' => ' zhān xīng xué', + '小便斗' => ' xiǎo biàn dǒu', + '三连冠' => ' sān lián guàn', + '冲击钻' => ' chōng jī zuàn', + '散兵线' => ' sǎn bīng xiàn', + '打长工' => ' dǎ cháng gōng', + '三重门' => ' sān chóng mén', + '标准差' => ' biāo zhǔn chā', + '长须鲸' => ' cháng xū jīng', + '小数点' => ' xiǎo shǔ diǎn', + '相位差' => ' xiàng wèi chā', + '挑战者' => ' tiǎo zhàn zhě', + '够得上' => ' gòu děi shàng', + '光散射' => ' guāng sǎn shè', + '应战书' => ' yìng zhàn shū', + '扁担星' => ' biǎn dàn xīng', + '长筒袜' => ' cháng tǒng wà', + '交白卷' => ' jiāo bái juàn', + '朝鲜文' => ' cháo xiǎn wén', + '正电荷' => ' zhèng diàn hè', + '见识浅' => ' jiàn shi qiǎn', + '冲孔机' => ' chòng kǒng jī', + '空调室' => ' kōng tiáo shì', + '原子钟' => ' yuán zǐ zhōng', + '空白点' => ' kòng bái diǎn', + '碱中毒' => ' jiǎn zhòng dú', + '对称轴' => ' duì chèn zhóu', + '空调车' => ' kōng tiáo chē', + '长颈鹿' => ' cháng jǐng lù', + '椎间盘' => ' zhuī jiān pán', + '长野县' => ' cháng yě xiàn', + '枕头风' => ' zhěn tou fēng', + '北朝鲜' => ' běi cháo xiǎn', + '公仔面' => ' gōng zǎi miàn', + '弦切角' => ' xián qiē jiǎo', + '病假条' => ' bìng jià tiáo', + '张国焘' => ' zhāng guó tāo', + '眼干症' => ' yǎn gān zhèng', + '旁切圆' => ' páng qiē yuán', + '卷层云' => ' juàn céng yún', + '金山屯' => ' jīn shān zhūn', + '世界上' => ' shì jiè shang', + '长短句' => ' cháng duǎn jù', + '赶得上' => ' gǎn děi shàng', + '飞将军' => ' fēi jiàng jūn', + '弹簧门' => ' tán huáng mén', + '正安县' => ' zhēng an xiàn', + '长春子' => ' cháng chūn zǐ', + '紧绷绷' => ' jǐn bēng bēng', + '请假条' => ' qǐng jià tiáo', + '钻井队' => ' zuàn jǐng duì', + '石头城' => ' shí tou chéng', + '钱串子' => ' qián chuàn zǐ', + '唐三藏' => ' táng sān zàng', + '取景框' => ' qǔ jǐng kuàng', + '绍兴市' => ' shào xīng shì', + '绍兴酒' => ' shào xīng jiǔ', + '风切变' => ' fēng qiē biàn', + '止疼片' => ' zhǐ téng piān', + '校正器' => ' jiào zhèng qì', + '结婚证' => ' jiē hūn zhèng', + '始兴县' => ' shǐ xīng xiàn', + '应用文' => ' yìng yòng wén', + '弹簧锁' => ' tán huáng suǒ', + '爱漂亮' => ' ài piào liang', + '香泡树' => ' xiāng pāo shù', + '肠杆菌' => ' cháng gǎn jūn', + '砷中毒' => ' shēn zhòng dú', + '敲竹杠' => ' qiāo zhū gàng', + '公切线' => ' gōng qiē xiàn', + '散兵坑' => ' sǎn bīng kēng', + '碰头会' => ' pèng tóu kuài', + '朝阳门' => ' zhāo yáng mén', + '复选框' => ' fù xuǎn kuàng', + '南朝鲜' => ' nán cháo xiǎn', + '中子源' => ' zhōng zǐ yuán', + '南北长' => ' nán běi cháng', + '协调员' => ' xié tiáo yuán', + '最前面' => ' zuì qián mian', + '中子星' => ' zhōng zǐ xīng', + '少年犯' => ' shào nián fàn', + '知更鸟' => ' zhī gēng niǎo', + '中毒性' => ' zhòng dú xìng', + '膀胱癌' => ' páng guāng ái', + '短波长' => ' duǎn bō cháng', + '汞中毒' => ' gǒng zhòng dú', + '双子座' => ' shuāng zǐ zuò', + '少女峰' => ' shào nǔ: fēng', + '强迫性' => ' qiǎng pò xìng', + '公冶长' => ' gōng yě cháng', + '载畜量' => ' zǎi chù liàng', + '中爪哇' => ' zhōng zhǎo wā', + '中洋脊' => ' zhōng yáng jǐ', + '再加上' => ' zài jiā shang', + '拍卖行' => ' pāi mài háng', + '挑花眼' => ' tiǎo huā yǎn', + '正规化' => ' zhèng guī huā', + '吊膀子' => ' diào bàng zi', + '弹钢琴' => ' tán gāng qín', + '转铃儿' => ' zhuàn líng r', + '转轴儿' => ' zhuàn zhóu r', + '运货员' => ' yùn huò yuán', + '忘不了' => ' wàng bù liǎo', + '粮食局' => ' liáng shi jú', + '两三个' => ' liǎng sān ge', + '还原剂' => ' huán yuán jì', + '搬楦头' => ' bān xuàn tou', + '司天监' => ' sī tiān jiàn', + '两个月' => ' liǎng ge yuè', + '遇难船' => ' yù nàn chuán', + '曲松县' => ' qǔ sōng xiàn', + '搓麻将' => ' cuō má jiàng', + '千佛山' => ' qiān fó shān', + '曲柄钻' => ' qū bǐng zuàn', + '曲水县' => ' qǔ shuǐ xiàn', + '良家子' => ' liáng jiā zǐ', + '转腰子' => ' zhuàn yāo zi', + '弹棉花' => ' tán mián huā', + '还魂纸' => ' huán hún zhǐ', + '跟不上' => ' gēn bu shàng', + '微中子' => ' wēi zhōng zǐ', + '圆石头' => ' yuán shí tou', + '不着调' => ' bù zháo diào', + '复兴党' => ' fù xīng dǎng', + '山岗子' => ' shān gāng zǐ', + '中郎将' => ' zhōng láng jiàng', + '电子层' => ' diàn zǐ céng', + '整脊学' => ' zhěng jǐ xué', + '找不着' => ' zhǎo bu zháo', + '冲盹儿' => ' chòng dǔn er', + '电子管' => ' diàn zǐ guǎn', + '间谍罪' => ' jiàn dié zuì', + '少女风' => ' shào nǚ fēng', + '门框子' => ' mén kuàng zi', + '数不上' => ' shǔ bù shàng', + '开拓性' => ' kāi tuò xìng', + '撑得住' => ' chēng de zhù', + '这年头' => ' zhè nián tou', + '扎囊县' => ' zā náng xiàn', + '协调人' => ' xié tiáo rén', + '卷心菜' => ' juàn xīn cài', + '山桐子' => ' shān tóng zǐ', + '协方差' => ' xié fāng chā', + '银行家' => ' yín háng jiā', + '调解人' => ' tiáo jiě rén', + '随想曲' => ' suí xiǎng qǔ', + '粘合胶' => ' nián hé jiāo', + '速调管' => ' sù tiáo guǎn', + '曲阳县' => ' qǔ yáng xiàn', + '车行道' => ' chē háng dào', + '天仙子' => ' tiān xiān zǐ', + '吃枪子' => ' chī qiāng zǐ', + '金曲奖' => ' jīn qǔ jiǎng', + '切换到' => ' qiē huàn dào', + '重复节' => ' chóng fù jié', + '鲁甸县' => ' lǔ diàn xiàn', + '重眼皮' => ' chóng yǎn pí', + '可行性' => ' kě xíng xìng', + '鲗鱼涌' => ' zéi yú chōng', + '大黄素' => ' dài huáng sù', + '嚼舌根' => ' jiáo shé gēn', + '嚼舌头' => ' jiáo shé tou', + '量子化' => ' liàng zǐ huà', + '脑栓塞' => ' nǎo shuān sè', + '受得了' => ' shòu de liǎo', + '重头戏' => ' chóng tóu xì', + '长安街' => ' cháng ān jiē', + '切削刃' => ' qiē xiāo rèn', + '星子县' => ' xīng zǐ xiàn', + '星宿海' => ' xīng xiù hǎi', + '量体温' => ' liáng tǐ wēn', + '切削面' => ' qiē xuē miàn', + '龙兴寺' => ' lóng xīng sì', + '想得开' => ' xiǎng de kāi', + '想不到' => ' xiǎng bú dào', + '切削角' => ' qiē xuē jiǎo', + '量子论' => ' liàng zǐ lùn', + '情急了' => ' qíng jí liǎo', + '召陵区' => ' shào líng qū', + '切向力' => ' qiē xiàng lì', + '更新版' => ' gēng xīn bǎn', + '分子量' => ' fèn zǐ liàng', + '粘性土' => ' nián xìng tǔ', + '调节板' => ' tiáo jié bǎn', + '调节池' => ' tiáo jié chí', + '娘子军' => ' niáng zǐ jūn', + '累进税' => ' lěi jìn shuì', + '更年期' => ' gēng nián qī', + '冷脸子' => ' lěng liǎn zǐ', + '转差率' => ' zhuǎn chā lǜ', + '转字锁' => ' zhuàn zì suǒ', + '更新世' => ' gēng xīn shì', + '均匀性' => ' jūn yún xìng', + '南乡子' => ' nán xiāng zǐ', + '轻音乐' => ' qīng yīn yuè', + '切成丝' => ' qiē chéng sī', + '载客量' => ' zǎi kè liàng', + '控制杆' => ' kòng zhì gǎn', + '白娘子' => ' bái niáng zǐ', + '肋条肉' => ' lèi tiáo ròu', + '软龈音' => ' ruǎn yín yīn', + '软着陆' => ' ruǎn zhuó lù', + '垣曲县' => ' yuán qǔ xiàn', + '乾隆帝' => ' qián lóng dì', + '利眠宁' => ' lì mián nìng', + '上大夫' => ' shàng dài fū', + '手指头' => ' shǒu zhí tou', + '苯中毒' => ' běn zhòng dú', + '数九天' => ' shǔ jǐu tiān', + '长舌妇' => ' cháng shé fù', + '出差费' => ' chū chāi fèi', + '音乐院' => ' yīn yuè yuàn', + '处分权' => ' chǔ fèn quán', + '顶呱呱' => ' dǐng guā guā', + '长乐市' => ' cháng lè shì', + '马生角' => ' mǎ shēng jué', + '斗南镇' => ' dǒu nán zhèn', + '应变力' => ' yìng biàn lì', + '新干县' => ' xīn gān xiàn', + '灵雀寺' => ' líng qiǎo sì', + '闪亮儿' => ' shǎn liàng r', + '抽空机' => ' chōu kòng jī', + '十天干' => ' shí tiān gān', + '冲劲儿' => ' chòng jìn er', + '汇出行' => ' huì chū háng', + '成都市' => ' chéng dū shì', + '长途车' => ' cháng tú chē', + '长葛市' => ' cháng gě shì', + '北斗镇' => ' běi dǒu zhèn', + '成方儿' => ' chéng fāng r', + '北斗星' => ' běi dǒu xīng', + '热效应' => ' rè xiào yìng', + '文化圈' => ' wén huà juàn', + '抽冷子' => ' chōu lěng zǐ', + '全武行' => ' quán wǔ háng', + '新娘子' => ' xīn niáng zǐ', + '涂浆台' => ' tú jiàng tái', + '调味汁' => ' tiáo wèi zhī', + '曾国藩' => ' zēng guó fān', + '点电荷' => ' diǎn diàn hè', + '调味品' => ' tiáo wèi pǐn', + '有弹性' => ' yǒu tán xìng', + '电熨斗' => ' diàn yùn dǒu', + '冰凝器' => ' bīng níng qì', + '八重奏' => ' bā chóng zòu', + '出风头' => ' chū fēng tou', + '贡嘎县' => ' gòng gá xiàn', + '牛仔衫' => ' niú zǎi shān', + '电荷泵' => ' diàn hè bèng', + '单房差' => ' dān fáng chā', + '命名法' => ' mìng míng fǎ', + '命名日' => ' mìng míng rì', + '病秧子' => ' bìng yāng zǐ', + '免不了' => ' miǎn bù liǎo', + '呱呱叫' => ' guā guā jiào', + '伸舌头' => ' shēn shé tou', + '调解书' => ' tiáo jiě shū', + '音乐厅' => ' yīn yuè tīng', + '长袍儿' => ' cháng páo er', + '抓功夫' => ' zhuā gōng fu', + '应聘者' => ' yìng pìn zhě', + '非对称' => ' fēi duì chèn', + '应名儿' => ' yìng míng ér', + '人参果' => ' rén shēn guǒ', + '隆子县' => ' lóng zǐ xiàn', + '吹奏乐' => ' chuī zòu yuè', + '千佛洞' => ' qiān fó dòng', + '射流泵' => ' shè liú bèng', + '受难者' => ' shòu nàn zhě', + '双子叶' => ' shuāng zǐ yè', + '喘不过' => ' chuǎn bu guò', + '南长区' => ' nán cháng qū', + '打孔钻' => ' dǎ kǒng zuàn', + '难民营' => ' nàn mín yíng', + '难为情' => ' nán wéi qíng', + '扫帚菜' => ' sào zhǒu cài', + '阳电荷' => ' yáng diàn hè', + '反应锅' => ' fǎn yìng guō', + '阳电子' => ' yáng diàn zǐ', + '阳曲县' => ' yáng qǔ xiàn', + '随风倒' => ' suí fēng dǎo', + '占地方' => ' zhàn dì fang', + '塞上曲' => ' sāi shàng qǔ', + '三勒浆' => ' sān lè jiāng', + '博兴县' => ' bó xīng xiàn', + '打水漂' => ' dǎ shuǐ piāo', + '三娘子' => ' sān niáng zǐ', + '阿莲乡' => ' a lián xiāng', + '雄配子' => ' xíong pèi zǐ', + '反斜杠' => ' fǎn xié gàng', + '反应式' => ' fǎn yìng shì', + '原子能' => ' yuán zǐ néng', + '马槟榔' => ' mǎ bīng lang', + '十三省' => ' shí sān xǐng', + '长恨歌' => ' cháng hèn gē', + '穷骨头' => ' qióng gú tou', + '表蒙子' => ' biǎo méng zǐ', + '反间谍' => ' fǎn jiàn dié', + '钱串儿' => ' qián chuàn r', + '受不了' => ' shòu bù liǎo', + '铺面房' => ' pū miàn fáng', + '纯音乐' => ' chún yīn yuè', + '磁效应' => ' cí xiào yìng', + '香子兰' => ' xiāng zǐ lán', + '鹿角胶' => ' lù jiǎo jiāo', + '反应堆' => ' fǎn yìng duī', + '纯阳子' => ' chún yáng zǐ', + '生发油' => ' shēng fà yóu', + '操纵杆' => ' cāo zòng gǎn', + '绿茸茸' => ' lǜ róng róng', + '担担面' => ' dàn dɑn miàn', + '曲颈甑' => ' qǔ jǐng zèng', + '生查子' => ' shēng zhā zǐ', + '嘉兴市' => ' jiā xīng shì', + '时间差' => ' shí jiān chā', + '嘉荫县' => ' jiā yìn xiàn', + '钻空子' => ' zuān kòng zi', + '长统袜' => ' cháng tǒng wà', + '长臂猿' => ' cháng bì yuán', + '怔神儿' => ' zhēng shén r', + '调整到' => ' tiáo zhěng dào', + '少年宫' => ' shào nián gōng', + '张僧繇' => ' zhāng sēng yóu', + '乱哄哄' => ' luàn hǒng hǒng', + '撑得慌' => ' chēng de huāng', + '码长城' => ' mǎ cháng chéng', + '检察长' => ' jiǎn chá cháng', + '间断性' => ' jiàn duàn xìng', + '轧钢厂' => ' zhá gāng chǎng', + '半长轴' => ' bàn cháng zhóu', + '统称为' => ' tǒng chēng wéi', + '出洋相' => ' chū yáng xiàng', + '切成块' => ' qiē chéng kuài', + '多重性' => ' duō chóng xìng', + '天长市' => ' tiān cháng shì', + '双子宫' => ' shuāng zǐ gōng', + '脑中风' => ' nǎo zhòng fēng', + '膀胱炎' => ' páng guāng yán', + '着重号' => ' zhuó zhòng hào', + '四重唱' => ' sì chóng chàng', + '曾庆红' => ' zēng qìng hóng', + '双折射' => ' shuāng zhē shè', + '蒜茸钳' => ' suàn róng qián', + '眼镜框' => ' yǎn jìng kuàng', + '谷梁传' => ' gǔ liáng zhuàn', + '种植园' => ' zhòng zhí yuán', + '弹簧钢' => ' tán huáng gāng', + '陈省身' => ' chén xǐng shēn', + '供应站' => ' gòng yīng zhàn', + '瞭望哨' => ' liào wàng shào', + '脏躁症' => ' zāng zào zhèng', + '灯笼椒' => ' dēng long jiāo', + '供应舰' => ' gōng yìng jiàn', + '小旋风' => ' xiǎo xuàn fēng', + '光波长' => ' guāng bō cháng', + '兜率天' => ' dōu shuài tiān', + '光感应' => ' guāng gǎn yìng', + '兜率宫' => ' dōu shuài gōng', + '强迫症' => ' qiǎng pò zhèng', + '长相思' => ' zhǎng xiàng sī', + '旋风装' => ' xuàn fēng zhuāng', + '长公主' => ' cháng gōng zhǔ', + '长兴岛' => ' cháng xīng dǎo', + '衡量制' => ' héng liang zhì', + '桑螵蛸' => ' sāng piāo xiāo', + '九重霄' => ' jiǔ chóng xiāo', + '身分证' => ' shēn fèn zhèng', + '闪长岩' => ' shǎn cháng yán', + '门边框' => ' mén biān kuàng', + '少年行' => ' shào nián xíng', + '王舍城' => ' wáng shè chéng', + '独唱曲' => ' dú chàng qǔ', + '谢长廷' => ' xiè cháng tíng', + '双曲面' => ' shuāng qǔ miàn', + '长岛县' => ' cháng dǎo xiàn', + '田长霖' => ' tián cháng lín', + '弹簧销' => ' tán huáng xiāo', + '长毛绒' => ' cháng máo róng', + '混水墙' => ' hún shuǐ qiáng', + '永兴县' => ' yǒng xīng xiàn', + '长命锁' => ' cháng mìng suǒ', + '长流水' => ' cháng liú shuǐ', + '龈脓肿' => ' yín nóng zhǒng', + '甲壳虫' => ' jiǎ qiào chóng', + '洪洞县' => ' hóng tóng xiàn', + '轰隆隆' => ' hōng lōng lóng', + '乾清宫' => ' qián qīng gōng', + '青少年' => ' qīng shào nián', + '重阳节' => ' chóng yáng jié', + '炸酱面' => ' zhá jiàng miàn', + '农场主' => ' nóng cháng zhǔ', + '青囊经' => ' qīng náng jīng', + '黄萎病' => ' huáng wěi bìng', + '光反应' => ' guāng fǎn yìng', + '广成子' => ' guǎng chéng zǐ', + '霰弹枪' => ' xiàn dàn qiāng', + '电转盘' => ' diàn zhuàn pán', + '清算行' => ' qīng suàn háng', + '青藏线' => ' qīng zàng xiàn', + '长长的' => ' cháng cháng de', + '星相学' => ' xīng xiàng xué', + '手相学' => ' shǒu xiàng xué', + '兴山县' => ' xīng shān xiàn', + '延长县' => ' yán cháng xiàn', + '两边倒' => ' liǎng biān dǎo', + '连接框' => ' lián jiē kuàng', + '相位表' => ' xiàng wèi biǎo', + '相适应' => ' xiāng shì yìng', + '响应值' => ' xiǎng yìng zhí', + '兴隆县' => ' xīng lóng xiàn', + '粮食厅' => ' liáng shi tīng', + '相电流' => ' xiàng diàn liú', + '兴城市' => ' xīng chéng shì', + '兴中会' => ' xīng zhōng huì', + '适应症' => ' shì yìng zhèng', + '鹿角霜' => ' lù jiǎo shuāng', + '罗盛教' => ' luó chéng jiào', + '侍应生' => ' shì yìng shēng', + '杓球场' => ' sháo qíu chǎng', + '延长线' => ' yán cháng xiàn', + '藏经洞' => ' zàng jīng dòng', + '重庆市' => ' chóng qìng shì', + '转轮王' => ' zhuàn lún wáng', + '弹簧管' => ' tán huáng guǎn', + '薯莨绸' => ' shǔ liáng chóu', + '星相术' => ' xīng xiàng shù', + '萧万长' => ' xiāo wàn cháng', + '星相师' => ' xīng xiàng shī', + '长城卡' => ' cháng chéng kǎ', + '应召站' => ' yìng zhào zhàn', + '宽甸县' => ' kuān diàn xiàn', + '前首相' => ' qián shǒu xiàng', + '相空间' => ' xiàng kōng jiān', + '长垣县' => ' cháng yuán xiàn', + '范长江' => ' fàn cháng jiāng', + '白晃晃' => ' bái huàng huǎng', + '工段长' => ' gōng duàn cháng', + '公平秤' => ' gōng píng chèng', + '绷场面' => ' bēng chǎng miàn', + '娘娘腔' => ' niáng niang qiāng', + '重正化' => ' chóng zhèng huà', + '转动轴' => ' zhuàn dòng zhóu', + '转动件' => ' zhuàn dòng jiàn', + '上将军' => ' shàng jiàng jūn', + '长宁县' => ' cháng níng xiàn', + '正当性' => ' zhèng dàng xìng', + '相对象' => ' xiàng duì xiàng', + '旧框框' => ' jiù kuàng kuang', + '金晃晃' => ' jīn huàng huǎng', + '左丞相' => ' zuǒ chéng xiàng', + '照相馆' => ' zhào xiàng guǎn', + '正长石' => ' zhèng cháng shí', + '长绒棉' => ' cháng róng mián', + '长寿面' => ' cháng shòu miàn', + '老框框' => ' lǎo kuàng kuàng', + '上半晌' => ' shàng bàn shǎng', + '肖像权' => ' xiāo xiàng quán', + '长岭县' => ' cháng lǐng xiàn', + '供应商' => ' gōng yìng shāng', + '长角羊' => ' cháng jiǎo yáng', + '扛长工' => ' káng cháng gōng', + '蒙蒙亮' => ' méng mēng liàng', + '长城牌' => ' cháng chéng pái', + '长庚星' => ' cháng gēng xīng', + '重甸甸' => ' zhòng diàn diàn', + '还乡团' => ' huán xiāng tuán', + '相平面' => ' xiàng píng miàn', + '中奖者' => ' zhòng jiǎng zhě', + '油晃晃' => ' yóu huàng huǎng', + '右丞相' => ' yòu chéng xiàng', + '长丰县' => ' cháng fēng xiàn', + '长兴县' => ' cháng xīng xiàn', + '长方形' => ' cháng fāng xíng', + '调整环' => ' tiáo zhěng huán', + '长生果' => ' cháng shēng guǒ', + '长滨乡' => ' cháng bīn xiāng', + '长治乡' => ' cháng zhì xiāng', + '长汀县' => ' cháng tīng xiàn', + '长毛象' => ' cháng máo xiàng', + '膀胱镜' => ' páng guāng jìng', + '长颈龙' => ' cháng jǐng lóng', + '长颈瓶' => ' cháng jǐng píng', + '长顺县' => ' cháng shùn xiàn', + '长明灯' => ' cháng míng dēng', + '团团转' => ' tuán tuán zhuàn', + '厚生相' => ' hòu shēng xiàng', + '工伤假' => ' gōng shāng jià', + '长白镇' => ' cháng bái zhèn', + '弹簧床' => ' tán huáng chuáng', + '长城站' => ' cháng chéng zhàn', + '创伤后' => ' chuāng shāng hòu', + '重定向' => ' chóng dìng xiàng', + '穷酸相' => ' qíong suān xiàng', + '长泰县' => ' cháng tài xiàn', + '兴冲冲' => ' xīng chōng chōng', + '应用层' => ' yìng yòng céng', + '长治县' => ' cháng zhì xiàn', + '长沙县' => ' cháng shā xiàn', + '茄萣乡' => ' qié dìng xiāng', + '江城子' => ' jiāng chéng zǐ', + '长海县' => ' cháng hǎi xiàn', + '长蛇阵' => ' cháng shé zhèn', + '二重唱' => ' èr chóng chàng', + '长统靴' => ' cháng tǒng xuē', + '长筒靴' => ' cháng tǒng xuē', + '应用性' => ' yìng yòng xìng', + '粘着性' => ' zhān zhuó xìng', + '长春市' => ' cháng chūn shì', + '村上隆' => ' cūn shàng lōng', + '旋风脚' => ' xuàn fēng jiǎo', + '檀香扇' => ' tán xiāng shàn', + '交响乐' => ' jiāo xiǎng yuè', + '长生天' => ' cháng shēng tiān', + '蛮漂亮' => ' mán piào liang', + '中间件' => ' zhōng jiān jiàn', + '两重天' => ' liǎng chóng tiān', + '中长跑' => ' zhōng cháng pǎo', + '花生浆' => ' huā shēng jiàng', + '双生子' => ' shuāng shēng zǐ', + '少壮派' => ' shào zhuàng pài', + '应声虫' => ' yìng shēng chóng', + '中甸县' => ' zhōng diàn xiàn', + '敲丧钟' => ' qiāo sāng zhōng', + '楞怔怔' => ' léng zhèng zhèng', + '正仓院' => ' zhēng cāng yuàn', + '众生相' => ' zhòng shēng xiàng', + '哮喘病' => ' xiào chuǎn bìng', + '两重性' => ' liǎng chóng xìng', + '长生殿' => ' cháng shēng diàn', + '卷轴装' => ' juàn zhóu zhuāng', + '元长乡' => ' yuán cháng xiāng', + '川滇藏' => ' chuān diān zàng', + '川党参' => ' chuān dǎng shēn', + '切向量' => ' qiē xiàng liàng', + '弓长岭' => ' gōng cháng lǐng', + '弹簧秤' => ' tán huáng chèng', + '朝鲜筝' => ' cháo xiǎn zhēng', + '公羊传' => ' gōng yáng zhuàn', + '长白县' => ' cháng bái xiàn', + '长白山' => ' cháng bái shān', + '中标者' => ' zhòng biāo zhě', + '香獐子' => ' xiāng zhāng zǐ', + '二重性' => ' èr chóng xìng', + '游说团' => ' yóu shuì tuán', + '链反应' => ' liàn fǎn yìng', + '传记片' => ' zhuàn jì piàn', + '供应者' => ' gōng yìng zhě', + '着陆场' => ' zhuó lù chǎng', + '着眼点' => ' zhuó yǎn diǎn', + '怪念头' => ' guài niàn tou', + '大城市' => ' dài chéng shì', + '性行为' => ' xìng xíng wéi', + '黑糁糁' => ' hēi shēn shēn', + '辉长岩' => ' huī cháng yán', + '性偏好' => ' xìng piān hào', + '性兴奋' => ' xìng xīng fèn', + '弹着点' => ' dàn zhuó diǎn', + '天台山' => ' tiān tāi shān', +); \ No newline at end of file diff --git a/vendor/overtrue/pinyin/data/words_1 b/vendor/overtrue/pinyin/data/words_1 new file mode 100644 index 0000000..e1b4cfb --- /dev/null +++ b/vendor/overtrue/pinyin/data/words_1 @@ -0,0 +1,8003 @@ + ' lěng zhá gāng', + '廊坊市' => ' láng fáng shì', + '酌处权' => ' zhuó chǔ quán', + '刀削面' => ' dāo xiāo miàn', + '李长春' => ' lǐ cháng chūn', + '切平面' => ' qiē píng miàn', + '还原酶' => ' huán yuán méi', + '不相称' => ' bù xiāng chèn', + '丹参酮' => ' dān shēn tóng', + '行香子' => ' xíng xiāng zǐ', + '挑战书' => ' tiǎo zhàn shū', + '能量子' => ' néng liàng zǐ', + '兴隆台' => ' xīng lóng tái', + '转速表' => ' zhuàn sù biǎo', + '快中子' => ' kuài zhōng zǐ', + '适应性' => ' shì yìng xìng', + '致良知' => ' zhì liáng zhī', + '电子秤' => ' diàn zǐ chèng', + '扬子江' => ' yáng zǐ jiāng', + '同字框' => ' tóng zì kuàng', + '兴国县' => ' xīng guó xiàn', + '退行性' => ' tuì xíng xìng', + '换档杆' => ' huàn dàng gǎn', + '分散相' => ' fēn sàn xiàng', + '毛茸茸' => ' máo róng róng', + '换挡杆' => ' huàn dǎng gǎn', + '东兴市' => ' dōng xīng shì', + '东三省' => ' dōng sān xǐng', + '救难船' => ' jiù nàn chuán', + '保险杠' => ' bǎo xiǎn gàng', + '豆豉酱' => ' dòu chǐ jiàng', + '灰蒙蒙' => ' huī mēng méng', + '黄埔港' => ' huáng pǔ gǎng', + '酸中毒' => ' suān zhòng dú', + '进行性' => ' jìn xíng xìng', + '兴海县' => ' xīng hǎi xiàn', + '豁免权' => ' huò miǎn quán', + '三重市' => ' sān chóng shì', + '都昌县' => ' dū chāng xiàn', + '九转丹' => ' jiǔ zhuàn dān', + '空白表' => ' kòng bái biǎo', + '间歇泉' => ' jiàn xiē quán', + '间接税' => ' jiàn jiē shuì', + '霰粒肿' => ' xiàn lì zhǒng', + '寻甸县' => ' xún diàn xiàn', + '戳脊梁' => ' chuō jǐ liang', + '洋中脊' => ' yáng zhōng jǐ', + '有粘性' => ' yǒu nián xìng', + '开小差' => ' kāi xiǎo chāi', + '朝鲜人' => ' cháo xiǎn rén', + '内省性' => ' nèi xǐng xìng', + '挑逗性' => ' tiǎo dòu xìng', + '福兴乡' => ' fú xīng xiāng', + '称之为' => ' chēng zhī wéi', + '照相簿' => ' zhào xiàng bù', + '白蛇传' => ' bái shé zhuàn', + '咸兴市' => ' xián xīng shì', + '关卡税' => ' guān qiǎ shuì', + '弧线长' => ' hú xiàn cháng', + '原子量' => ' yuán zǐ liàng', + '长蛇座' => ' cháng shé zuò', + '丧葬费' => ' sāng zàng fèi', + '乔冠华' => ' qiáo guān huà', + '子长县' => ' zǐ cháng xiàn', + '量油尺' => ' liáng yóu chǐ', + '称多县' => ' chèn duō xiàn', + '见世面' => ' xiàn shì miàn', + '三重奏' => ' sān chóng zòu', + '转一趟' => ' zhuàn yī tàng', + '线桄子' => ' xiàn guàng zi', + '转车台' => ' zhuàn chē tái', + '供应品' => ' gōng yìng pǐn', + '涡阳县' => ' guō yáng xiàn', + '供应室' => ' gōng yìng shì', + '拓荒者' => ' tuò huāng zhě', + '兴奋性' => ' xīng fèn xìng', + '缝纫工' => ' féng rèn gōng', + '一年生' => ' yì nián shēng', + '自转轴' => ' zì zhuàn zhóu', + '天台宗' => ' tiān tāi zōng', + '电子枪' => ' diàn zǐ qiāng', + '电荷量' => ' diàn hè liàng', + '弹性销' => ' tán xìng xiāo', + '铜管乐' => ' tóng guǎn yuè', + '还原焰' => ' huán yuán yàn', + '烹调术' => ' pēng tiáo shù', + '切空间' => ' qiē kōng jiān', + '凉州曲' => ' liáng zhōu qǔ', + '三框栏' => ' sān kuàng lán', + '兴仁县' => ' xīng rén xiàn', + '相似形' => ' xiāng sì xíng', + '扇面琴' => ' shān miàn qín', + '间谍网' => ' jiàn dié wǎng', + '兴文县' => ' xīng wén xiàn', + '扫帚星' => ' sào zhǒu xīng', + '命令行' => ' mìng lìng háng', + '摔跟头' => ' shuāi gēn tou', + '教书匠' => ' jiāo shū jiàng', + '照相纸' => ' zhào xiàng zhǐ', + '长寿菜' => ' cháng shòu cài', + '枞阳县' => ' zōng yáng xiàn', + '荥经县' => ' yíng jīng xiàn', + '着火点' => ' zháo huǒ diǎn', + '幻想曲' => ' huàn xiǎng qǔ', + '曾荫权' => ' zēng yìn quán', + '强的松' => ' qiáng dí sōng', + '面包片' => ' miàn bāo piān', + '照相机' => ' zhào xiàng jī', + '铅中毒' => ' qiān zhòng dú', + '中圈套' => ' zhòng quān tào', + '喜相逢' => ' xǐ xiàng féng', + '佛跳墙' => ' fó tiào qiáng', + '总长度' => ' zǒng cháng dù', + '黑旋风' => ' hēi xuàn fēng', + '纵切面' => ' zòng qiē miàn', + '相片儿' => ' xiàng piān er', + '干事长' => ' gàn shi zhǎng', + '漂白水' => ' piǎo bái shuǐ', + '重婚罪' => ' chóng hūn zuì', + '拉长脸' => ' lā cháng liǎn', + '校书郎' => ' jiào shū láng', + '沧浪亭' => ' cāng láng tíng', + '氧效应' => ' yǎng xiào yìng', + '冗长度' => ' rǒng cháng dù', + '三重县' => ' sān chóng xiàn', + '行情表' => ' háng qíng biǎo', + '娘家姓' => ' niáng jia xìng', + '沉甸甸' => ' chén diàn diàn', + '中都城' => ' zhōng dū chéng', + '绷子床' => ' bēng zi chuáng', + '审判长' => ' shěn pàn cháng', + '航行灯' => ' háng xíng dēng', + '狂想曲' => ' kuáng xiǎng qǔ', + '海监船' => ' hǎi jiàn chuán', + '长镜头' => ' cháng jìng tóu', + '生还者' => ' shēng huán zhě', + '颈椎病' => ' jǐng zhuī bìng', + '小公共' => ' xiǎo gōng gòng', + '清劲风' => ' qīng jìng fēng', + '钻井工' => ' zuàn jǐng gōng', + '黄克强' => ' huáng kè jiàng', + '黄长烨' => ' huáng cháng yè', + '吃空饷' => ' chī kòng xiǎng', + '还原糖' => ' huán yuán táng', + '照相版' => ' zhào xiàng bǎn', + '照相术' => ' zhào xiàng shù', + '定兴县' => ' dìng xīng xiàn', + '测量船' => ' cè liáng chuán', + '灭虫宁' => ' miè chóng nìng', + '车头相' => ' chē tóu xiàng', + '金刚钻' => ' jīn gāng zuàn', + '兴平市' => ' xīng píng shì', + '朴正熙' => ' piáo zhèng xī', + '轴对称' => ' zhóu duì chèn', + '相扑手' => ' xiàng pū shǒu', + '漂流瓶' => ' piāo liú píng', + '复兴乡' => ' fù xīng xiāng', + '晕血症' => ' yùn xuè zhèng', + '白相人' => ' bái xiàng rén', + '卷筒纸' => ' juàn tǒng zhǐ', + '假芫茜' => ' jiǎ yuán qiàn', + '万圣节' => ' wàn shèng jiē', + '种植者' => ' zhòng zhí zhě', + '调味料' => ' tiáo wèi liào', + '排行榜' => ' pái háng bǎng', + '粘土砖' => ' nián tǔ zhuān', + '水浒传' => ' shuǐ hǔ zhuàn', + '卷叶虫' => ' juàn yè chóng', + '红娘子' => ' hóng niáng zǐ', + '轧钢条' => ' zhá gāng tiáo', + '弹簧板' => ' tán huáng bǎn', + '酆都城' => ' fēng dū chéng', + '兴宁市' => ' xīng níng shì', + '林甸县' => ' lín diàn xiàn', + '电转儿' => ' diàn zhuàn ér', + '运动员' => ' yùn dòng yuán', + '拖累症' => ' tuō lěi zhèng', + '将进酒' => ' qiāng jìn jiǔ', + '创可贴' => ' chuāng kě tiē', + '上齿龈' => ' shàng chǐ yín', + '闹哄哄' => ' nào hōng hǒng', + '吹管乐' => ' chuī guǎn yuè', + '走为上' => ' zǒu wéi shàng', + '溜溜转' => ' liū liū zhuàn', + '重身子' => ' chóng shēn zi', + '超对称' => ' chāo duì chèn', + '子宫腔' => ' zǐ gōng qiāng', + '未命名' => ' wèi mìng míng', + '重覆性' => ' chóng fù xìng', + '数得上' => ' shǔ děi shàng', + '太行山' => ' tài háng shān', + '种牛痘' => ' zhòng niú dòu', + '星相图' => ' xīng xiàng tú', + '横切面' => ' héng qiē miàn', + '量尺寸' => ' liáng chǐ cùn', + '调节税' => ' tiáo jié shuì', + '调停者' => ' tiáo tíng zhě', + '伽南香' => ' qié nán xiāng', + '相应物' => ' xiāng yìng wù', + '荐头店' => ' jiàn tou diàn', + '娘子关' => ' niáng zǐ guān', + '调节表' => ' tiáo jié biǎo', + '切换键' => ' qiē huàn jiàn', + '小娘子' => ' xiǎo niáng zǐ', + '闹嚷嚷' => ' nào rāng rɑng', + '列女传' => ' liè nǚ zhuàn', + '放大率' => ' fàng dà shuài', + '看得中' => ' kàn de zhòng', + '打折票' => ' dǎ shé piào', + '反应力' => ' fǎn yìng lì', + '剪切力' => ' jiǎn qiē lì', + '工夫茶' => ' gōng fu chá', + '电子云' => ' diàn zǐ yún', + '胃粘膜' => ' wèi nián mó', + '小舅子' => ' xiǎo jiù zǐ', + '肠阻塞' => ' cháng zǔ sè', + '过得硬' => ' guò de yìng', + '剪头发' => ' jiǎn tóu fa', + '肋间肌' => ' lèi jiān jī', + '拗口令' => ' ào kǒu lìng', + '压倒性' => ' yā dǎo xìng', + '脊椎炎' => ' jǐ zhuī yán', + '甲壳素' => ' jiǎ qiào sù', + '眼见得' => ' yǎn jiàn de', + '弹球盘' => ' tán qíu pán', + '直贡呢' => ' zhí gòng ní', + '消闲儿' => ' xiāo xián r', + '耍骨头' => ' shuǎ gú tou', + '耍笔杆' => ' shuǎ bǐ gǎn', + '看花眼' => ' kān huā yǎn', + '不要脸' => ' bú yào liǎn', + '车前子' => ' chē qián zǐ', + '决明子' => ' jué míng zǐ', + '澎湖岛' => ' péng hú dǎo', + '甩负荷' => ' shuǎi fù hè', + '核门槛' => ' hé mén jiàn', + '畜产品' => ' xù chǎn pǐn', + '小石子' => ' xiǎo shí zǐ', + '顺手儿' => ' shùn shǒu r', + '留成儿' => ' líu chéng r', + '要不了' => ' yào bù liǎo', + '氧分子' => ' yǎng fēn zǐ', + '掺沙子' => ' chān shā zi', + '打麻将' => ' dǎ má jiàng', + '顶尖儿' => ' dǐng jiān r', + '佛手瓜' => ' fó shǒu guā', + '乐学者' => ' yuè xué zhě', + '罗刹国' => ' luó chà guó', + '番茄汁' => ' fān qié zhī', + '歪点子' => ' wāi diǎn zǐ', + '小叔子' => ' xiǎo shū zǐ', + '不倒翁' => ' bù dǎo wēng', + '更衣室' => ' gēng yī shì', + '逍遥子' => ' xiāo yáo zǐ', + '片儿会' => ' piān er huì', + '大龙湫' => ' dà lóng qiū', + '叫化子' => ' jiào huā zi', + '放得下' => ' fàng de xià', + '捅娄子' => ' tǒng lóu zǐ', + '看门人' => ' kān mén rén', + '太子港' => ' tài zǐ gǎng', + '淋溶土' => ' lìn róng tǔ', + '轧钢机' => ' zhá gāng jī', + '老爷岭' => ' lǎo ye lǐng', + '箭垛子' => ' jiàn duò zi', + '娘儿们' => ' niáng r men', + '冷处理' => ' lěng chǔ lǐ', + '音乐节' => ' yīn yuè jié', + '打晃儿' => ' dǎ huàng er', + '省劲儿' => ' shěng jìn r', + '高低杠' => ' gāo dī gàng', + '大还丹' => ' dà huán dān', + '副反应' => ' fù fǎn yìng', + '音乐会' => ' yīn yuè huì', + '不协调' => ' bù xié tiáo', + '反应物' => ' fǎn yìng wù', + '太子党' => ' tài zǐ dǎng', + '协调器' => ' xié tiáo qì', + '吃不了' => ' chī bù liǎo', + '音乐家' => ' yīn yuè jiā', + '博白县' => ' bó bái xiàn', + '佛山市' => ' fó shān shì', + '树行子' => ' shù hàng zi', + '数得着' => ' shǔ de zháo', + '重复法' => ' chóng fù fǎ', + '夫妻相' => ' fū qī xiàng', + '韦应物' => ' wéi yìng wù', + '贼忒忒' => ' zéi tuī tuī', + '漂流物' => ' piāo liú wù', + '半彪子' => ' bàn biāo zǐ', + '韛拐子' => ' bài guǎi zǐ', + '处理权' => ' chǔ lǐ quán', + '反应热' => ' fǎn yìng rè', + '知识界' => ' zhī shi jiè', + '差异性' => ' chā yì xìng', + '鬼点子' => ' guǐ diǎn zǐ', + '短打扮' => ' duǎn dǎ ban', + '切削机' => ' qiē xiāo jī', + '重离子' => ' zhòng lí zǐ', + '军乐队' => ' jūn yuè duì', + '一边倒' => ' yī biān dǎo', + '不划算' => ' bù huá suàn', + '软耳朵' => ' ruǎn ěr duo', + '惠更斯' => ' huì gēng sī', + '电子琴' => ' diàn zǐ qín', + '野颠茄' => ' yě diān qié', + '原子价' => ' yuán zǐ jià', + '电子书' => ' diàn zǐ shū', + '翻跟头' => ' fān gēn tou', + '翻跟斗' => ' fān gēn dǒu', + '刘少奇' => ' liú shào qí', + '打工仔' => ' dǎ gōng zǎi', + '刘涓子' => ' líu juān zǐ', + '番茄粉' => ' fān qié fěn', + '重活儿' => ' zhòng huó r', + '反应炉' => ' fǎn yìng lú', + '较真儿' => ' jiào zhēn r', + '草垫子' => ' cǎo diàn zǐ', + '音乐学' => ' yīn yuè xué', + '反应器' => ' fǎn yìng qì', + '柔佛州' => ' róu fó zhōu', + '淋球菌' => ' lìn qíu jūn', + '蜚蠊科' => ' fěi lián kē', + '石头人' => ' shí tou rén', + '磨工病' => ' mò gōng bìng', + '原子团' => ' yuán zǐ tuán', + '增压泵' => ' zēng yà bèng', + '朝鲜语' => ' cháo xiǎn yǔ', + '均方差' => ' jūn fāng chā', + '变速杆' => ' biàn sù gǎn', + '差旅费' => ' chāi lǚ fèi', + '参宿七' => ' shēn xìu qī', + '漂白液' => ' piǎo bái yè', + '阿房宫' => ' ē páng gōng', + '量子沫' => ' liàng zǐ mò', + '翻筋斗' => ' fān jīn dǒu', + '小太太' => ' xiǎo tài tai', + '子宫内' => ' zǐ gōng nèi', + '缝纫机' => ' féng rèn jī', + '伽罗华' => ' jiā luó huà', + '刽子手' => ' guì zǐ shǒu', + '露马脚' => ' lòu mǎ jiǎo', + '搬舌头' => ' bān shé tou', + '总书记' => ' zǒng shū ji', + '子宫瘤' => ' zǐ gōng liú', + '碳原子' => ' tàn yuán zǐ', + '露馅儿' => ' lòu xiàn ér', + '波带片' => ' bō dài piān', + '种植业' => ' zhòng zhí yè', + '行得通' => ' xíng de tōng', + '称得起' => ' chēng de qǐ', + '鹿谷乡' => ' lù gǔ xiāng', + '主属性' => ' zhǔ shǔ xìng', + '莲花落' => ' lián huā lào', + '跟得上' => ' gēn de shàng', + '曾孙女' => ' zēng sūn nǚ', + '蒙代尔' => ' mēng dài ěr', + '著作郎' => ' zhù zuò láng', + '闹乱子' => ' nào luàn zǐ', + '朝歌镇' => ' zhāo gē zhèn', + '氢原子' => ' qīng yuán zǐ', + '公共课' => ' gōng gòng kè', + '碰钉子' => ' pèng dìng zǐ', + '蒙嘉慧' => ' měng jiā huì', + '竹扫帚' => ' zhú sào zhou', + '电子眼' => ' diàn zǐ yǎn', + '行列式' => ' háng liè shì', + '乐清市' => ' yuè qīng shì', + '尖沙咀' => ' jiān shā zuǐ', + '一点点' => ' yì diǎn diǎn', + '炸薯条' => ' zhá shǔ tiáo', + '枪杆子' => ' qiāng gǎn zi', + '女舍监' => ' nǔ: shè jiān', + '朝鲜族' => ' cháo xiǎn zú', + '炸薯片' => ' zhá shǔ piàn', + '瞎忙活' => ' xiā máng huo', + '子宫环' => ' zǐ gōng huán', + '桌面儿' => ' zhuō miàn r', + '槐荫区' => ' huái yìn qū', + '小崽子' => ' xiǎo zǎi zǐ', + '复兴社' => ' fù xīng shè', + '已更新' => ' yǐ gēng xīn', + '翘拇指' => ' qiáo mǔ zhǐ', + '看不见' => ' kàn bú jiàn', + '调温器' => ' tiáo wēn qì', + '看不懂' => ' kàn bu dǒng', + '抹灰层' => ' mò huī céng', + '漏斗云' => ' lòu dǒu yún', + '疏勒县' => ' shū lè xiàn', + '倒霉蛋' => ' dǎo méi dàn', + '核反应' => ' hé fǎn yìng', + '金国汗' => ' jīn guó hán', + '鱼子酱' => ' yú zǐ jiàng', + '钟子期' => ' zhōng zǐ qī', + '调节阀' => ' tiáo jié fá', + '室内乐' => ' shì nèi yuè', + '多菲什' => ' duō fēi shí', + '铺盖卷' => ' pū gài juǎn', + '易卜生' => ' yì bǔ shēng', + '占卜者' => ' zhān bǔ zhě', + '何应钦' => ' hé yìng qīn', + '大娘子' => ' dà niáng zǐ', + '散文家' => ' sǎn wén jiā', + '升压器' => ' shēng yà qì', + '听不到' => ' tīng bu dào', + '真是的' => ' zhēn shi de', + '百分号' => ' bǎi fēn háo', + '爪哇岛' => ' zhǎo wā dǎo', + '好家伙' => ' hǎo jiā huo', + '坏分子' => ' huài fèn zǐ', + '粘结剂' => ' nián jié jì', + '供给者' => ' gōng jǐ zhě', + '鹿角菜' => ' lù jiǎo cài', + '咽峡炎' => ' yān xiá yán', + '金铃子' => ' jīn líng zǐ', + '银行业' => ' yín háng yè', + '功夫球' => ' gōng fu qíu', + '受委屈' => ' shòu wěi qu', + '不适当' => ' bù shì dàng', + '银行卡' => ' yín háng kǎ', + '氧乙炔' => ' yǎng yǐ quē', + '汽缸盖' => ' qì gāng gài', + '着边儿' => ' zháo biān r', + '一块肉' => ' yí kuài ròu', + '罄身儿' => ' qìng shēn r', + '调音器' => ' tiáo yīn qì', + '供给制' => ' gōng jǐ zhì', + '氟中毒' => ' fú zhòng dú', + '摆擂台' => ' bǎi lèi tái', + '解调器' => ' jiě tiáo qì', + '淮南子' => ' huái nán zǐ', + '耳光子' => ' ěr guāng zǐ', + '烂舌头' => ' làn shé tou', + '调谐器' => ' tiáo xié qì', + '猪仔包' => ' zhū zǎi bāo', + '笨家伙' => ' bèn jiā huo', + '数不着' => ' shǔ bù zháo', + '蒙古人' => ' měng gǔ rén', + '枪杆儿' => ' qiāng gǎn r', + '尥蹶子' => ' liào jué zǐ', + '善男子' => ' shàn nán zǐ', + '什刹海' => ' shí chà hǎi', + '校验器' => ' jiào yàn qì', + '蚌埠市' => ' bèng bù shì', + '露一手' => ' lòu yī shǒu', + '迎宾曲' => ' yíng bīn qǔ', + '工尺谱' => ' gōng chě pǔ', + '阴电子' => ' yīn diàn zǐ', + '原子束' => ' yuán zǐ shù', + '发现号' => ' fā xiàn háo', + '扎猛子' => ' zhā měng zǐ', + '发横财' => ' fā hèng cái', + '人缘儿' => ' rén yuán ér', + '原子弹' => ' yuán zǐ dàn', + '出疹子' => ' chū zhěn zǐ', + '切菜板' => ' qiē cài bǎn', + '抗倒伏' => ' kàng dǎo fú', + '扎线带' => ' zā xiàn dài', + '原子堆' => ' yuán zǐ duī', + '脚腕子' => ' jiǎo wàn zǐ', + '洞子货' => ' dòng zǐ huò', + '寻开心' => ' xín kāi xīn', + '旅行家' => ' lǚ háng jia', + '感应器' => ' gǎn yìng qì', + '采桑子' => ' cǎi sāng zǐ', + '蛋白石' => ' dàn bái shí', + '间奏曲' => ' jiàn zòu qǔ', + '闵行区' => ' mǐn háng qū', + '间隔号' => ' jiàn gé hào', + '兵油子' => ' bīng yóu zǐ', + '和面机' => ' huó miàn jī', + '亨格洛' => ' hēng gé luò', + '盘杠子' => ' pán gàng zi', + '梅干菜' => ' méi gān cài', + '分子筛' => ' fèn zǐ shāi', + '女神蛤' => ' nǔ: shén gé', + '两头儿' => ' liǎng tóu r', + '长距离' => ' cháng jù lí', + '蚰蜒草' => ' yóu dàn cǎo', + '嚼裹儿' => ' jiáo guǒ ér', + '干着急' => ' gān zháo jí', + '集电杆' => ' jí diàn gǎn', + '票贩子' => ' piào fàn zǐ', + '报纸夹' => ' bào zhǐ jiá', + '书卷气' => ' shū juàn qì', + '咽鼓管' => ' yān gǔ guǎn', + '粘膜炎' => ' nián mó yán', + '伤和气' => ' shāng hé qi', + '处理掉' => ' chǔ lǐ diào', + '陈子昂' => ' chén zǐ áng', + '动载荷' => ' dòng zài hè', + '差一点' => ' chà yì diǎn', + '原子时' => ' yuán zǐ shí', + '牛肉干' => ' niú ròu gān', + '童子军' => ' tóng zǐ jūn', + '室间隔' => ' shì jiàn gé', + '公因子' => ' gōng yīn zǐ', + '犬夜叉' => ' quǎn yè chà', + '积累性' => ' jī lěi xìng', + '牡丹亭' => ' mǔ dan tíng', + '原子论' => ' yuán zǐ lùn', + '磁测量' => ' cí cè liáng', + '先君子' => ' xiān jūn zǐ', + '人造草' => ' rén zào cǎo', + '面人儿' => ' miàn rén ér', + '镇得住' => ' zhèn de zhù', + '栽跟头' => ' zāi gēn tou', + '牡丹坊' => ' mǔ dan fāng', + '几丁质' => ' jī dīng zhì', + '切面机' => ' qiē miàn jī', + '不粘锅' => ' bù nián guō', + '发型师' => ' fà xíng shī', + '粘结力' => ' nián jié lì', + '小器作' => ' xiǎo qì zuō', + '华龙区' => ' huà lóng qū', + '切片机' => ' qiē piàn jī', + '高丽朝' => ' gāo lí cháo', + '天可汗' => ' tiān kè hán', + '高丽参' => ' gāo lí shēn', + '磨坊主' => ' mò fáng zhǔ', + '漂泊者' => ' piāo bó zhě', + '石榴汁' => ' shí liu zhī', + '自感应' => ' zì gǎn yìng', + '切断机' => ' qiē duàn jī', + '八行书' => ' bā háng shū', + '切分音' => ' qiē fēn yīn', + '人影儿' => ' rén yǐng ér', + '有时候' => ' yǒu shí hou', + '挑担子' => ' tiāo dàn zi', + '旁边儿' => ' páng biān r', + '翻斗车' => ' fān dǒu chē', + '什件儿' => ' shí jiàn ér', + '早知道' => ' zǎo zhī dao', + '切换阀' => ' qiē huàn fá', + '悲切切' => ' bēi qiè qiē', + '架秧子' => ' jià yāng zǐ', + '三班倒' => ' sān bān dǎo', + '仙方儿' => ' xiān fāng r', + '弦乐器' => ' xián yuè qì', + '绮想曲' => ' qǐ xiǎng qǔ', + '济宁市' => ' jǐ níng shì', + '磁感应' => ' cí gǎn yìng', + '经不住' => ' jīng bu zhù', + '窝囊废' => ' wō nāng fèi', + '三毛猫' => ' sān máo māo', + '穿衣服' => ' chuān yī fu', + '小人儿' => ' xiǎo rén er', + '热轧钢' => ' rè zhá gāng', + '烘笼儿' => ' hōng lóng r', + '小妹子' => ' xiǎo mèi zǐ', + '秒差距' => ' miǎo chā jù', + '孢子囊' => ' bāo zǐ náng', + '露头角' => ' lù tóu jiǎo', + '干燥室' => ' gān zào shì', + '杂和面' => ' zá huo miàn', + '农家子' => ' nóng jiā zǐ', + '看家狗' => ' kān jiā gǒu', + '傍角儿' => ' bàng jué ér', + '卷铺盖' => ' juǎn pū gài', + '处女航' => ' chǔ nǚ háng', + '江西腊' => ' jiāng xī là', + '校对机' => ' jiào duì jī', + '干燥窑' => ' gān zào yáo', + '硬骨头' => ' yìng gú tou', + '皮划艇' => ' pí huá tǐng', + '麻纤维' => ' mā xiān wéi', + '月收入' => ' yuè shōu rù', + '福州市' => ' fú zhōu shì', + '爪哇国' => ' zhǎo wā guó', + '口角炎' => ' kǒu jué yán', + '手腕子' => ' shǒu wàn zǐ', + '四方台' => ' sì fāng tái', + '应力值' => ' yìng lì zhí', + '福泉市' => ' fú quán shì', + '碾磙子' => ' niǎn gǔn zǐ', + '瓦岗军' => ' wǎ gāng jūn', + '不干净' => ' bù gān jìng', + '福清市' => ' fú qīng shì', + '找不到' => ' zhǎo bu dào', + '感应力' => ' gǎn yìng lì', + '茄红素' => ' qié hóng sù', + '吃馆子' => ' chī guǎn zǐ', + '五苓散' => ' wǔ líng sǎn', + '包干制' => ' bāo gān zhì', + '赤松子' => ' chì sōng zǐ', + '应景儿' => ' yìng jǐng r', + '说得着' => ' shuō de zháo', + '蔡侯纸' => ' cài hòu zhǐ', + '管扳子' => ' guǎn bān zǐ', + '私生子' => ' sī shēng zǐ', + '管乐器' => ' guǎn yuè qì', + '剪切机' => ' jiǎn qiē jī', + '宜兴市' => ' yí xīng shì', + '尉迟恭' => ' yù chí gōng', + '放包袱' => ' fàng bāo fú', + '有分寸' => ' yǒu fēn cun', + '软骨头' => ' ruǎn gú tou', + '敞开儿' => ' chǎng kāi r', + '好事者' => ' hào shì zhě', + '印子钱' => ' yìn zǐ qián', + '筋斗云' => ' jīn dǒu yún', + '冲压机' => ' chòng yā jī', + '斗六市' => ' dǒu lìu shì', + '漂流木' => ' piāo liú mù', + '半吊子' => ' bàn diào zǐ', + '催眠曲' => ' cuī mián qǔ', + '感应炉' => ' gǎn yìng lú', + '漂白剂' => ' piǎo bái jì', + '吃干饭' => ' chī gān fàn', + '打长途' => ' dǎ cháng tú', + '粘土岩' => ' nián tǔ yán', + '无为县' => ' wú wéi xiàn', + '油炸鬼' => ' yóu zhá guǐ', + '妃子笑' => ' fēi zǐ xiào', + '牛头刨' => ' niú tóu bào', + '刹那间' => ' chà nà jiān', + '水处理' => ' shuǐ chǔ lǐ', + '子程序' => ' zǐ chéng xù', + '哀伤地' => ' āi shāng de', + '解痉剂' => ' xiè jìng jì', + '烘干器' => ' hōng gān qì', + '散文诗' => ' sǎn wén shī', + '开拓者' => ' kāi tuò zhě', + '水磨沟' => ' shuǐ mò gōu', + '穆棱市' => ' mù líng shì', + '扫把星' => ' sào bǎ xīng', + '屎壳郎' => ' shǐ ke làng', + '戳得住' => ' chuō de zhù', + '间歇热' => ' jiàn xiē rè', + '战斗部' => ' zhàn dǒu bù', + '刮削器' => ' guā xiāo qì', + '章子怡' => ' zhāng zǐ yí', + '闹别扭' => ' nào biè niǔ', + '看门狗' => ' kān mén gǒu', + '干洗店' => ' gān xǐ diàn', + '角斗士' => ' jué dòu shì', + '煤斗车' => ' méi dǒu chē', + '子母钟' => ' zǐ mǔ zhōng', + '新兴区' => ' xīn xīng qū', + '奏鸣曲' => ' zòu míng qǔ', + '高挑儿' => ' gāo tiǎo ér', + '福鼎市' => ' fú dǐng shì', + '钢琴曲' => ' gāng qín qǔ', + '复兴门' => ' fù xīng mén', + '月氏人' => ' yuè zhī rén', + '三都县' => ' sān dū xiàn', + '电子学' => ' diàn zǐ xué', + '子宫炎' => ' zǐ gōng yán', + '背带裙' => ' bēi dài qún', + '顾颉刚' => ' gù xié gāng', + '雪佛龙' => ' xuě fó lóng', + '笑脸儿' => ' xiào liǎn r', + '舍身崖' => ' shè shēn yá', + '男人家' => ' nán rén jia', + '宅度假' => ' zhái dù jià', + '叫花子' => ' jiào huā zǐ', + '倒胃口' => ' dǎo wèi kǒu', + '水饺儿' => ' shuǐ jiǎo r', + '数来宝' => ' shǔ lái bǎo', + '祭司长' => ' jì sī cháng', + '数不清' => ' shǔ bù qīng', + '弹射出' => ' tán shè chū', + '区字框' => ' qū zì kuàng', + '长鼻目' => ' cháng bí mù', + '李娃传' => ' lǐ wá zhuàn', + '所得税' => ' suǒ de shuì', + '睡午觉' => ' shuì wǔ jiào', + '五虎将' => ' wǔ hǔ jiàng', + '看不中' => ' kàn bu zhōng', + '贱骨头' => ' jiàn gú tou', + '潭子乡' => ' tán zǐ xiāng', + '脚趾头' => ' jiǎo zhǐ tou', + '佛坪县' => ' fó píng xiàn', + '考试卷' => ' kǎo shì juàn', + '大黄鱼' => ' dài huáng yú', + '民乐县' => ' mín yuè xiàn', + '海参崴' => ' hǎi shēn wēi', + '使兴奋' => ' shǐ xīng fèn', + '姬松茸' => ' jī sōng róng', + '正角儿' => ' zhèng jué er', + '都察院' => ' dū chá yuàn', + '钻井机' => ' zuàn jǐng jī', + '卖关子' => ' mài guān zǐ', + '珲春市' => ' hún chūn shì', + '低转速' => ' dī zhuàn sù', + '槟榔屿' => ' bīng láng yǔ', + '柬埔寨' => ' jián pǔ zhài', + '老朋友' => ' lǎo péng you', + '柳毅传' => ' lǐu yì zhuàn', + '佛冈县' => ' fó gāng xiàn', + '贾平凹' => ' jiǎ píng wā', + '香附子' => ' xiāng fù zǐ', + '脑卒中' => ' nǎo cù zhòng', + '果子酱' => ' guǒ zǐ jiàng', + '肺栓塞' => ' fèi shuān sè', + '用不着' => ' yòng bù zháo', + '脚指甲' => ' jiǎo zhǐ jia', + '当回事' => ' dàng huí shì', + '用不了' => ' yòng bù liǎo', + '小瘪三' => ' xiǎo biē sān', + '影片儿' => ' yǐng piān er', + '小胡同' => ' xiǎo hú tòng', + '脚蹬子' => ' jiǎo dēng zǐ', + '家雀儿' => ' jiā qiǎo ér', + '应试者' => ' yìng shì zhě', + '干燥管' => ' gān zào guǎn', + '黑钻石' => ' hēi zuàn shí', + '中微子' => ' zhòng wēi zǐ', + '套口供' => ' tào kǒu gòng', + '亚平宁' => ' yà píng nìng', + '子洲县' => ' zǐ zhōu xiàn', + '不中意' => ' bù zhòng yì', + '二重奏' => ' èr chóng zòu', + '女傧相' => ' nǚ bīn xiàng', + '中庶子' => ' zhōng shù zǐ', + '著作权' => ' zhù zuò quán', + '崆峒区' => ' kōng tóng qū', + '老家伙' => ' lǎo jiā huo', + '管不着' => ' guǎn bu zháo', + '算不了' => ' suàn bù liǎo', + '桦甸市' => ' huà diàn shì', + '照片儿' => ' zhào piān er', + '硬着陆' => ' yìng zhuó lù', + '二重根' => ' èr chóng gēn', + '兴教寺' => ' xīng jiào sì', + '校准器' => ' jiào zhǔn qì', + '用得着' => ' yòng de zháo', + '澎湖县' => ' péng hú xiàn', + '中乐透' => ' zhòng lè tòu', + '行李卷' => ' xíng lǐ juàn', + '标识码' => ' biāo zhì mǎ', + '折杨柳' => ' shé yáng liǔ', + '失身分' => ' shī shēn fèn', + '总供给' => ' zǒng gōng jǐ', + '缝针迹' => ' féng zhēn jì', + '调频器' => ' tiáo pín qì', + '舟曲县' => ' zhōu qǔ xiàn', + '缝衣针' => ' féng yī zhēn', + '缅甸语' => ' miǎn diàn yǔ', + '接插件' => ' jiē chā jiàn', + '校准仪' => ' jiào zhǔn yí', + '飞行家' => ' fēi háng jia', + '片儿警' => ' piàn r jǐng', + '繁峙县' => ' fán shì xiàn', + '分权制' => ' fèn quán zhì', + '娃娃装' => ' wá wa zhuāng', + '钻孔机' => ' zuàn kǒng jī', + '兴庆区' => ' xīng qìng qū', + '标识符' => ' biāo zhì fú', + '变样儿' => ' biàn yàng r', + '缝合线' => ' féng hé xiàn', + '老来少' => ' lǎo lái shào', + '兴化市' => ' xīng huà shì', + '兴和县' => ' xīng hé xiàn', + '兴安盟' => ' xīng ān méng', + '兴安县' => ' xīng ān xiàn', + '狮子乡' => ' shī zǐ xiāng', + '大藏经' => ' dà zàng jīng', + '生闷气' => ' shēng mēn qì', + '资兴市' => ' zī xīng shì', + '进给量' => ' jìn jǐ liàng', + '咽喉痛' => ' yān hóu tòng', + '调节剂' => ' tiáo jié jì', + '甚高频' => ' shèn gāo pín', + '装糊涂' => ' zhuāng hú tu', + '甲壳类' => ' jiǎ qiào lèi', + '转载机' => ' zhuǎn zǎi jī', + '柏林墙' => ' bó lín qiáng', + '听得懂' => ' tīng de dǒng', + '听不见' => ' tīng bu jiàn', + '延长器' => ' yán cháng qì', + '鸭绿江' => ' yā lù jiāng', + '知识面' => ' zhī shi miàn', + '行为学' => ' xíng wéi xué', + '知识性' => ' zhī shi xìng', + '不相干' => ' bù xiāng gān', + '指定值' => ' zhǐ dìng zhí', + '天晓得' => ' tiān xiǎo de', + '佳丽酿' => ' jiā lì niáng', + '黄葛树' => ' huáng gě shù', + '绿营兵' => ' lù yíng bīng', + '总干事' => ' zǒng gàn shi', + '江都市' => ' jiāng dū shì', + '脑梗塞' => ' nǎo gěng sè', + '伊甸园' => ' yī diàn yuán', + '沂蒙山' => ' yí měng shān', + '宁都县' => ' níng dū xiàn', + '乱弹琴' => ' luàn tán qín', + '兴山区' => ' xīng shān qū', + '倒春寒' => ' dǎo chūn hán', + '黏着力' => ' nián zhuó lì', + '黏着语' => ' nián zhuó yǔ', + '前齿龈' => ' qián chǐ yín', + '沧浪客' => ' cāng láng kè', + '藏经阁' => ' zàng jīng gé', + '长草区' => ' cháng cǎo qū', + '酶反应' => ' méi fǎn yìng', + '炸油饼' => ' zhá yóu bǐng', + '相思曲' => ' xiāng sī qǔ', + '江干区' => ' jiāng gān qū', + '应变仪' => ' yìng biàn yí', + '洋漂族' => ' yáng piāo zú', + '横筋斗' => ' héng jīn dǒu', + '内切圆' => ' nèi qiē yuán', + '小王子' => ' xiǎo wáng zǐ', + '反应池' => ' fǎn yìng chí', + '横爬行' => ' héng pá xíng', + '华罗庚' => ' huà luó gēng', + '华兴会' => ' huá xīng huì', + '粘着力' => ' zhān zhuó lì', + '干鲜果' => ' gān xiān guǒ', + '粘胶液' => ' nián jiāo yè', + '李昌镐' => ' lǐ chāng hào', + '的确良' => ' dí què liáng', + '皇太子' => ' huáng tài zǐ', + '牡丹乡' => ' mǔ dan xiāng', + '黏涎子' => ' nián xián zǐ', + '四重奏' => ' sì chóng zòu', + '冠军杯' => ' guàn jūn bēi', + '龙门刨' => ' lóng mén bào', + '水仙子' => ' shuǐ xiān zǐ', + '晒骆驼' => ' shài luò tuo', + '都江堰' => ' dū jiāng yàn', + '怪叔叔' => ' guài shū shu', + '说不了' => ' shuō bù liǎo', + '说不着' => ' shuō bù zháo', + '相思子' => ' xiāng sī zǐ', + '曲周县' => ' qǔ zhōu xiàn', + '枪子儿' => ' qiāng zǐ er', + '量子态' => ' liàng zǐ tài', + '乡人子' => ' xiāng rén zǐ', + '采血针' => ' cǎi xiě zhēn', + '当间儿' => ' dāng jiàn ér', + '逮捕证' => ' dài bǔ zhèng', + '量子数' => ' liàng zǐ shù', + '都庞岭' => ' dū páng lǐng', + '钌铞儿' => ' liào diào ér', + '测量值' => ' cè liáng zhí', + '重估后' => ' chóng gū hòu', + '测量学' => ' cè liáng xué', + '大行星' => ' dà háng xīng', + '重孙子' => ' chóng sūn zi', + '重孙女' => ' chóng sūn nǚ', + '拉家常' => ' lá jiā cháng', + '剪应力' => ' jiǎn yìng lì', + '麻将牌' => ' má jiàng pái', + '干燥瓶' => ' gān zào píng', + '航行图' => ' háng xíng tú', + '空格键' => ' kòng gé jiàn', + '二人转' => ' èr rén zhuàn', + '行家话' => ' háng jiā huà', + '粘性力' => ' nián xìng lì', + '落埋怨' => ' lào mán yuàn', + '嫡长子' => ' dí zhǎng zǐ', + '色达县' => ' shǎi dá xiàn', + '爪尖儿' => ' zhuǎ jiān er', + '定冠词' => ' dìng guàn cí', + '种植土' => ' zhòng zhí tǔ', + '耳咽管' => ' ěr yān guǎn', + '脊神经' => ' jǐ shén jīng', + '降魔杵' => ' xiáng mó chǔ', + '童子尿' => ' tóng zǐ niào', + '漂流者' => ' piāo líu zhě', + '干扰源' => ' gān rǎo yuán', + '儿童片' => ' ér tóng piān', + '相国寺' => ' xiàng guó sì', + '烘干窑' => ' hōng gān yáo', + '中子流' => ' zhōng zǐ liú', + '烘干室' => ' hōng gān shì', + '子宫颈' => ' zǐ gōng jǐng', + '给事中' => ' jǐ shì zhōng', + '夜行性' => ' yè xíng xìng', + '着陆点' => ' zhuó lù diǎn', + '对称美' => ' duì chèn měi', + '康有为' => ' kāng yǒu wéi', + '实际上' => ' shí jì shang', + '李重茂' => ' lǐ chóng mào', + '反应慢' => ' fǎn yìng màn', + '李公朴' => ' lǐ gōng piáo', + '行道树' => ' háng dào shù', + '中子弹' => ' zhōng zǐ dàn', + '朴槿惠' => ' piáo jǐn huì', + '中子数' => ' zhōng zǐ shù', + '泰兴市' => ' tài xīng shì', + '效应器' => ' xiào yìng qì', + '小家伙' => ' xiǎo jiā huo', + '放暑假' => ' fàng shǔ jià', + '韩湘子' => ' hán xiāng zǐ', + '栽种机' => ' zāi zhòng jī', + '什邡市' => ' shí fāng shì', + '中大夫' => ' zhōng dài fū', + '脚指头' => ' jiǎo zhǐ tou', + '花朝节' => ' huā zhāo jié', + '纠结点' => ' jīu jiē diǎn', + '校验位' => ' jiào yàn wèi', + '大将军' => ' dà jiàng jūn', + '曹不兴' => ' cáo bù xīng', + '插杠子' => ' chā gàng zi', + '光杆儿' => ' guāng gǎn ér', + '这程子' => ' zhè chéng zǐ', + '京片子' => ' jīng piān zi', + '放寒假' => ' fàng hán jià', + '赵子龙' => ' zhào zǐ lóng', + '银行学' => ' yín háng xué', + '管乐队' => ' guǎn yuè duì', + '校勘学' => ' jiào kān xué', + '兴宁区' => ' xīng níng qū', + '东兴区' => ' dōng xīng qū', + '照片子' => ' zhào piān zi', + '蚌山区' => ' bèng shān qū', + '指甲刀' => ' zhǐ jia dāo', + '看守所' => ' kān shǒu suǒ', + '还魂丹' => ' huán hún dān', + '讲义气' => ' jiǎng yì qi', + '船夫曲' => ' chuán fū qǔ', + '抗干扰' => ' kàng gān rǎo', + '出大差' => ' chū dà chāi', + '串处理' => ' chuàn chǔ lǐ', + '姑娘家' => ' gū niáng jiā', + '串门子' => ' chuàn mén zǐ', + '上钩儿' => ' shàng gōu r', + '蒙古国' => ' měng gǔ guó', + '前处理' => ' qián chǔ lǐ', + '薄瑞光' => ' bó ruì guāng', + '张掖市' => ' zhāng yè shì', + '够得着' => ' gòu de zháo', + '西洋参' => ' xī yáng shēn', + '元好问' => ' yuán hào wèn', + '少奶奶' => ' shào nǎi nɑi', + '应急灯' => ' yìng jí dēng', + '干扰者' => ' gān rǎo zhě', + '燕麦粥' => ' yān mài zhōu', + '无将牌' => ' wú jiàng pái', + '被没收' => ' bèi mò shōu', + '钻石婚' => ' zuàn shí hūn', + '系列片' => ' xì liè piān', + '九华山' => ' jiǔ huà shān', + '广安门' => ' guǎng ān mén', + '未见得' => ' wèi jiàn de', + '文冠果' => ' wén guàn guǒ', + '萧子显' => ' xiāo zǐ xiǎn', + '原子尘' => ' yuán zǐ chén', + '新都桥' => ' xīn dū qiáo', + '电子狗' => ' diàn zǐ gǒu', + '氧原子' => ' yǎng yuán zǐ', + '爵士乐' => ' jué shì yuè', + '澄泥砚' => ' dèng ní yàn', + '螺桨毂' => ' luó jiǎng gū', + '回流泵' => ' huí liú bèng', + '少不了' => ' shào bù liǎo', + '间接费' => ' jiàn jiē fèi', + '测量器' => ' cè liáng qì', + '无穷尽' => ' wú qióng jìn', + '买东西' => ' mǎi dōng xi', + '军乐团' => ' jūn yuè tuán', + '蟹爪兰' => ' xiè zhuǎ lán', + '元真子' => ' yuán zhēn zǐ', + '广安市' => ' guǎng ān shì', + '张柏芝' => ' zhāng bó zhī', + '多水分' => ' duō shuǐ fèn', + '子弹箱' => ' zǐ dàn xiāng', + '浪漫曲' => ' làng màn qǔ', + '蒙松雨' => ' mēng sōng yǔ', + '虞应龙' => ' yú yìng lóng', + '榴霰弹' => ' liú xiàn dàn', + '泌阳县' => ' bì yáng xiàn', + '嫌麻烦' => ' xián má fan', + '乾安县' => ' qián ān xiàn', + '上边儿' => ' shàng bian r', + '够不着' => ' gòu bù zháo', + '子空间' => ' zǐ kōng jiān', + '五行阵' => ' wǔ háng zhèn', + '负效应' => ' fù xiào yìng', + '转弯子' => ' zhuàn wān zi', + '煎炸油' => ' jiān zhá yóu', + '调制器' => ' tiáo zhì qì', + '看上去' => ' kàn shang qu', + '鼓囊囊' => ' gǔ nāng nɑng', + '畹町市' => ' wǎn dīng shì', + '兴业县' => ' xīng yè xiàn', + '藏青色' => ' zàng qīng sè', + '蒙古包' => ' měng gǔ bāo', + '虎跑泉' => ' hǔ páo quán', + '女强人' => ' nǚ qiǎng rén', + '丽水市' => ' lí shuǐ shì', + '大长今' => ' dà cháng jīn', + '摽劲儿' => ' biào jìn er', + '五通神' => ' wǔ tòng shén', + '黄药子' => ' huáng yào zǐ', + '调皮鬼' => ' tiáo pí guǐ', + '短不了' => ' duǎn bù liǎo', + '灾难片' => ' zāi nàn piàn', + '滑行道' => ' huá háng dào', + '尹潽善' => ' yǐn pǔ shàn', + '背黑锅' => ' bēi hēi guō', + '供应科' => ' gōng yìng kē', + '变奏曲' => ' biàn zòu qǔ', + '枕头套' => ' zhěn tou tào', + '蔺相如' => ' lìn xiàng rú', + '羊痒疫' => ' yáng yǎng yì', + '美差事' => ' měi chāi shì', + '孔丛子' => ' kǒng cóng zǐ', + '林家翘' => ' lín jiā qiáo', + '科教片' => ' kē jiào piān', + '小插曲' => ' xiǎo chā qǔ', + '失忆症' => ' shī yì zhèng', + '血中毒' => ' xuè zhòng dú', + '真番郡' => ' zhēn pān jùn', + '好朋友' => ' hǎo péng you', + '烫头发' => ' tàng tóu fa', + '罗唝曲' => ' luó gòng qǔ', + '电子束' => ' diàn zǐ shù', + '三只手' => ' sān zhī shǒu', + '灾难性' => ' zāi nàn xìng', + '载驳船' => ' zǎi bó chuán', + '长安区' => ' cháng ān qū', + '宝生佛' => ' bǎo shēng fó', + '剪切板' => ' jiǎn qiē bǎn', + '弦乐队' => ' xián yuè duì', + '红曲' => ' hóng qǔ', + '半折' => ' bàn shé', + '仆从' => ' pú cóng', + '粘液' => ' nián yè', + '碾子' => ' niǎn zǐ', + '应付' => ' yìng fù', + '查下' => ' zhā xià', + '粗朴' => ' cū piáo', + '不省' => ' bù xǐng', + '悲咽' => ' bēi yān', + '答应' => ' dā yìng', + '灾煞' => ' zāi shà', + '伴乐' => ' bàn yuè', + '令子' => ' lìng zǐ', + '猛子' => ' měng zǐ', + '粘合' => ' nián hé', + '查勤' => ' zhā qín', + '粘糊' => ' nián hū', + '粘米' => ' nián mǐ', + '拗陷' => ' ào xiàn', + '蒙族' => ' měng zú', + '杆菌' => ' gǎn jūn', + '卜算' => ' bǔ suàn', + '福兴' => ' fú xīng', + '空儿' => ' kòng ér', + '干贝' => ' gān bèi', + '咽峡' => ' yān xiá', + '罗刹' => ' luó chà', + '难属' => ' nàn shǔ', + '精子' => ' jīng zǐ', + '答讪' => ' dā shàn', + '别别' => ' biè biè', + '流尽' => ' liú jìn', + '绵薄' => ' mián bó', + '糊弄' => ' hù nòng', + '罗罗' => ' luō luō', + '原曲' => ' yuán qǔ', + '案卷' => ' àn juàn', + '拶指' => ' zǎn zhǐ', + '梵刹' => ' fàn chà', + '干嚎' => ' gān háo', + '应和' => ' yìng hè', + '应力' => ' yìng lì', + '折头' => ' zhé tou', + '笔供' => ' bǐ gòng', + '嗔喝' => ' chēn hè', + '单于' => ' chán yú', + '缜发' => ' zhěn fà', + '应得' => ' yīng de', + '指头' => ' zhǐ tou', + '呼应' => ' hū yìng', + '嗜好' => ' shì hào', + '累累' => ' lěi lěi', + '人为' => ' rén wéi', + '懮虑' => ' yōu lù:', + '咽喉' => ' yān hóu', + '排车' => ' pǎi chē', + '梵呗' => ' fàn bài', + '比兴' => ' bǐ xīng', + '折煞' => ' shé shà', + '网吧' => ' wǎng bā', + '排难' => ' pái nàn', + '拾芥' => ' shí jiè', + '蝤蛑' => ' yóu móu', + '剥脱' => ' bāo tuō', + '哽咽' => ' gěng yè', + '累进' => ' lěi jìn', + '指甲' => ' zhǐ jia', + '折耗' => ' shé hào', + '绵子' => ' mián zǐ', + '干菜' => ' gān cài', + '抽咽' => ' chōu yè', + '攮子' => ' nǎng zǐ', + '宁子' => ' níng zǐ', + '一通' => ' yī tòng', + '拗劲' => ' niù jìn', + '抡才' => ' lún cái', + '慰藉' => ' wèi jiè', + '拖累' => ' tuō lěi', + '难友' => ' nàn yǒu', + '园子' => ' yuán zǐ', + '元子' => ' yuán zǐ', + '一天' => ' yì tiān', + '论处' => ' lùn chǔ', + '查察' => ' zhā chá', + '鱼漂' => ' yú piāo', + '咽炎' => ' yān yán', + '插接' => ' chā jiē', + '粘土' => ' nián tǔ', + '粘度' => ' nián dù', + '所为' => ' suǒ wéi', + '嘉荫' => ' jiā yìn', + '憎恶' => ' zēng wù', + '哪边' => ' nǎ bian', + '缝合' => ' féng hé', + '有分' => ' yǒu fèn', + '折箩' => ' zhē luó', + '神佛' => ' shén fó', + '闭卷' => ' bì juàn', + '懂得' => ' dǒng de', + '论著' => ' lùn zhù', + '查数' => ' zhā shù', + '应急' => ' yìng jí', + '干草' => ' gān cǎo', + '榛子' => ' zhēn zǐ', + '玉琢' => ' yù zhuó', + '磨削' => ' mó xiāo', + '查查' => ' zhā zhā', + '参错' => ' cēn cuò', + '策应' => ' cè yìng', + '论难' => ' lùn nàn', + '栽倒' => ' zāi dǎo', + '磨坊' => ' mò fáng', + '捻子' => ' niǎn zǐ', + '边子' => ' biān zǐ', + '吴兴' => ' wú xīng', + '亹亹' => ' wěi wěi', + '排叉' => ' pái chà', + '藏历' => ' zàng lì', + '团子' => ' tuán zǐ', + '啴啴' => ' tān tān', + '杠夫' => ' gàng fū', + '严查' => ' yán zhā', + '交恶' => ' jiāo wù', + '咽头' => ' yān tóu', + '采血' => ' cǎi xiě', + '漏尽' => ' lòu jìn', + '瀑流' => ' bào liú', + '杠子' => ' gàng zi', + '篇子' => ' piān zǐ', + '筛子' => ' shāi zǐ', + '石头' => ' shí tou', + '拢子' => ' lǒng zǐ', + '藏戏' => ' zàng xì', + '门斗' => ' mén dǒu', + '石作' => ' shí zuō', + '以还' => ' yǐ huán', + '藏医' => ' zàng yī', + '应物' => ' yìng wù', + '拐子' => ' guǎi zǐ', + '掺合' => ' chān he', + '大卷' => ' dà juàn', + '嚼子' => ' jiáo zǐ', + '喽喽' => ' lou lou', + '干旱' => ' gān hàn', + '半宿' => ' bàn xiǔ', + '臧否' => ' zāng pǐ', + '攒集' => ' cuán jí', + '扳倒' => ' bān dǎo', + '桑耶' => ' sāng yē', + '宝刹' => ' bǎo chà', + '碇泊' => ' dìng bó', + '繁峙' => ' fán shì', + '呵欠' => ' hē qiàn', + '呢绒' => ' ní róng', + '存查' => ' cún zhā', + '恶相' => ' è xiàng', + '愚氓' => ' yú méng', + '刺参' => ' cì shēn', + '干燥' => ' gān zào', + '晕高' => ' yùn gāo', + '云为' => ' yún wéi', + '摧折' => ' cuī shé', + '已甚' => ' yǐ shèn', + '乐感' => ' yuè gǎn', + '被难' => ' bèi nàn', + '乐池' => ' yuè chí', + '哗然' => ' huá rán', + '京都' => ' jīng dū', + '碑拓' => ' bēi tuò', + '镚子' => ' bèng zǐ', + '应募' => ' yìng mù', + '仿似' => ' fǎng sì', + '从子' => ' cóng zǐ', + '端的' => ' duān dì', + '龙子' => ' lóng zǐ', + '折本' => ' shé běn', + '骠骑' => ' piào qí', + '应举' => ' yìng jǔ', + '能子' => ' néng zǐ', + '缝补' => ' féng bǔ', + '攒聚' => ' cuán jù', + '硙硙' => ' wèi wèi', + '遗少' => ' yí shào', + '舍下' => ' shè xià', + '才分' => ' cái fèn', + '漂儿' => ' piāo ér', + '刺痒' => ' cì yáng', + '绷子' => ' bēng zǐ', + '磅礴' => ' páng bó', + '拌和' => ' bàn huò', + '嚼蜡' => ' jiáo là', + '拚贴' => ' pīn tiē', + '闷雷' => ' mēn léi', + '查报' => ' zhā bào', + '拖斗' => ' tuō dǒu', + '绫子' => ' líng zǐ', + '扒手' => ' pá shǒu', + '吸着' => ' xī zhuó', + '有处' => ' yǒu chǔ', + '噱头' => ' xué tou', + '才尽' => ' cái jìn', + '难胞' => ' nàn bāo', + '掺杂' => ' chān zá', + '产子' => ' chǎn zǐ', + '牛舍' => ' niú shè', + '五更' => ' wǔ gēng', + '一着' => ' yī zhāo', + '算子' => ' suàn zǐ', + '一觉' => ' yī jiào', + '不调' => ' bù tiáo', + '扎囊' => ' zā náng', + '桔槔' => ' jié gāo', + '干酪' => ' gān lào', + '不甚' => ' bù shèn', + '蒙医' => ' měng yī', + '乾安' => ' qián ān', + '营子' => ' yíng zǐ', + '干透' => ' gān tòu', + '干货' => ' gān huò', + '缨子' => ' yīng zǐ', + '拜倒' => ' bài dǎo', + '粘聚' => ' nián jù', + '答卷' => ' dá juàn', + '哪些' => ' něi xiē', + '粘膜' => ' nián mó', + '壅塞' => ' yōng sè', + '筋斗' => ' jīn dǒu', + '郢都' => ' yǐng dū', + '鱼卷' => ' yú juàn', + '一了' => ' yī liǎo', + '梗塞' => ' gěng sè', + '仔肩' => ' zī jiān', + '一卷' => ' yī juàn', + '楦子' => ' xuàn zǐ', + '攒盒' => ' cuán hé', + '龙脊' => ' lóng jǐ', + '灵子' => ' líng zǐ', + '吓倒' => ' xià dǎo', + '灶头' => ' zào tou', + '干杯' => ' gān bēi', + '绝倒' => ' jué dǎo', + '姘头' => ' pīn tou', + '粘附' => ' nián fù', + '披卷' => ' pī juàn', + '变子' => ' biàn zǐ', + '恫吓' => ' dòng hè', + '灶火' => ' zào huō', + '碑帖' => ' bēi tiè', + '手把' => ' shǒu bà', + '丧服' => ' sāng fú', + '管子' => ' guǎn zǐ', + '不更' => ' bù gēng', + '牤子' => ' māng zǐ', + '砚台' => ' yàn tāi', + '缏子' => ' biàn zǐ', + '条几' => ' tiáo jī', + '棒喝' => ' bàng hè', + '秧子' => ' yāng zǐ', + '卷发' => ' juǎn fà', + '丧仪' => ' sāng yí', + '齿数' => ' chǐ shǔ', + '干果' => ' gān guǒ', + '草舍' => ' cǎo shè', + '休假' => ' xiū jià', + '仙子' => ' xiān zǐ', + '干尸' => ' gān shī', + '嗳酸' => ' ǎi suān', + '纤夫' => ' qiàn fū', + '难舍' => ' nán shè', + '子道' => ' zǐ dào', + '干爹' => ' gān diē', + '矸子' => ' gān zǐ', + '丹佛' => ' dān fó', + '茶几' => ' chá jī', + '颉利' => ' xié lì', + '茬子' => ' chá zǐ', + '茅厕' => ' máo si', + '主仆' => ' zhǔ pú', + '芍陂' => ' què pí', + '乐谱' => ' yuè pǔ', + '茄子' => ' qié zi', + '乐府' => ' yuè fǔ', + '簪子' => ' zān zǐ', + '倒塌' => ' dǎo tā', + '芋头' => ' yù tou', + '花蛤' => ' huā gé', + '怎得' => ' zěn de', + '贼子' => ' zéi zǐ', + '心曲' => ' xīn qǔ', + '花都' => ' huā dū', + '心子' => ' xīn zǐ', + '倒地' => ' dǎo dì', + '石勒' => ' shí lè', + '信子' => ' xìn zǐ', + '芯子' => ' xìn zǐ', + '乐器' => ' yuè qì', + '碟子' => ' dié zǐ', + '丸子' => ' wán zǐ', + '干儿' => ' gān ér', + '倒伏' => ' dǎo fú', + '薄弱' => ' bó ruò', + '着哩' => ' zhe lǐ', + '幸得' => ' xìng de', + '落得' => ' luò de', + '茄科' => ' qié kē', + '乙炔' => ' yǐ quē', + '看似' => ' kàn sì', + '乜嘢' => ' niè yě', + '看押' => ' kān yā', + '看护' => ' kān hù', + '倒戈' => ' dǎo gē', + '花呢' => ' huā ní', + '荀子' => ' xún zǐ', + '砟子' => ' zhǎ zǐ', + '倒序' => ' dǎo xù', + '渣子' => ' zhā zǐ', + '破处' => ' pò chǔ', + '眸子' => ' móu zǐ', + '乐曲' => ' yuè qǔ', + '些子' => ' xiē zǐ', + '蟹子' => ' xiè zǐ', + '干涸' => ' gān hé', + '干洗' => ' gān xǐ', + '干枯' => ' gān kū', + '乐律' => ' yuè lǜ', + '仔仔' => ' zǐ zǎi', + '来得' => ' lái de', + '稚子' => ' zhì zǐ', + '的确' => ' dí què', + '秦都' => ' qín dū', + '私处' => ' sī chǔ', + '休得' => ' xiū de', + '寓舍' => ' yù shè', + '发卡' => ' fà qiǎ', + '落子' => ' lào zǐ', + '羞恶' => ' xiū wù', + '发帖' => ' fā tiě', + '吁求' => ' yù qiú', + '萎靡' => ' wěi mǐ', + '与人' => ' yú rén', + '菲姬' => ' fěi jī', + '发难' => ' fā nàn', + '白佛' => ' bái fó', + '于思' => ' yú sāi', + '落下' => ' là xià', + '白子' => ' bái zǐ', + '槟子' => ' bīn zǐ', + '乐迷' => ' yuè mí', + '尽力' => ' jìn lì', + '尽意' => ' jìn yì', + '倒毙' => ' dǎo bì', + '磨烦' => ' mò fán', + '的士' => ' dí shì', + '白鹄' => ' bái hú', + '范蠡' => ' fàn lǐ', + '瞽阇' => ' gǔ shé', + '云子' => ' yún zǐ', + '杂处' => ' zá chǔ', + '扎紧' => ' zā jǐn', + '苦难' => ' kǔ nàn', + '苦处' => ' kǔ chǔ', + '崽子' => ' zǎi zǐ', + '载负' => ' zǎi fù', + '乳晕' => ' rǔ yùn', + '砣子' => ' tuó zǐ', + '砌末' => ' qiè mò', + '花子' => ' huā zǐ', + '砂子' => ' shā zǐ', + '白发' => ' bái fà', + '新曲' => ' xīn qǔ', + '草耙' => ' cǎo pá', + '倒把' => ' dǎo bǎ', + '极为' => ' jí wéi', + '丫杈' => ' yā chà', + '丫头' => ' yā tou', + '萎叶' => ' wěi yè', + '熨帖' => ' yù tiē', + '与闻' => ' yù wén', + '菹醢' => ' zū hǎi', + '科斗' => ' kē dǒu', + '腕子' => ' wàn zǐ', + '雪子' => ' xuě zǐ', + '信佛' => ' xìn fó', + '逾分' => ' yú fèn', + '许处' => ' xǔ chǔ', + '釉子' => ' yòu zǐ', + '番禺' => ' pān yú', + '只此' => ' zhī cǐ', + '桠杈' => ' yā chà', + '疏勒' => ' shū lè', + '不菲' => ' bù fěi', + '与会' => ' yù huì', + '不要' => ' bú yào', + '余处' => ' yú chǔ', + '橱子' => ' chú zǐ', + '楔子' => ' xiē zǐ', + '积累' => ' jī lěi', + '果子' => ' guǒ zǐ', + '枚卜' => ' méi bǔ', + '例假' => ' lì jià', + '独处' => ' dú chǔ', + '脊背' => ' jǐ bèi', + '脊髓' => ' jǐ suǐ', + '压载' => ' yā zǎi', + '压蔓' => ' yā wàn', + '蕃庑' => ' fán wú', + '煞气' => ' shà qì', + '无处' => ' wú chǔ', + '油子' => ' yóu zǐ', + '痱子' => ' fèi zǐ', + '梅子' => ' méi zǐ', + '瘤子' => ' liú zǐ', + '瓤儿' => ' ráng r', + '窥伺' => ' kuī sì', + '窑子' => ' yáo zǐ', + '枣子' => ' zǎo zǐ', + '幽咽' => ' yōu yè', + '止咳' => ' zhǐ ké', + '瓜子' => ' guā zǐ', + '臊子' => ' sào zǐ', + '舀子' => ' yǎo zǐ', + '质的' => ' zhì dì', + '征辟' => ' zhǐ bì', + '不遂' => ' bù suí', + '直得' => ' zhí de', + '疟子' => ' yào zǐ', + '瘸子' => ' qué zǐ', + '瘊子' => ' hóu zǐ', + '癖好' => ' pǐ hào', + '哲子' => ' zhé zǐ', + '稻子' => ' dào zǐ', + '倒爷' => ' dǎo yé', + '折翼' => ' shé yì', + '干衣' => ' gān yī', + '瘫子' => ' tān zǐ', + '落泊' => ' luò bó', + '蔫儿' => ' niān r', + '下得' => ' xià de', + '茓子' => ' xué zǐ', + '腰子' => ' yāo zǐ', + '狍子' => ' páo zǐ', + '过得' => ' guò de', + '荤粥' => ' xūn yù', + '武侯' => ' wǔ hòu', + '犹子' => ' yóu zǐ', + '支子' => ' zhī zǐ', + '只字' => ' zhī zì', + '之子' => ' zhī zǐ', + '葛优' => ' gě yōu', + '竿子' => ' gān zǐ', + '脱发' => ' tuō fà', + '牢子' => ' láo zǐ', + '笆斗' => ' bā dǒu', + '使得' => ' shǐ de', + '下子' => ' xià zǐ', + '答白' => ' dā bái', + '夏子' => ' xià zǐ', + '历尽' => ' lì jìn', + '牙龈' => ' yá yín', + '历数' => ' lì shǔ', + '有得' => ' yǒu de', + '由得' => ' yóu de', + '踅子' => ' xué zǐ', + '不住' => ' bú zhù', + '学佛' => ' xué fó', + '不到' => ' bú dào', + '栀子' => ' zhī zǐ', + '作曲' => ' zuò qǔ', + '下都' => ' xià dū', + '柏悦' => ' bó yuè', + '热帖' => ' rè tiě', + '押头' => ' yā tou', + '死难' => ' sǐ nàn', + '脊肋' => ' jǐ lèi', + '脊柱' => ' jǐ zhù', + '为期' => ' wéi qī', + '乌什' => ' wū shí', + '值得' => ' zhí de', + '薄厚' => ' bó hòu', + '作死' => ' zuō sǐ', + '笔杆' => ' bǐ gǎn', + '脊索' => ' jǐ suǒ', + '无为' => ' wú wéi', + '柏林' => ' bó lín', + '无尽' => ' wú jìn', + '芜累' => ' wú lěi', + '西乐' => ' xī yuè', + '殷都' => ' yīn dū', + '无干' => ' wú gān', + '侉子' => ' kuǎ zǐ', + '主薄' => ' zhǔ bù', + '爆肚' => ' bào dǔ', + '李适' => ' lǐ kuò', + '模板' => ' mú bǎn', + '抹脸' => ' mā liǎn', + '粪耙' => ' fèn pá', + '子书' => ' zǐ shū', + '一般' => ' yì bān', + '薄酒' => ' bó jiǔ', + '胳臂' => ' gē bei', + '处罚' => ' chǔ fá', + '佛门' => ' fó mén', + '栽子' => ' zāi zǐ', + '处死' => ' chǔ sǐ', + '美的' => ' měi dí', + '比才' => ' bì cái', + '假日' => ' jià rì', + '佛龛' => ' fó kān', + '一干' => ' yī gān', + '混子' => ' hùn zǐ', + '为伍' => ' wéi wǔ', + '薄海' => ' bó hǎi', + '饮子' => ' yǐn zǐ', + '罗勒' => ' luó lè', + '围子' => ' wéi zǐ', + '处于' => ' chǔ yú', + '耗子' => ' hào zǐ', + '没药' => ' mò yào', + '泊车' => ' bó chē', + '绿林' => ' lù lín', + '没落' => ' mò luò', + '翅子' => ' chì zǐ', + '处女' => ' chǔ nǚ', + '木杆' => ' mù gǎn', + '浸没' => ' jìn mò', + '荫庇' => ' yìn bì', + '海子' => ' hǎi zǐ', + '栅子' => ' zhà zǐ', + '柞栎' => ' zuò lì', + '子惠' => ' zǐ huì', + '自好' => ' zì hào', + '组分' => ' zǔ fèn', + '什器' => ' shí qì', + '自处' => ' zì chǔ', + '一舍' => ' yī shè', + '问卜' => ' wèn bǔ', + '系留' => ' jì liú', + '滚子' => ' gǔn zǐ', + '子月' => ' zǐ yuè', + '假期' => ' jià qī', + '淹没' => ' yān mò', + '毛肚' => ' máo dǔ', + '美发' => ' měi fà', + '佛陀' => ' fó tuó', + '炸鱼' => ' zhá yú', + '炸鸡' => ' zhá jī', + '人儿' => ' rén ér', + '鹞子' => ' yào zǐ', + '微子' => ' wēi zǐ', + '泊位' => ' bó wèi', + '何干' => ' hé gān', + '卖解' => ' mài xiè', + '约塔' => ' yāo tǎ', + '泡子' => ' pāo zǐ', + '脂麻' => ' zhī ma', + '似雪' => ' sì xuě', + '胞子' => ' bāo zǐ', + '稠糊' => ' chóu hū', + '应答' => ' yìng dá', + '垸子' => ' yuàn zǐ', + '背着' => ' bēi zhe', + '出倒' => ' chū dǎo', + '乱子' => ' luàn zǐ', + '偏得' => ' piān de', + '一宿' => ' yī xiǔ', + '应敌' => ' yìng dí', + '绷瓷' => ' bèng cí', + '秦桧' => ' qín huì', + '任人' => ' rèn rén', + '干脆' => ' gān cuì', + '御苑' => ' yù yuàn', + '编曲' => ' biān qǔ', + '出塞' => ' chū sài', + '偏曲' => ' piān qǔ', + '干瘪' => ' gān biě', + '处士' => ' chǔ shì', + '伺候' => ' cì hou', + '一带' => ' yí dài', + '毛厕' => ' máo si', + '絮叨' => ' xù dáo', + '河叉' => ' hé chà', + '泡吧' => ' pào bā', + '续假' => ' xù jià', + '假子' => ' jiǎ zǐ', + '未卜' => ' wèi bǔ', + '苇子' => ' wěi zǐ', + '一化' => ' yī huā', + '蝎子' => ' xiē zǐ', + '佛家' => ' fó jiā', + '圩子' => ' wéi zǐ', + '佛学' => ' fó xué', + '沙拉' => ' shā là', + '作揖' => ' zuō yī', + '臆度' => ' yì duó', + '催吐' => ' cuī tù', + '藉以' => ' jiè yǐ', + '五斗' => ' wǔ dǒu', + '帷子' => ' wéi zǐ', + '子男' => ' zǐ nán', + '处理' => ' chǔ lǐ', + '人子' => ' rén zǐ', + '罹难' => ' lí nàn', + '一匙' => ' yī chí', + '缉查' => ' jī zhā', + '柚木' => ' yóu mù', + '沤肥' => ' òu féi', + '直发' => ' zhí fà', + '活佛' => ' huó fó', + '梭子' => ' suō zǐ', + '佛号' => ' fó hào', + '雁子' => ' yàn zǐ', + '子金' => ' zǐ jīn', + '烟子' => ' yān zǐ', + '檐子' => ' yán zǐ', + '蚂蚱' => ' mà zhà', + '一到' => ' yí dào', + '累计' => ' lěi jì', + '活似' => ' huó sì', + '兰若' => ' lán rě', + '眼子' => ' yǎn zǐ', + '尽速' => ' jìn sù', + '子衿' => ' zǐ jīn', + '肋骨' => ' lèi gǔ', + '一撮' => ' yī zuǒ', + '佚失' => ' yì shī', + '议处' => ' yì chǔ', + '文曲' => ' wén qǔ', + '蜗杆' => ' wō gǎn', + '介子' => ' jiè zǐ', + '淫靡' => ' yín mǐ', + '企划' => ' qǐ huá', + '子侄' => ' zǐ zhí', + '薄待' => ' bó dài', + '束发' => ' shù fà', + '累积' => ' lěi jī', + '俟候' => ' sì hòu', + '磨盘' => ' mò pán', + '般乐' => ' pán lè', + '乐户' => ' yuè hù', + '干涩' => ' gān sè', + '磨叨' => ' mò dāo', + '楼子' => ' lóu zǐ', + '乐舞' => ' yuè wǔ', + '干渴' => ' gān kě', + '乐歌' => ' yuè gē', + '华发' => ' huá fà', + '莞尔' => ' wǎn ěr', + '祝发' => ' zhù fà', + '舍弟' => ' shè dì', + '东阿' => ' dōng ē', + '杯葛' => ' bēi gě', + '李悝' => ' lǐ kuī', + '旧都' => ' jiù dū', + '尽责' => ' jìn zé', + '盘曲' => ' pán qǔ', + '倒替' => ' dǎo tì', + '磨难' => ' mó nàn', + '舍利' => ' shè lì', + '菌子' => ' jùn zi', + '磨碎' => ' mò suì', + '活泛' => ' huó fá', + '一撇' => ' yī piě', + '虾子' => ' xiā zǐ', + '子都' => ' zǐ dōu', + '淡泊' => ' dàn bó', + '仆人' => ' pú rén', + '蛤蚧' => ' gé jiè', + '淡薄' => ' dàn bó', + '蛙鞋' => ' wā xié', + '结壳' => ' jiē ké', + '结子' => ' jié zǐ', + '令狐' => ' líng hú', + '木头' => ' mù tou', + '查核' => ' zhā hé', + '蚶子' => ' hān zǐ', + '何处' => ' hé chǔ', + '什叶' => ' shí yè', + '亏得' => ' kuī de', + '虺蜥' => ' huǐ xī', + '译著' => ' yì zhù', + '佛书' => ' fó shū', + '查缉' => ' zhā jī', + '屋舍' => ' wū shè', + '老姥' => ' lǎo mǔ', + '染发' => ' rǎn fà', + '柔佛' => ' róu fó', + '分子' => ' fèn zǐ', + '结发' => ' jié fà', + '子畜' => ' zǐ chù', + '号子' => ' hào zǐ', + '以为' => ' yǐ wéi', + '济南' => ' jǐ nán', + '渡假' => ' dù jià', + '毛发' => ' máo fà', + '背逆' => ' bēi nì', + '假发' => ' jiǎ fà', + '背负' => ' bēi fù', + '毫发' => ' háo fà', + '结巴' => ' jiē bā', + '泰勒' => ' tài lè', + '背脊' => ' bèi jǐ', + '子口' => ' zǐ kǒu', + '闻得' => ' wén de', + '澹泊' => ' dàn bó', + '结扎' => ' jié zā', + '测度' => ' cè duó', + '给事' => ' jǐ shì', + '纳什' => ' nà shí', + '纽子' => ' niǔ zǐ', + '绦子' => ' tāo zǐ', + '摇把' => ' yáo bà', + '纰缪' => ' pī miù', + '饮马' => ' yìn mǎ', + '兀鹫' => ' wū jiù', + '肉脯' => ' ròu fǔ', + '湮没' => ' yān mò', + '沦没' => ' lún mò', + '稍息' => ' shào xī', + '好高' => ' hào gāo', + '没顶' => ' mò dǐng', + '干挠' => ' gān náo', + '犁靬' => ' lí jiān', + '缝衣' => ' féng yī', + '封子' => ' fēng zǐ', + '蜂子' => ' fēng zǐ', + '市担' => ' shì dàn', + '只眼' => ' zhī yǎn', + '大兴' => ' dà xīng', + '分出' => ' fèn chū', + '干支' => ' gān zhī', + '干扰' => ' gān rǎo', + '飞掠' => ' fēi lüě', + '间隔' => ' jiàn gé', + '球差' => ' qíu chā', + '吠舍' => ' fèi shè', + '九难' => ' jiǔ nàn', + '非为' => ' fēi wéi', + '只在' => ' zhī zài', + '干休' => ' gān xiū', + '帮子' => ' bāng zǐ', + '直指' => ' zhí zhǐ', + '致知' => ' zhì zhī', + '王子' => ' wáng zǐ', + '阏氏' => ' yān zhī', + '犂靬' => ' lí jiān', + '班什' => ' bān shí', + '发还' => ' fā huán', + '绢子' => ' juàn zǐ', + '由头' => ' yóu tou', + '新干' => ' xīn gān', + '发蒙' => ' fā mēng', + '卷叶' => ' juàn yè', + '甚于' => ' shèn yú', + '枯干' => ' kū gān', + '庚子' => ' gēng zǐ', + '神怡' => ' shēn yí', + '发兴' => ' fā xīng', + '只道' => ' zhī dào', + '净得' => ' jìng de', + '至为' => ' zhì wéi', + '干涉' => ' gān shè', + '究诘' => ' jiū jié', + '分划' => ' fēn huá', + '斤斗' => ' jīn dǒu', + '猵狙' => ' piàn jū', + '分值' => ' fèn zhí', + '猜度' => ' cāi duó', + '锋镝' => ' fēng dí', + '分处' => ' fēn chǔ', + '狗血' => ' gǒu xiě', + '铺首' => ' pū shǒu', + '弥缝' => ' mí féng', + '甚么' => ' shèn me', + '弹纠' => ' tán jīu', + '散人' => ' sǎn rén', + '磈磊' => ' kuǐ lěi', + '般游' => ' pán yóu', + '塞外' => ' sài wài', + '形似' => ' xíng sì', + '檃栝' => ' yǐn kuò', + '窥度' => ' kuī duó', + '定都' => ' dìng dū', + '弹球' => ' tán qíu', + '档子' => ' dàng zǐ', + '舟曲' => ' zhōu qǔ', + '吊子' => ' diào zǐ', + '定子' => ' dìng zǐ', + '顶子' => ' dǐng zǐ', + '款曲' => ' kuǎn qǔ', + '丁子' => ' dīng zǐ', + '锭子' => ' dìng zǐ', + '弹花' => ' tán huā', + '三倒' => ' sān dǎo', + '略为' => ' lüè wéi', + '坑子' => ' kēng zǐ', + '别扭' => ' biè niǔ', + '当机' => ' dàng jī', + '三舍' => ' sān shè', + '弹琴' => ' tán qín', + '不兴' => ' bù xīng', + '仓卒' => ' cāng cù', + '番茄' => ' fān qié', + '兴替' => ' xīng tì', + '会稽' => ' kuài jī', + '散话' => ' sǎn huà', + '招子' => ' zhāo zǐ', + '卜占' => ' bǔ zhān', + '弹斥' => ' tán chì', + '垫子' => ' diàn zǐ', + '坠子' => ' zhuì zǐ', + '弹指' => ' tán zhǐ', + '罩子' => ' zhào zǐ', + '白术' => ' bái zhú', + '弹射' => ' tán shè', + '斗车' => ' dǒu chē', + '卜宅' => ' bǔ zhái', + '弹奏' => ' tán zòu', + '色子' => ' shǎi zǐ', + '折到' => ' shé dào', + '斗酒' => ' dǒu jiǔ', + '弹回' => ' tán huí', + '折过' => ' zhē guò', + '洞子' => ' dòng zǐ', + '发丧' => ' fā sāng', + '申曲' => ' shēn qǔ', + '不当' => ' bù dàng', + '断喝' => ' duàn hè', + '棱子' => ' léng zǐ', + '劲旅' => ' jìng lǚ', + '倒回' => ' dǎo huí', + '圆子' => ' yuán zǐ', + '看家' => ' kān jiā', + '镇咳' => ' zhèn ké', + '得兼' => ' de jiān', + '坊子' => ' fāng zǐ', + '眄睨' => ' miàn nì', + '得令' => ' de lìng', + '旋木' => ' xuàn mù', + '均匀' => ' jūn yún', + '累月' => ' lěi yuè', + '冷子' => ' lěng zǐ', + '之至' => ' zhī zhì', + '占课' => ' zhān kè', + '得神' => ' de shén', + '主角' => ' zhǔ jué', + '得算' => ' de suàn', + '擂主' => ' lèi zhǔ', + '链子' => ' liàn zǐ', + '楞子' => ' léng zǐ', + '必应' => ' bì yìng', + '倒败' => ' dǎo bài', + '燎祭' => ' liào jì', + '铲子' => ' chǎn zǐ', + '待查' => ' dài zhā', + '脸子' => ' liǎn zǐ', + '倒还' => ' dǎo hái', + '乐坛' => ' yuè tán', + '倒好' => ' dǎo hǎo', + '当户' => ' dàng hù', + '劲拔' => ' jìng bá', + '错处' => ' cuò chǔ', + '瘅疟' => ' dàn nüè', + '了悟' => ' liǎo wù', + '则甚' => ' zé shèn', + '锁匙' => ' suǒ chí', + '槵子' => ' huàn zǐ', + '了如' => ' liǎo rú', + '干哕' => ' gān yuě', + '当得' => ' dāng de', + '锁钥' => ' suǒ yuè', + '四更' => ' sì gēng', + '待会' => ' dāi huì', + '代为' => ' dài wéi', + '丹墀' => ' dan chi', + '单处' => ' dān chǔ', + '劲敌' => ' jìng dí', + '恐吓' => ' kǒng hè', + '择席' => ' zhái xí', + '立卷' => ' lì juàn', + '款子' => ' kuǎn zǐ', + '枋子' => ' fāng zǐ', + '卷第' => ' juàn dì', + '茅舍' => ' máo shè', + '几丁' => ' jī dīng', + '卷末' => ' juàn mò', + '卷次' => ' juàn cì', + '肢势' => ' zhī shì', + '甚巨' => ' shèn jù', + '杏子' => ' xìng zǐ', + '居丧' => ' jū sāng', + '瓦隆' => ' wǎ lōng', + '大宛' => ' dà yuān', + '茶匙' => ' chá chí', + '方子' => ' fāng zǐ', + '勒庞' => ' lè páng', + '智识' => ' zhì shí', + '璧还' => ' bì huán', + '直切' => ' zhí qiē', + '几谏' => ' jī jiàn', + '军乐' => ' jūn yuè', + '神子' => ' shén zǐ', + '隽秀' => ' jùn xiù', + '留难' => ' liú nàn', + '厦门' => ' xià mén', + '旋子' => ' xuán zǐ', + '甚而' => ' shèn ér', + '刀切' => ' dāo qiē', + '地壳' => ' dì qiào', + '彻查' => ' chè zhā', + '曾思' => ' zēng sī', + '德兴' => ' dé xīng', + '倒社' => ' dǎo shè', + '乐师' => ' yuè shī', + '看头' => ' kàn tou', + '倒着' => ' dǎo zhe', + '旦角' => ' dàn jué', + '眼晕' => ' yǎn yùn', + '挓挲' => ' zhā shā', + '勘查' => ' kān zhā', + '勒令' => ' lè lìng', + '的当' => ' dí dàng', + '眯缝' => ' mī féng', + '勃兴' => ' bó xīng', + '轧车' => ' zhá chē', + '佛灯' => ' fó dēng', + '彷似' => ' fǎng sì', + '困处' => ' kùn chǔ', + '铁杆' => ' tiě gǎn', + '甚钜' => ' shèn jù', + '翻查' => ' fān zhā', + '翻斗' => ' fān dǒu', + '断发' => ' duàn fà', + '锻模' => ' duàn mú', + '倒翻' => ' dǎo fān', + '顺子' => ' shùn zǐ', + '着衣' => ' zhuó yī', + '笋干' => ' sǔn gān', + '倒霉' => ' dǎo méi', + '所处' => ' suǒ chǔ', + '倒运' => ' dǎo yùn', + '消得' => ' xiāo de', + '曲松' => ' qǔ sōng', + '遭难' => ' zāo nàn', + '落藉' => ' luò jiè', + '落难' => ' luò nàn', + '倒班' => ' dǎo bān', + '曲阳' => ' qǔ yáng', + '落魄' => ' luò tuò', + '著作' => ' zhù zuò', + '阳曲' => ' yáng qǔ', + '说得' => ' shuō de', + '着力' => ' zhuó lì', + '着地' => ' zháo dì', + '刷子' => ' shuā zǐ', + '着墨' => ' zhuó mò', + '那些' => ' nèi xiē', + '着急' => ' zháo jí', + '本处' => ' běn chǔ', + '造血' => ' zào xiě', + '刨刀' => ' bào dāo', + '挼搓' => ' ruó cuō', + '着棋' => ' zhuó qí', + '司钻' => ' sī zuàn', + '若为' => ' ruò wéi', + '曲水' => ' qǔ shuǐ', + '过过' => ' guò guo', + '判处' => ' pàn chǔ', + '松子' => ' sōng zǐ', + '宋子' => ' sòng zǐ', + '过分' => ' guò fèn', + '散碎' => ' sǎn suì', + '素朴' => ' sù piáo', + '逢俉' => ' féng wú', + '着法' => ' zhāo fǎ', + '倒蛋' => ' dǎo dàn', + '列为' => ' liè wéi', + '著书' => ' zhù shū', + '倒卖' => ' dǎo mài', + '着笔' => ' zhuó bǐ', + '算得' => ' suàn de', + '逼供' => ' bī gòng', + '着色' => ' zhuó sè', + '蒜子' => ' suàn zǐ', + '曲调' => ' qǔ diào', + '似曾' => ' sì céng', + '死当' => ' sǐ dàng', + '遛弯' => ' liù wān', + '苗子' => ' miáo zǐ', + '苦参' => ' kǔ shēn', + '兴复' => ' xīng fù', + '煞有' => ' shà yǒu', + '半载' => ' bàn zǎi', + '唼喋' => ' shà zhá', + '杏脯' => ' xìng fǔ', + '少艾' => ' shào ài', + '关子' => ' guān zǐ', + '兴福' => ' xīng fú', + '若干' => ' ruò gān', + '倒反' => ' dǎo fǎn', + '杀掠' => ' shā lüě', + '衫子' => ' shān zǐ', + '苫布' => ' shàn bù', + '兴波' => ' xīng bō', + '兴发' => ' xīng fā', + '倒休' => ' dǎo xiū', + '行都' => ' xíng dū', + '行卜' => ' xíng bǔ', + '芟荑' => ' shān yí', + '芥蓝' => ' gài lán', + '阿城' => ' a chéng', + '山曲' => ' shān qǔ', + '芥兰' => ' gài lán', + '杉篙' => ' shā gāo', + '刨齿' => ' bào chǐ', + '倒茬' => ' dǎo chá', + '隆子' => ' lóng zǐ', + '丝杠' => ' sī gàng', + '还击' => ' huán jī', + '寺观' => ' sì guàn', + '四行' => ' sì háng', + '还席' => ' huán xí', + '着意' => ' zhuó yì', + '还俗' => ' huán sú', + '邀约' => ' yāo yuē', + '倒台' => ' dǎo tái', + '散尽' => ' sàn jìn', + '兴革' => ' xīng gé', + '迁都' => ' qiān dū', + '四通' => ' sì tòng', + '隔行' => ' gé háng', + '博兴' => ' bó xīng', + '倒下' => ' dǎo xià', + '还礼' => ' huán lǐ', + '兴德' => ' xīng dé', + '著述' => ' zhù shù', + '铺面' => ' pū miàn', + '阑干' => ' lán gān', + '倒数' => ' dào shǔ', + '肆应' => ' sì yìng', + '曲周' => ' qǔ zhōu', + '外头' => ' wài tou', + '划过' => ' huá guò', + '真子' => ' zhēn zǐ', + '振子' => ' zhèn zǐ', + '砧子' => ' zhēn zǐ', + '可曾' => ' kě zēng', + '课卷' => ' kè juàn', + '一哄' => ' yí hòng', + '横死' => ' hèng sǐ', + '罗嗦' => ' luō suo', + '塞北' => ' sài běi', + '削皮' => ' xiāo pí', + '菸斗' => ' yān dǒu', + '瘦子' => ' shòu zǐ', + '划痕' => ' huá hén', + '舌头' => ' shé tou', + '萎缩' => ' wěi suō', + '划切' => ' huá qiè', + '剿袭' => ' chāo xí', + '倒灶' => ' dǎo zào', + '萎蕤' => ' wěi ruí', + '晕倒' => ' yūn dǎo', + '晕染' => ' yùn rǎn', + '晕池' => ' yùn chí', + '俞穴' => ' shù xué', + '贞子' => ' zhēn zǐ', + '真似' => ' zhēn sì', + '劈柴' => ' pǐ chái', + '弹出' => ' tán chū', + '当夜' => ' dàng yè', + '当铺' => ' dàng pù', + '看门' => ' kān mén', + '欺哄' => ' qī hǒng', + '剀切' => ' kǎi qiē', + '不着' => ' bù zháo', + '建都' => ' jiàn dū', + '多难' => ' duō nàn', + '枝杈' => ' zhī chà', + '不见' => ' bú jiàn', + '都统' => ' dū tǒng', + '附着' => ' fù zhuó', + '疹子' => ' zhěn zǐ', + '斗室' => ' dǒu shì', + '折实' => ' shé shí', + '舍友' => ' shè yǒu', + '散诞' => ' sǎn dàn', + '占卜' => ' zhān bǔ', + '折损' => ' shé sǔn', + '瘪三' => ' biē sān', + '折折' => ' shé shé', + '丙子' => ' bǐng zǐ', + '靠头' => ' kào tou', + '晕血' => ' yùn xuè', + '晕车' => ' yùn chē', + '都灵' => ' dū líng', + '锄头' => ' chú tou', + '劲卒' => ' jìng zú', + '笑呀' => ' xiào yā', + '劫难' => ' jié nàn', + '倒是' => ' dǎo shì', + '菌盖' => ' jùn gài', + '倒楣' => ' dǎo méi', + '小曲' => ' xiǎo qǔ', + '铺平' => ' pū píng', + '华佗' => ' huà tuó', + '铺垫' => ' pū diàn', + '销子' => ' xiāo zǐ', + '时为' => ' shí wéi', + '明子' => ' míng zǐ', + '华为' => ' huá wéi', + '华视' => ' huà shì', + '倒槽' => ' dǎo cáo', + '收得' => ' shōu de', + '华陀' => ' huà tuó', + '钎子' => ' qiān zǐ', + '昏倒' => ' hūn dǎo', + '钉耙' => ' dīng pá', + '铺展' => ' pū zhǎn', + '铺陈' => ' pū chén', + '铺炕' => ' pū kàng', + '施为' => ' shī wéi', + '萹蓄' => ' biān xù', + '配载' => ' pèi zǎi', + '暑假' => ' shǔ jià', + '小纪' => ' xiǎo jǐ', + '里弄' => ' lǐ lòng', + '落价' => ' lào jià', + '酸曲' => ' suān qǔ', + '酷肖' => ' kù xiāo', + '刹时' => ' chà shí', + '酬和' => ' chóu hè', + '暴晒' => ' pù shài', + '落拓' => ' luò tuò', + '配角' => ' pèi jué', + '石拓' => ' shí tuò', + '邻舍' => ' lín shè', + '朴硝' => ' pò xiāo', + '晓得' => ' xiǎo de', + '散职' => ' sǎn zhí', + '树杈' => ' shù chà', + '散粉' => ' sǎn fěn', + '星子' => ' xīng zǐ', + '莎草' => ' suō cǎo', + '试帖' => ' shì tiě', + '铺衬' => ' pū chèn', + '菌伞' => ' jùn sǎn', + '倒飞' => ' dǎo fēi', + '曾父' => ' zēng fù', + '懒散' => ' lǎn sǎn', + '周内' => ' zhōu nà', + '干咳' => ' gān hāi', + '佛堂' => ' fó táng', + '爪机' => ' zhuǎ jī', + '下载' => ' xià zǎi', + '爪子' => ' zhuǎ zǐ', + '爪哇' => ' zhǎo wā', + '佛说' => ' fó shuō', + '尽好' => ' jìn hǎo', + '差迟' => ' chā chí', + '爪儿' => ' zhuǎ ér', + '夫差' => ' fū chāi', + '少爷' => ' shào yé', + '尽瘁' => ' jìn cuì', + '烫发' => ' tàng fà', + '差错' => ' chā cuò', + '肘子' => ' zhǒu zǐ', + '尽美' => ' jìn měi', + '父丧' => ' fù sāng', + '牟平' => ' mù píng', + '天呀' => ' tiān yā', + '天哪' => ' tiān na', + '天子' => ' tiān zǐ', + '毋宁' => ' wú nìng', + '隔扇' => ' gé shān', + '牛只' => ' níu zhī', + '间色' => ' jiàn sè', + '毡子' => ' zhān zǐ', + '剪发' => ' jiǎn fà', + '供案' => ' gòng àn', + '尤为' => ' yóu wéi', + '供佛' => ' gòng fó', + '好学' => ' hào xué', + '一样' => ' yí yàng', + '更移' => ' gēng yí', + '高知' => ' gāo zhì', + '跟斗' => ' gēn dǒu', + '为主' => ' wéi zhǔ', + '给水' => ' jǐ shuǐ', + '炸糕' => ' zhá gāo', + '葛洪' => ' gě hóng', + '间杂' => ' jiàn zá', + '炮制' => ' páo zhì', + '屏息' => ' bǐng xī', + '间日' => ' jiàn rì', + '炮烙' => ' páo luò', + '灾难' => ' zāi nàn', + '女红' => ' nǚ gōng', + '湾仔' => ' wān zǎi', + '济宁' => ' jǐ níng', + '牛饮' => ' niú yìn', + '镯子' => ' zhuó zǐ', + '哽塞' => ' gěng sè', + '揭帖' => ' jiē tiě', + '煞是' => ' shà shì', + '煞白' => ' shà bái', + '朱干' => ' zhū gān', + '死劲' => ' sǐ jìng', + '复还' => ' fù huán', + '燕国' => ' yān guó', + '殆尽' => ' dài jìn', + '熨斗' => ' yùn dǒu', + '轧空' => ' gá kōng', + '差池' => ' chā chí', + '差役' => ' chāi yì', + '无间' => ' wú jiàn', + '差别' => ' chā bié', + '殉难' => ' xùn nàn', + '照得' => ' zhào de', + '诘难' => ' jié nán', + '蠡县' => ' lǐ xiàn', + '特调' => ' tè tiáo', + '少府' => ' shào fǔ', + '家什' => ' jiā shí', + '海蜇' => ' hǎi zhé', + '钻机' => ' zuàn jī', + '害臊' => ' hài sào', + '舟子' => ' zhōu zǐ', + '无爪' => ' wú zhuǎ', + '佛手' => ' fó shǒu', + '燕麦' => ' yān mài', + '不了' => ' bù liǎo', + '牛仔' => ' niú zǎi', + '尽然' => ' jìn rán', + '牙行' => ' yá háng', + '牙冠' => ' yá guàn', + '片子' => ' piān zi', + '片儿' => ' piān er', + '尽人' => ' jìn rén', + '干裂' => ' gān liè', + '轴子' => ' zhóu zǐ', + '差价' => ' chā jià', + '干花' => ' gān huā', + '烟斗' => ' yān dǒu', + '叫子' => ' jiào zǐ', + '酵子' => ' jiào zǐ', + '交子' => ' jiāo zǐ', + '骄子' => ' jiāo zǐ', + '段子' => ' duàn zǐ', + '娇子' => ' jiāo zǐ', + '干犯' => ' gān fàn', + '干粪' => ' gān fèn', + '无着' => ' wú zhuó', + '干耗' => ' gān hào', + '更易' => ' gēng yì', + '澎湖' => ' péng hú', + '浮漂' => ' fú piāo', + '打烊' => ' dǎ yàng', + '抹角' => ' mò jiǎo', + '童子' => ' tóng zǐ', + '游散' => ' yóu sǎn', + '抹面' => ' mò miàn', + '竭尽' => ' jié jìn', + '二更' => ' èr gēng', + '端子' => ' duān zǐ', + '投奔' => ' tóu bèn', + '脖颈' => ' bó gěng', + '空地' => ' kòng dì', + '打更' => ' dǎ gēng', + '隐处' => ' yǐn chǔ', + '干馏' => ' gān liú', + '晨曲' => ' chén qǔ', + '什锦' => ' shí jǐn', + '篷子' => ' péng zǐ', + '挑起' => ' tiǎo qǐ', + '空子' => ' kòng zi', + '喝令' => ' hè lìng', + '栟榈' => ' bīng lǘ', + '结实' => ' jiē shi', + '配乐' => ' pèi yuè', + '空隙' => ' kòng xì', + '事假' => ' shì jià', + '问倒' => ' wèn dǎo', + '埂子' => ' gěng zǐ', + '打哄' => ' dǎ hǒng', + '没乱' => ' mò luàn', + '没什' => ' méi shí', + '戳子' => ' chuō zǐ', + '丧礼' => ' sāng lǐ', + '义兴' => ' yì xīng', + '吁请' => ' yù qǐng', + '干号' => ' gān háo', + '绿营' => ' lù yíng', + '抖搂' => ' dǒu lōu', + '干饭' => ' gān fàn', + '缪缪' => ' miù miù', + '挎斗' => ' kuà dǒu', + '兵子' => ' bīng zǐ', + '咧咧' => ' liē liē', + '柄子' => ' bǐng zǐ', + '了局' => ' liǎo jú', + '空余' => ' kòng yú', + '啜泣' => ' chuò qì', + '窝囊' => ' wō nāng', + '批卷' => ' pī juàn', + '剥除' => ' bāo chú', + '阴处' => ' yīn chǔ', + '名子' => ' míng zǐ', + '扯臊' => ' chě sào', + '挑拨' => ' tiǎo bō', + '尿泡' => ' suī pào', + '巡查' => ' xún zhā', + '膏油' => ' gào yóu', + '毒爪' => ' dú zhuǎ', + '奔头' => ' bèn tóu', + '焙干' => ' bèi gān', + '干亲' => ' gān qīn', + '奇羡' => ' jī xiàn', + '校核' => ' jiào hé', + '干人' => ' gān rén', + '干肉' => ' gān ròu', + '刚子' => ' gāng zǐ', + '为难' => ' wéi nán', + '皋陶' => ' gāo yáo', + '奏帖' => ' zòu tiě', + '奏乐' => ' zòu yuè', + '为着' => ' wéi zhe', + '一点' => ' yì diǎn', + '为止' => ' wéi zhǐ', + '干薪' => ' gān xīn', + '母丧' => ' mǔ sāng', + '为人' => ' wéi rén', + '屏气' => ' bǐng qì', + '尿脬' => ' suī pāo', + '民乐' => ' mín yuè', + '糕干' => ' gāo gān', + '篙头' => ' gāo tou', + '喉咽' => ' hóu yān', + '大轴' => ' dà zhòu', + '朝露' => ' zhāo lù', + '吵子' => ' chǎo zǐ', + '邮折' => ' yóu shé', + '椎骨' => ' zhuī gǔ', + '命薄' => ' mìng bó', + '鸣镝' => ' míng dí', + '什菜' => ' shí cài', + '绊倒' => ' bàn dǎo', + '大藏' => ' dà zàng', + '差数' => ' chā shù', + '岗子' => ' gǎng zǐ', + '属文' => ' zhǔ wén', + '猪仔' => ' zhū zǎi', + '著者' => ' zhù zhě', + '浇薄' => ' jiāo bó', + '猪只' => ' zhū zhī', + '告倒' => ' gào dǎo', + '为时' => ' wéi shí', + '缸子' => ' gāng zǐ', + '山脊' => ' shān jǐ', + '告假' => ' gào jià', + '山子' => ' shān zǐ', + '奠都' => ' diàn dū', + '子层' => ' zǐ céng', + '宫掖' => ' gōng yè', + '倒跌' => ' dǎo diē', + '尽饱' => ' jìn bǎo', + '模样' => ' mú yàng', + '了无' => ' liǎo wú', + '直奔' => ' zhí bèn', + '等子' => ' děng zǐ', + '循分' => ' xún fèn', + '盘诘' => ' pán jié', + '盘查' => ' pán zhā', + '戥子' => ' děng zǐ', + '盘倒' => ' pán dǎo', + '了得' => ' liǎo de', + '镫子' => ' dèng zǐ', + '基甸' => ' jī diàn', + '尽头' => ' jìn tóu', + '累加' => ' lěi jiā', + '执拗' => ' zhí niù', + '省悟' => ' xǐng wù', + '的应' => ' de yìng', + '得用' => ' de yòng', + '垣曲' => ' yuán qǔ', + '盼头' => ' pàn tou', + '的真' => ' dí zhēn', + '等得' => ' děng de', + '累代' => ' lěi dài', + '风子' => ' fēng zǐ', + '乐队' => ' yuè duì', + '妹妹' => ' mèi mei', + '行伍' => ' háng wǔ', + '杭育' => ' háng yō', + '行子' => ' háng zǐ', + '宁都' => ' níng dū', + '子星' => ' zǐ xīng', + '滑倒' => ' huá dǎo', + '子埝' => ' zǐ niàn', + '国难' => ' guó nàn', + '准的' => ' zhǔn dì', + '贵处' => ' guì chǔ', + '渺子' => ' miǎo zǐ', + '佛殿' => ' fó diàn', + '归处' => ' guī chǔ', + '奇零' => ' jī líng', + '子囊' => ' zǐ náng', + '子陵' => ' zǐ líng', + '子明' => ' zǐ míng', + '锅伙' => ' guō huo', + '过淋' => ' guò lìn', + '姐姐' => ' jiě jie', + '蜾蠃' => ' guǒ luǒ', + '子棉' => ' zǐ mián', + '打颤' => ' dǎ zhàn', + '累时' => ' lěi shí', + '得能' => ' de néng', + '子弦' => ' zǐ xián', + '尽皆' => ' jìn jiē', + '朝暮' => ' zhāo mù', + '得亏' => ' děi kuī', + '尽数' => ' jìn shù', + '尽职' => ' jìn zhí', + '尽是' => ' jìn shì', + '报载' => ' bào zǎi', + '唠唠' => ' lào lào', + '得着' => ' de zháo', + '落架' => ' lào jià', + '劲急' => ' jìng jí', + '甸子' => ' diàn zǐ', + '报帖' => ' bào tiě', + '得性' => ' de xìng', + '锥子' => ' zhuī zǐ', + '得行' => ' de xíng', + '荡子' => ' dàng zǐ', + '挡子' => ' dǎng zǐ', + '倒板' => ' dǎo bǎn', + '曾波' => ' zēng bō', + '念佛' => ' niàn fó', + '忖度' => ' cǔn duó', + '当子' => ' dāng zǐ', + '倒错' => ' dǎo cuò', + '尽致' => ' jìn zhì', + '尽言' => ' jìn yán', + '得请' => ' de qǐng', + '电发' => ' diàn fà', + '乐音' => ' yuè yīn', + '标的' => ' biāo dì', + '累世' => ' lěi shì', + '得幸' => ' de xìng', + '了不' => ' liǎo bù', + '樵子' => ' qiáo zǐ', + '场儿' => ' chǎng r', + '狼子' => ' láng zǐ', + '琅邪' => ' láng yá', + '后头' => ' hòu tou', + '尽心' => ' jìn xīn', + '复兴' => ' fù xīng', + '得当' => ' dé dàng', + '电磨' => ' diàn mò', + '后燕' => ' hòu yān', + '店子' => ' diàn zǐ', + '癫子' => ' diān zǐ', + '拉纤' => ' lā qiàn', + '盔头' => ' kuī tou', + '颠仆' => ' diān pú', + '勒兵' => ' lè bīng', + '唠扯' => ' lào chě', + '骶椎' => ' dǐ zhuī', + '混和' => ' hùn huò', + '子平' => ' zǐ píng', + '海难' => ' hǎi nàn', + '泄露' => ' xiè lòu', + '钻塔' => ' zuàn tǎ', + '黑煞' => ' hēi shà', + '子贡' => ' zǐ gòng', + '画帖' => ' huà tiè', + '侯家' => ' hòu jiā', + '泌阳' => ' bì yáng', + '侯氏' => ' hòu shì', + '互见' => ' hù xiàn', + '化为' => ' huà wéi', + '左肋' => ' zuǒ lèi', + '鹄望' => ' hú wàng', + '好施' => ' hào shī', + '油炸' => ' yóu zhá', + '孟子' => ' mèng zǐ', + '孝子' => ' xiào zǐ', + '孔子' => ' kǒng zǐ', + '泊松' => ' bó sōng', + '洪佛' => ' hóng fó', + '红子' => ' hóng zǐ', + '子网' => ' zǐ wǎng', + '洗马' => ' xiǎn mǎ', + '洋脊' => ' yáng jǐ', + '子洲' => ' zǐ zhōu', + '嫌恶' => ' xián wù', + '一定' => ' yí dìng', + '宜兴' => ' yí xīng', + '子宫' => ' zǐ gōng', + '钻具' => ' zuàn jù', + '浪子' => ' làng zǐ', + '流血' => ' liú xiě', + '流干' => ' líu gān', + '没收' => ' mò shōu', + '和药' => ' huò yào', + '獾子' => ' huān zǐ', + '子房' => ' zǐ fáng', + '环子' => ' huán zǐ', + '沉没' => ' chén mò', + '子丑' => ' zǐ chǒu', + '回煞' => ' huí shà', + '子京' => ' zǐ jīng', + '子婴' => ' zǐ yīng', + '子真' => ' zǐ zhēn', + '婚假' => ' hūn jià', + '宁可' => ' nìng kě', + '一块' => ' yí kuài', + '寒舍' => ' hán shè', + '沦为' => ' lún wéi', + '寒假' => ' hán jià', + '回弹' => ' huí tán', + '密缝' => ' mì féng', + '泰斗' => ' tài dǒu', + '横逆' => ' hèng nì', + '子痫' => ' zǐ xián', + '漏斗' => ' lòu dǒu', + '家舍' => ' jiā shè', + '椎体' => ' zhuī tǐ', + '少妇' => ' shào fù', + '少女' => ' shào nǚ', + '家累' => ' jiā lěi', + '伽蓝' => ' qié lán', + '棘爪' => ' jí zhuǎ', + '气钻' => ' qì zuàn', + '沤凼' => ' òu dàng', + '狗舍' => ' gǒu shè', + '夹被' => ' jiá bèi', + '漂砾' => ' piāo lì', + '好斗' => ' hào dòu', + '卓子' => ' zhuó zǐ', + '一更' => ' yī gēng', + '汗国' => ' hán guó', + '乖子' => ' guāi zǐ', + '溶没' => ' róng mò', + '子乔' => ' zǐ qiáo', + '子桑' => ' zǐ sāng', + '浅薄' => ' qiǎn bó', + '混蛋' => ' hún dàn', + '混球' => ' hún qíu', + '漂移' => ' piāo yí', + '乖剌' => ' guāi là', + '孱头' => ' càn tou', + '鼓角' => ' gǔ jiǎo', + '子产' => ' zǐ chǎn', + '官子' => ' guān zǐ', + '泥守' => ' nì shǒu', + '环伺' => ' huán sì', + '子精' => ' zǐ jīng', + '泡货' => ' pāo huò', + '柞蚕' => ' zuò cán', + '泥泞' => ' ní nìng', + '潜没' => ' qián mò', + '婊子' => ' biǎo zǐ', + '咕隆' => ' gū lōng', + '漂泊' => ' piāo bó', + '漂浮' => ' piāo fú', + '漂洗' => ' piǎo xǐ', + '给养' => ' jǐ yǎng', + '谷坊' => ' gǔ fáng', + '继兴' => ' jì xīng', + '固着' => ' gù zhuó', + '自供' => ' zì gòng', + '挂斗' => ' guà dǒu', + '满处' => ' mǎn chǔ', + '瓜蔓' => ' guā wàn', + '怪得' => ' guài de', + '倒闭' => ' dǎo bì', + '逻辑' => ' luó ji', + '不对' => ' bú duì', + '差讹' => ' chā é', + '薄雾' => ' bó wù', + '五子' => ' wǔ zǐ', + '谱子' => ' pǔ zǐ', + '乌拉' => ' wù lɑ', + '西曲' => ' xī qǔ', + '图子' => ' tú zǐ', + '栖栖' => ' xī xī', + '熬稃' => ' āo fū', + '热呼' => ' rè hu', + '毂辘' => ' gū lù', + '一起' => ' yì qǐ', + '跛子' => ' bǒ zǐ', + '跂訾' => ' qǐ zǐ', + '夫子' => ' fū zǐ', + '薄利' => ' bó lì', + '子堤' => ' zǐ dī', + '姑子' => ' gū zǐ', + '息子' => ' xī zǐ', + '系子' => ' xì zǐ', + '股子' => ' gǔ zǐ', + '自个' => ' zì gě', + '一路' => ' yí lù', + '谱曲' => ' pǔ qǔ', + '巨擘' => ' jù bò', + '奇偶' => ' jī ǒu', + '母子' => ' mǔ zǐ', + '薄暮' => ' bó mù', + '无似' => ' wú sì', + '薄暗' => ' bó àn', + '奇技' => ' qí jì', + '苏打' => ' sū dá', + '贾客' => ' gǔ kè', + '薄技' => ' bó jì', + '漯河' => ' tà hé', + '於菟' => ' wū tú', + '局子' => ' jú zǐ', + '杌子' => ' wù zǐ', + '女子' => ' nǚ zǐ', + '女仆' => ' nǚ pú', + '阁子' => ' gé zǐ', + '葛麻' => ' gě má', + '屋脊' => ' wū jǐ', + '屈曲' => ' qū qǔ', + '奴仆' => ' nú pú', + '兀秃' => ' wū tū', + '激子' => ' jī zǐ', + '汩没' => ' gǔ mò', + '骨碌' => ' gū lù', + '细发' => ' xì fà', + '子路' => ' zǐ lù', + '曲子' => ' qǔ zi', + '厄难' => ' è nàn', + '度曲' => ' dù qǔ', + '衣子' => ' yī zǐ', + '痦子' => ' wù zǐ', + '子弟' => ' zǐ dì', + '不义' => ' bú yì', + '都司' => ' dū sī', + '鹤发' => ' hè fà', + '西子' => ' xī zǐ', + '酷似' => ' kù sì', + '西打' => ' xī dá', + '蛤蛎' => ' gé lì', + '寄子' => ' jì zǐ', + '医卜' => ' yī bǔ', + '寄与' => ' jì yú', + '卒子' => ' zú zǐ', + '蛤蜊' => ' gé lí', + '淤塞' => ' yū sè', + '夜曲' => ' yè qǔ', + '都督' => ' dū du', + '都护' => ' dū hù', + '独得' => ' dú de', + '古佛' => ' gǔ fó', + '桴子' => ' fú zǐ', + '语塞' => ' yǔ sè', + '武曲' => ' wǔ qǔ', + '起子' => ' qǐ zǐ', + '语域' => ' yǔ yù', + '穄子' => ' jì zǐ', + '鸡子' => ' jī zǐ', + '虎子' => ' hǔ zǐ', + '薄地' => ' bó dì', + '子安' => ' zǐ ān', + '子部' => ' zǐ bù', + '子叶' => ' zǐ yè', + '机子' => ' jī zǐ', + '浮子' => ' fú zǐ', + '附子' => ' fù zǐ', + '嘎子' => ' gǎ zǐ', + '婀娜' => ' ē nuó', + '迪吧' => ' dí bā', + '曲艺' => ' qǔ yì', + '畜牧' => ' xù mù', + '袄子' => ' ǎo zǐ', + '克勒' => ' kè lè', + '套儿' => ' tào r', + '喜得' => ' xǐ de', + '子夜' => ' zǐ yè', + '盖儿' => ' gài r', + '佛力' => ' fó lì', + '佛理' => ' fó lǐ', + '辈儿' => ' bèi r', + '子母' => ' zǐ mǔ', + '字模' => ' zì mú', + '佛偈' => ' fó jì', + '羁勒' => ' jī lè', + '佛骨' => ' fó gǔ', + '子墨' => ' zǐ mò', + '佛曲' => ' fó qǔ', + '子么' => ' zǐ mǒ', + '妻子' => ' qī zǐ', + '差额' => ' chā é', + '屉子' => ' tì zǐ', + '剃发' => ' tì fà', + '佛土' => ' fó tǔ', + '於乎' => ' wū hū', + '籍没' => ' jí mò', + '特勒' => ' tè lè', + '一意' => ' yī yì', + '记得' => ' jì de', + '佛历' => ' fó lì', + '佛母' => ' fó mǔ', + '尉犁' => ' yù lí', + '撒播' => ' sǎ bō', + '案子' => ' àn zǐ', + '脉脉' => ' mò mò', + '瓯子' => ' ōu zǐ', + '组曲' => ' zǔ qǔ', + '扒鸡' => ' pá jī', + '派司' => ' pā sī', + '筢子' => ' pá zǐ', + '卜卜' => ' bǔ bǔ', + '撒督' => ' sǎ dū', + '发屋' => ' fà wū', + '瓦剌' => ' wǎ là', + '发丝' => ' fà sī', + '趵趵' => ' bō bō', + '咖喱' => ' gā lí', + '胡佛' => ' hú fó', + '啪啦' => ' pā lā', + '昵比' => ' nì bì', + '的子' => ' de zǐ', + '麸子' => ' fū zǐ', + '牙子' => ' yá zǐ', + '哇啦' => ' wā lā', + '子鸡' => ' zǐ jī', + '父子' => ' fù zǐ', + '偶遇' => ' ǒu yù', + '迤逦' => ' yǐ lǐ', + '大雅' => ' dà yá', + '子期' => ' zǐ qī', + '狸子' => ' lí zǐ', + '帕子' => ' pà zǐ', + '末子' => ' mò zǐ', + '葛摩' => ' gě mó', + '子奇' => ' zǐ qí', + '大都' => ' dà dū', + '帝都' => ' dì dū', + '哥子' => ' gē zǐ', + '希腊' => ' xī là', + '席勒' => ' xí lè', + '带儿' => ' dài r', + '无得' => ' wú de', + '娃子' => ' wá zǐ', + '六安' => ' lù ān', + '夫夫' => ' fú fū', + '薄荷' => ' bò he', + '薄礼' => ' bó lǐ', + '负荷' => ' fù hè', + '万俟' => ' mò qí', + '辣子' => ' là zǐ', + '独子' => ' dú zǐ', + '爷爷' => ' yé ye', + '理发' => ' lǐ fà', + '妮子' => ' nī zǐ', + '佛子' => ' fó zǐ', + '辟谷' => ' bì gǔ', + '娃娃' => ' wá wa', + '太阿' => ' tài ē', + '踏踏' => ' tā tà', + '哈巴' => ' hǎ bā', + '觑糊' => ' qù hu', + '一度' => ' yí dù', + '哈呀' => ' hā yā', + '泰阿' => ' tài ē', + '迷糊' => ' mí hu', + '巨贾' => ' jù gǔ', + '逆子' => ' nì zǐ', + '玛曲' => ' mǎ qǔ', + '居积' => ' jū jī', + '覆没' => ' fù mò', + '子兮' => ' zǐ xī', + '沮洳' => ' jù rù', + '子息' => ' zǐ xī', + '子午' => ' zǐ wǔ', + '武都' => ' wǔ dū', + '二曲' => ' èr qǔ', + '法子' => ' fǎ zǐ', + '怒发' => ' nù fà', + '要击' => ' yāo jī', + '齱齵' => ' zōu yú', + '瓦舍' => ' wǎ shè', + '解法' => ' xiè fǎ', + '瓦刀' => ' wà dāo', + '崴泥' => ' wēi ní', + '龟兹' => ' qiū cí', + '要得' => ' yào de', + '麇集' => ' qún jí', + '丽致' => ' lí zhì', + '要末' => ' yào me', + '鼻咽' => ' bí yān', + '盐都' => ' yán dū', + '叱喝' => ' chì hè', + '核查' => ' hé zhā', + '可汗' => ' kè hán', + '帵子' => ' wān zǐ', + '宛似' => ' wǎn sì', + '亲子' => ' qīn zǐ', + '叱呵' => ' chì hē', + '觉得' => ' jué de', + '角力' => ' jué lì', + '吆喝' => ' yāo he', + '湾泊' => ' wān bó', + '鹄候' => ' hú hòu', + '替角' => ' tì jué', + '句读' => ' jù dòu', + '掸子' => ' dǎn zǐ', + '骨头' => ' gú tou', + '凯撒' => ' kǎi sǎ', + '投得' => ' tóu de', + '骰子' => ' tóu zǐ', + '吐泻' => ' tù xiè', + '血塞' => ' xuè sè', + '衣甲' => ' yì jiǎ', + '骨殖' => ' gǔ shi', + '角色' => ' jué sè', + '论语' => ' lún yǔ', + '韦曲' => ' wéi qǔ', + '驮子' => ' duò zǐ', + '试液' => ' shì yè', + '高丽' => ' gāo lí', + '摩莎' => ' mó suō', + '要子' => ' yào zǐ', + '角抵' => ' jué dǐ', + '崴子' => ' wēi zǐ', + '外子' => ' wài zǐ', + '鲍耶' => ' bào yē', + '阿顺' => ' ē shùn', + '驳倒' => ' bó dǎo', + '记为' => ' jì wéi', + '可数' => ' kě shǔ', + '且夫' => ' qiě fú', + '可塞' => ' kě sài', + '记载' => ' jì zǎi', + '鬓发' => ' bìn fà', + '合为' => ' hé wéi', + '脱模' => ' tuō mú', + '月子' => ' yuè zǐ', + '折子' => ' zhé zǐ', + '诸葛' => ' zhū gě', + '为底' => ' wéi dǐ', + '割舍' => ' gē shè', + '铁勒' => ' tiě lè', + '卑鄙' => ' bēi bǐ', + '列子' => ' liè zǐ', + '阿亨' => ' a hēng', + '阻难' => ' zǔ nàn', + '半拉' => ' bàn lǎ', + '半打' => ' bàn dá', + '遇难' => ' yù nàn', + '阇黎' => ' shé lí', + '魏都' => ' wèi dū', + '褶子' => ' zhě zǐ', + '托子' => ' tuō zǐ', + '袒裼' => ' tǎn xī', + '入月' => ' rù yuè', + '囤聚' => ' tún jù', + '屯堡' => ' tún pù', + '腿子' => ' tuǐ zǐ', + '发式' => ' fà shì', + '合著' => ' hé zhù', + '发带' => ' fà dài', + '切末' => ' qiē mò', + '发菜' => ' fà cài', + '为乐' => ' wéi lè', + '魔难' => ' mó nàn', + '褪去' => ' tùn qù', + '为力' => ' wéi lì', + '一只' => ' yì zhī', + '推磨' => ' tuī mò', + '诱掖' => ' yòu yè', + '袍子' => ' páo zǐ', + '入肉' => ' rù ròu', + '里头' => ' lǐ tou', + '认得' => ' rèn de', + '服事' => ' fú shi', + '吐血' => ' tù xiě', + '欸乃' => ' ǎi nǎi', + '歌子' => ' gē zǐ', + '子女' => ' zǐ nǚ', + '鄙薄' => ' bǐ bó', + '兀自' => ' wū zì', + '何似' => ' hé sì', + '那末' => ' nà me', + '那曲' => ' nǎ qū', + '子细' => ' zǐ xì', + '蝲蛄' => ' là gǔ', + '补给' => ' bǔ jǐ', + '饵子' => ' ěr zǐ', + '歌曲' => ' gē qǔ', + '里脊' => ' lǐ jǐ', + '嫡子' => ' dí zǐ', + '喔喔' => ' wō wō', + '苏子' => ' sū zǐ', + '俗子' => ' sú zǐ', + '蔚蔚' => ' yù yù', + '嗉子' => ' sù zǐ', + '垡子' => ' fá zǐ', + '几米' => ' jī mǐ', + '几率' => ' jī lǜ', + '几欲' => ' jī yù', + '那麽' => ' nà me', + '服服' => ' fú fu', + '几案' => ' jī àn', + '宜都' => ' yí dū', + '虾蟆' => ' há ma', + '咔叽' => ' kǎ jī', + '不子' => ' bù zǐ', + '禾子' => ' hé zǐ', + '诃子' => ' hē zǐ', + '刻薄' => ' kè bó', + '泥子' => ' nì zǐ', + '次子' => ' cì zǐ', + '粒子' => ' lì zǐ', + '虮子' => ' jǐ zǐ', + '朴子' => ' pò zǐ', + '犊子' => ' dú zǐ', + '蟢子' => ' xǐ zǐ', + '弟弟' => ' dì di', + '子目' => ' zǐ mù', + '都邑' => ' dū yì', + '曲奇' => ' qǔ qí', + '子粒' => ' zǐ lì', + '泥古' => ' nì gǔ', + '疑似' => ' yí sì', + '疙疸' => ' gē da', + '訾议' => ' zǐ yì', + '曲沃' => ' qǔ wò', + '琴曲' => ' qín qǔ', + '子羽' => ' zǐ yǔ', + '仆役' => ' pú yì', + '子句' => ' zǐ jù', + '发妻' => ' fà qī', + '坷垃' => ' kē lā', + '法曲' => ' fǎ qǔ', + '弟子' => ' dì zǐ', + '子集' => ' zǐ jí', + '欧泊' => ' ōu bó', + '发乳' => ' fà rǔ', + '微曲' => ' wēi qǔ', + '子嗣' => ' zǐ sì', + '古刹' => ' gǔ chà', + '投子' => ' tóu zǐ', + '只得' => ' zhǐ de', + '蛮子' => ' mán zǐ', + '蚕薄' => ' cán bó', + '骨朵' => ' gū duǒ', + '误差' => ' wù chā', + '诸子' => ' zhū zǐ', + '驼子' => ' tuó zǐ', + '撒门' => ' sǎ mén', + '子鱼' => ' zǐ yú', + '呼啦' => ' hū lā', + '孤子' => ' gū zǐ', + '举子' => ' jǔ zǐ', + '几希' => ' jī xī', + '暗堡' => ' àn pù', + '婆子' => ' pó zǐ', + '几乎' => ' jī hū', + '窝子' => ' wō zǐ', + '子婿' => ' zǐ xù', + '曲率' => ' qǔ lǜ', + '子虚' => ' zǐ xū', + '具足' => ' jù zú', + '曲目' => ' qǔ mù', + '巨子' => ' jù zǐ', + '发际' => ' fà jì', + '季子' => ' jì zǐ', + '瓠子' => ' hù zǐ', + '坷拉' => ' kē lā', + '序曲' => ' xù qǔ', + '素子' => ' sù zǐ', + '鹄立' => ' hú lì', + '都德' => ' dū dé', + '四子' => ' sì zǐ', + '俗曲' => ' sú qǔ', + '子域' => ' zǐ yù', + '怒喝' => ' nù hè', + '体己' => ' tī ji', + '阇梨' => ' shé lí', + '得色' => ' de sè', + '鏊子' => ' ào zǐ', + '斗门' => ' dǒu mén', + '的卢' => ' dì lú', + '樀樀' => ' dí dí', + '日没' => ' rì mò', + '嘀哒' => ' dī dā', + '乐得' => ' lè de', + '乐呵' => ' lè hē', + '目的' => ' mù dì', + '模糊' => ' mó hu', + '铺路' => ' pū lù', + '模子' => ' mú zǐ', + '勒子' => ' lè zǐ', + '模具' => ' mú jù', + '模似' => ' mó sì', + '复辟' => ' fù bì', + '于都' => ' yú dū', + '勒逼' => ' lè bī', + '勒抑' => ' lè yì', + '得按' => ' de àn', + '堵塞' => ' dǔ sè', + '锯子' => ' jù zǐ', + '卜辞' => ' bǔ cí', + '棵子' => ' kē zǐ', + '须得' => ' xū de', + '余裕' => ' yú yù', + '秕子' => ' bǐ zǐ', + '秘鲁' => ' bì lǔ', + '租子' => ' zū zǐ', + '吗哪' => ' mǎ nǎ', + '余吾' => ' yú wú', + '故都' => ' gù dū', + '伛偻' => ' yǔ lǚ', + '稀薄' => ' xī bó', + '没地' => ' mò dì', + '阿诺' => ' a nuò', + '戊子' => ' wù zǐ', + '戏子' => ' xì zǐ', + '戏曲' => ' xì qǔ', + '没入' => ' mò rù', + '自给' => ' zì jǐ', + '摊儿' => ' tān r', + '圩日' => ' xū rì', + '去得' => ' qù de', + '露茜' => ' lù xī', + '撒子' => ' sā zǐ', + '铺砌' => ' pū qì', + '锞子' => ' kè zǐ', + '嗳腐' => ' ài fǔ', + '砝码' => ' fǎ mǎ', + '达子' => ' dá zǐ', + '搭子' => ' dā zǐ', + '矮子' => ' ǎi zǐ', + '博得' => ' bó de', + '卜居' => ' bǔ jū', + '大佛' => ' dà fó', + '南无' => ' nā mó', + '答答' => ' dā dā', + '噶嗒' => ' gá tà', + '舞曲' => ' wǔ qǔ', + '阿森' => ' a sēn', + '破的' => ' pò dì', + '咋呀' => ' zǎ yā', + '俟机' => ' sì jī', + '附和' => ' fù hè', + '卜课' => ' bǔ kè', + '般若' => ' bō rě', + '得也' => ' de yě', + '杜塞' => ' dù sè', + '阿伦' => ' a lún', + '坨儿' => ' tuó r', + '鞑子' => ' dá zǐ', + '阿斗' => ' ā dǒu', + '剂子' => ' jì zǐ', + '菥蓂' => ' xī mì', + '口哦' => ' kǒu ò', + '钜子' => ' jù zǐ', + '墨子' => ' mò zǐ', + '钩儿' => ' gōu r', + '李子' => ' lǐ zǐ', + '塞责' => ' sè zé', + '锔子' => ' jū zǐ', + '徒裼' => ' tú xī', + '弥勒' => ' mí lè', + '塔扎' => ' tǎ zā', + '坯模' => ' pī mú', + '百色' => ' bó sè', + '拉忽' => ' lǎ hū', + '役畜' => ' yì xù', + '剌剌' => ' là là', + '砬子' => ' lá zǐ', + '劲儿' => ' jìn r', + '铺叙' => ' pū xù', + '铺子' => ' pù zǐ', + '卜骨' => ' bǔ gǔ', + '勒马' => ' lè mǎ', + '科克' => ' kē kè', + '雨子' => ' yǔ zǐ', + '得气' => ' de qì', + '姓曾' => ' xìng zēng', + '阿附' => ' ē fù', + '壳儿' => ' ké r', + '理儿' => ' lǐ r', + '尜儿' => ' gá r', + '啊哟' => ' ā yō', + '挨饿' => ' ái è', + '皮儿' => ' pí r', + '啊呀' => ' ā yā', + '阿里' => ' a lǐ', + '曾姓' => ' zēng xìng', + '肚儿' => ' dǔ r', + '杂沓' => ' zá tà', + '辱没' => ' rǔ mò', + '集子' => ' jí zǐ', + '呕气' => ' òu qì', + '嗣子' => ' sì zǐ', + '曾家' => ' zēng jiā', + '嘀嗒' => ' dī dā', + '呕吐' => ' ǒu tù', + '嘎啦' => ' gā lā', + '呜咽' => ' wū yè', + '子儿' => ' zǐ r', + '嗯啊' => ' ng ā', + '鹿子' => ' lù zǐ', + '啊哈' => ' ā hā', + '阿耶' => ' ā yē', + '吖吖' => ' ā ā', + '阿苏' => ' a sū', + '囮子' => ' é zǐ', + '阿瑟' => ' a sè', + '阿佤' => ' a wǎ', + '呃呃' => ' e e㐀', + '巴阿' => ' bā a', + '腌臜' => ' ā zā', + '阿比' => ' ē bǐ', + '抹额' => ' mò é', + '末儿' => ' mò r', + '阿的' => ' ā dì', + '阿巴' => ' a bā', + '阿坝' => ' a bà', + '阿堵' => ' ē dǔ', + '便宜' => ' pián yí', + '阿卡' => ' a kǎ', + '哦呀' => ' ò yā', + '阿谀' => ' ē yú', + '地儿' => ' dì r', + '磨子' => ' mò zǐ', + '碌曲' => ' lù qǔ', + '离子' => ' lí zǐ', + '哑子' => ' yǎ zǐ', + '玉佛' => ' yù fó', + '卧佛' => ' wò fó', + '欲得' => ' yù de', + '沵迤' => ' mǐ yǐ', + '谜子' => ' mí zǐ', + '靶子' => ' bǎ zǐ', + '喜子' => ' xǐ zǐ', + '胆儿' => ' dǎn r', + '腻子' => ' nì zǐ', + '曲谱' => ' qǔ pǔ', + '靡靡' => ' mǐ mǐ', + '扶掖' => ' fú yè', + '莫得' => ' mò de', + '栖息' => ' qī xī', + '冒顿' => ' mò dú', + '靺鞨' => ' mò hé', + '把儿' => ' bà er', + '预卜' => ' yù bǔ', + '麻麻' => ' mā mɑ', + '革吉' => ' gé jí', + '姁姁' => ' xū xū', + '浴佛' => ' yù fó', + '屈子' => ' qū zǐ', + '恶煞' => ' è shà', + '孺子' => ' rú zǐ', + '嗳气' => ' ài qì', + '恶心' => ' ě xīn', + '恶嗪' => ' ě qín', + '瘄子' => ' cù zǐ', + '粗呢' => ' cū ní', + '撒布' => ' sǎ bù', + '刺啦' => ' cī lā', + '爱子' => ' ài zǐ', + '入子' => ' rù zǐ', + '垆坶' => ' lú mù', + '呲牙' => ' zī yá', + '靡丽' => ' mǐ lì', + '词曲' => ' cí qǔ', + '磨叽' => ' mò ji', + '卤子' => ' lǔ zǐ', + '乳女' => ' rǔ nǚ', + '码子' => ' mǎ zǐ', + '麻秸' => ' má jí', + '玉子' => ' yù zǐ', + '秃子' => ' tū zǐ', + '虚子' => ' xū zǐ', + '扒犁' => ' pá li', + '得死' => ' de sǐ', + '得理' => ' de lǐ', + '不敷' => ' bù fū', + '拘泥' => ' jū nì', + '马勒' => ' mǎ lè', + '马子' => ' mǎ zǐ', + '篦子' => ' bì zǐ', + '马尾' => ' mǎ yǐ', + '餔子' => ' bū zǐ', + '布子' => ' bù zǐ', + '堡子' => ' bǔ zi', + '补子' => ' bǔ zǐ', + '丫子' => ' yā zǐ', + '哈佛' => ' hā fó', + '米芾' => ' mǐ fú', + '驹子' => ' jū zǐ', + '哇哇' => ' wā wā', + '脊骨' => ' jǐ gǔ', + '部曲' => ' bù qǔ', + '哎呀' => ' āi yā', + '箕子' => ' jī zǐ', + '把子' => ' bà zi', + '曲裾' => ' qǔ jū', + '莫邪' => ' mò yé', + '壳子' => ' ké zǐ', + '哑哑' => ' yā yā', + '呀呀' => ' yā yā', + '木模' => ' mù mú', + '意得' => ' yì de', + '吧嗒' => ' bā dā', + '坝子' => ' bà zǐ', + '吧唧' => ' bā jī', + '一发' => ' yī fà', + '七佛' => ' qī fó', + '耳子' => ' ěr zǐ', + '七个' => ' qī ge', + '吖嗪' => ' ā qín', + '佛寺' => ' fó sì', + '一得' => ' yī de', + '拨刺' => ' bō cī', + '耙子' => ' pá zǐ', + '吟哦' => ' yín é', + '磨墨' => ' mó mò', + '魣鱼' => ' yú yú', + '婢子' => ' bì zǐ', + '哈达' => ' hǎ dá', + '哈迪' => ' hǎ dí', + '拨子' => ' bō zǐ', + '簸箕' => ' bò ji', + '簿子' => ' bù zǐ', + '牟尼' => ' mù ní', + '披靡' => ' pī mǐ', + '似核' => ' sì hé', + '肚子' => ' dǔ zi', + '可恶' => ' kě wù', + '移易' => ' yí yì', + '胡子' => ' hú zǐ', + '仡仡' => ' yì yì', + '给予' => ' jǐ yǔ', + '鱼子' => ' yú zǐ', + '挨打' => ' ái dǎ', + '挨批' => ' ái pī', + '咿呀' => ' yī yā', + '挨挤' => ' ái jǐ', + '郁塞' => ' yù sè', + '丝柏' => ' sī bó', + '鱼肚' => ' yú dǔ', + '挨骂' => ' ái mà', + '移译' => ' yí yì', + '糊糊' => ' hū hū', + '发髻' => ' fà jì', + '咖哩' => ' kā li', + '哀子' => ' āi zǐ', + '糢糊' => ' mó hu', + '古都' => ' gǔ dū', + '披发' => ' pī fà', + '须发' => ' xū fà', + '曲剧' => ' qǔ jù', + '播撒' => ' bō sǎ', + '木子' => ' mù zǐ', + '抹布' => ' mā bù', + '哥哥' => ' gē ge', + '那得' => ' nà de', + '梨子' => ' lí zǐ', + '衲子' => ' nà zǐ', + '曲辞' => ' qǔ cí', + '嗳哟' => ' ài yō', + '筏子' => ' fá zǐ', + '答理' => ' dā lǐ', + '策勒' => ' cè lè', + '佛语' => ' fó yǔ', + '脯子' => ' pú zǐ', + '取得' => ' qǔ de', + '曲度' => ' qǔ dù', + '箅子' => ' bì zǐ', + '驴子' => ' lǘ zǐ', + '不落' => ' bù là', + '翼子' => ' yì zǐ', + '胰子' => ' yí zǐ', + '义子' => ' yì zǐ', + '须子' => ' xū zǐ', + '吧女' => ' bā nǚ', + '一个' => ' yí gè', + '乙巳' => ' yǐ sì', + '仆妇' => ' pú fù', + '昳丽' => ' yì lì', + '麻力' => ' má li', + '呢子' => ' ní zǐ', + '伢子' => ' yá zǐ', + '伯伯' => ' bó bo', + '摩撒' => ' mā sa', + '伯都' => ' bó dū', + '估衣' => ' gù yī', + '摩挲' => ' mā sā', + '吴子' => ' wú zǐ', + '吽牙' => ' ōu yá', + '耶鲁' => ' yē lǔ', + '仆仆' => ' pú pú', + '合子' => ' hé zǐ', + '泼剌' => ' pō là', + '伺服' => ' sì fú', + '鹿谷' => ' lù gǔ', + '鹄的' => ' gǔ dì', + '以撒' => ' yǐ sǎ', + '格勒' => ' gé lè', + '坯子' => ' pī zǐ', + '继子' => ' jì zǐ', + '系泊' => ' jì bó', + '佛祖' => ' fó zǔ', + '阻塞' => ' zǔ sè', + '闭塞' => ' bì sè', + '丽都' => ' lì dū', + '的卡' => ' dí kǎ', + '礼佛' => ' lǐ fó', + '乐子' => ' lè zǐ', + '得度' => ' de dù', + '得得' => ' de de', + '地堡' => ' dì pù', + '俚曲' => ' lǐ qǔ', + '阿丹' => ' a dān', + '摸吧' => ' mō bā', + '里子' => ' lǐ zǐ', + '德都' => ' dé dū', + '阿来' => ' a lái', + '得地' => ' de dì', + '壶子' => ' hú zǐ', + '抹子' => ' mǒ zǐ', + '棋子' => ' qí zǐ', + '核子' => ' hé zǐ', + '核儿' => ' hú ér', + '呢呢' => ' ní ní', + '貔子' => ' pí zǐ', + '碧波' => ' bì bō', + '耶稣' => ' yē sū', + '野葛' => ' yě gě', + '鞍子' => ' ān zǐ', + '巴子' => ' bā zǐ', + '安子' => ' ān zǐ', + '弃子' => ' qì zǐ', + '揦子' => ' lá zǐ', + '衣被' => ' yì pī', + '页子' => ' yè zǐ', + '耶耶' => ' yē yē', + '也得' => ' yě de', + '也似' => ' yě sì', + '埯子' => ' ǎn zǐ', + '耶律' => ' yē lǜ', + '七子' => ' qī zǐ', + '掖咕' => ' yē gu', + '伽马' => ' gā mǎ', + '八子' => ' bā zǐ', + '仆射' => ' pú yè', + '伺机' => ' sì jī', + '伺隙' => ' sì xì', + '似乎' => ' sì hū', + '耶西' => ' yē xī', + '庵子' => ' ān zǐ', + '挨罚' => ' ái fá', + '佛牙' => ' fó yá', + '呵喝' => ' hē hè', + '一似' => ' yī sì', + '呼吁' => ' hū yù', + '佛爷' => ' fó yé', + '味儿' => ' wèi r', + '呵责' => ' hē zé', + '呵护' => ' hē hù', + '鮨科' => ' qí kē', + '凫茈' => ' fú zǐ', + '鸭子' => ' yā zǐ', + '佛法' => ' fó fǎ', + '一曲' => ' yī qǔ', + '佛塔' => ' fó tǎ', + '呵呵' => ' hē hē', + '呼喝' => ' hū hè', + '婢仆' => ' bì pú', + '耶酥' => ' yē sū', + '吐蕃' => ' tǔ bō', + '吐沫' => ' tù mò', + '吉勒' => ' jí lè', + '栖宿' => ' qī sù', + '搭拉' => ' dā la', + '吧吧' => ' bā bā', + '游子' => ' yóu zǐ', + '射的' => ' shè dì', + '琢磨' => ' zuó mo', + '打杈' => ' dǎ chà', + '哪吒' => ' né zhā', + '没世' => ' mò shì', + '抹灰' => ' mò huī', + '磨粉' => ' mò fěn', + '批假' => ' pī jià', + '难挨' => ' nán ái', + '扳子' => ' bān zǐ', + '啦呱' => ' lā gua', + '喀什' => ' kā shí', + '泯没' => ' mǐn mò', + '柰子' => ' nài zǐ', + '打折' => ' dǎ shé', + '喀嚓' => ' kā chā', + '缪斯' => ' miù sī', + '菜子' => ' cài zǐ', + '难弟' => ' nàn dì', + '哪个' => ' něi gè', + '穇子' => ' cǎn zǐ', + '拉杆' => ' lā gǎn', + '槽子' => ' cáo zǐ', + '艚子' => ' cáo zǐ', + '押解' => ' yā jiè', + '内子' => ' nèi zǐ', + '南子' => ' nán zǐ', + '咳痰' => ' ké tán', + '拓扑' => ' tuò pū', + '委蛇' => ' wēi yí', + '哇靠' => ' wā kào', + '拓拔' => ' tuò bá', + '不究' => ' bù jiù', + '补假' => ' bǔ jià', + '咱家' => ' zá jiā', + '潘子' => ' pān zǐ', + '咳嗽' => ' ké sòu', + '剥取' => ' bāo qǔ', + '彩子' => ' cǎi zǐ', + '拾级' => ' shè jí', + '不价' => ' bù jie', + '啪嚓' => ' pā chā', + '怒号' => ' nù háo', + '不倒' => ' bù dǎo', + '拶子' => ' zǎn zǐ', + '牛子' => ' niú zǐ', + '妞子' => ' niū zǐ', + '不处' => ' bù chǔ', + '咯嚓' => ' gē chā', + '拉呱' => ' lā guā', + '抿子' => ' mǐn zǐ', + '拓跋' => ' tuò bá', + '八斗' => ' bā dǒu', + '绊子' => ' bàn zǐ', + '剥壳' => ' bāo ké', + '吧托' => ' bā tuō', + '吞没' => ' tūn mò', + '名儿' => ' míng r', + '卡壳' => ' qiǎ ké', + '卡具' => ' qiǎ jù', + '熬菜' => ' āo cài', + '凄咽' => ' qī yān', + '吧台' => ' bā tái', + '柈子' => ' bàn zǐ', + '八舍' => ' bā shè', + '插曲' => ' chā qǔ', + '提溜' => ' dī liū', + '起解' => ' qǐ jiè', + '吖啶' => ' ā dìng', + '君子' => ' jūn zǐ', + '推子' => ' tuī zǐ', + '七煞' => ' qī shà', + '炮子' => ' pào zǐ', + '扫把' => ' sào bǎ', + '朴刀' => ' pō dāo', + '半子' => ' bàn zǐ', + '差可' => ' chā kě', + '配曲' => ' pèi qǔ', + '南曲' => ' nán qǔ', + '男仆' => ' nán pú', + '杈子' => ' chā zǐ', + '汊子' => ' chà zǐ', + '拉倒' => ' lā dǎo', + '拂士' => ' bì shì', + '查理' => ' zhā lǐ', + '车毂' => ' chē gū', + '炮格' => ' páo gé', + '采邑' => ' cài yì', + '酦醅' => ' pō pēi', + '掖门' => ' yè mén', + '百子' => ' bǎi zǐ', + '含糊' => ' hán hu', + '稗子' => ' bài zǐ', + '败子' => ' bài zǐ', + '吸尽' => ' xī jìn', + '探子' => ' tàn zǐ', + '版子' => ' bǎn zǐ', + '吸血' => ' xī xiě', + '陂陀' => ' pō tuó', + '呢帽' => ' ní mào', + '拖沓' => ' tuō tà', + '披散' => ' pī sǎn', + '干礼' => ' gān lǐ', + '干租' => ' gān zū', + '套子' => ' tào zǐ', + '昆曲' => ' kūn qǔ', + '岔子' => ' chà zǐ', + '套曲' => ' tào qǔ', + '屯子' => ' tún zǐ', + '秆子' => ' gǎn zǐ', + '奥什' => ' aò shí', + '奇数' => ' jī shù', + '赴难' => ' fù nàn', + '干子' => ' gàn zǐ', + '差异' => ' chā yì', + '失意' => ' shī yì', + '附载' => ' fù zǎi', + '附识' => ' fù zhì', + '夹克' => ' jiá kè', + '干没' => ' gān mò', + '干碍' => ' gān ài', + '干巴' => ' gān bā', + '巨著' => ' jù zhù', + '杆儿' => ' gǎn ér', + '甘子' => ' gān zǐ', + '柑子' => ' gān zǐ', + '干急' => ' gān jí', + '子时' => ' zǐ shí', + '蒿子' => ' hāo zǐ', + '宿舍' => ' sù shè', + '宿分' => ' sù fèn', + '家子' => ' jiā zǐ', + '家姑' => ' jiā gū', + '荷载' => ' hè zǎi', + '害得' => ' hài de', + '子弹' => ' zǐ dàn', + '和诗' => ' hè shī', + '子曰' => ' zǐ yuē', + '干谒' => ' gān yè', + '何为' => ' hé wéi', + '喝问' => ' hè wèn', + '子实' => ' zǐ shí', + '差距' => ' chā jù', + '鸡舍' => ' jī shè', + '呱哒' => ' guā dā', + '干呕' => ' gān ǒu', + '干热' => ' gān rè', + '杆塔' => ' gǎn tǎ', + '赶圩' => ' gǎn xū', + '干股' => ' gān gǔ', + '干季' => ' gān jì', + '拗口' => ' ào kǒu', + '暗查' => ' àn zhā', + '割切' => ' gē qiē', + '夹衣' => ' jiá yī', + '给回' => ' jǐ huí', + '居处' => ' jū chǔ', + '奶子' => ' nǎi zǐ', + '好似' => ' hǎo sì', + '好哇' => ' hǎo wā', + '尾子' => ' wěi zǐ', + '蛤仔' => ' gé zǎi', + '络子' => ' lào zi', + '圪节' => ' gē jiē', + '尧都' => ' yáo dū', + '哀乐' => ' āi yuè', + '拮据' => ' jié jū', + '哀号' => ' āi háo', + '拜佛' => ' bài fó', + '臬兀' => ' niè wū', + '哇噻' => ' wā sāi', + '哇塞' => ' wā sāi', + '牛肚' => ' niú dǔ', + '不为' => ' bù wéi', + '夺得' => ' duó de', + '歌呗' => ' gē bài', + '夹袄' => ' jiá ǎo', + '狗子' => ' gǒu zǐ', + '呱唧' => ' guā jī', + '属意' => ' zhǔ yì', + '羔子' => ' gāo zǐ', + '家仆' => ' jiā pú', + '购得' => ' gòu de', + '狗脊' => ' gǒu jǐ', + '枸橘' => ' gōu jú', + '好色' => ' hào sè', + '夹布' => ' jiá bù', + '好恶' => ' hào wù', + '贾祸' => ' gǔ huò', + '篙子' => ' gāo zǐ', + '锢露' => ' gù lòu', + '贾人' => ' gǔ rén', + '鼓乐' => ' gǔ yuè', + '妃子' => ' fēi zǐ', + '呱嗒' => ' guā dā', + '沟子' => ' gōu zǐ', + '膏泽' => ' gào zé', + '好客' => ' hào kè', + '好奇' => ' hào qí', + '膏子' => ' gāo zǐ', + '宝子' => ' bǎo zǐ', + '挨揍' => ' ái zòu', + '黑子' => ' hēi zǐ', + '扎染' => ' zā rǎn', + '打倒' => ' dǎ dǎo', + '扒糕' => ' pá gāo', + '喝彩' => ' hè cǎi', + '喝道' => ' hè dào', + '闷气' => ' mēn qì', + '喝采' => ' hè cǎi', + '宓妃' => ' fú fēi', + '扒灰' => ' pá huī', + '扒搂' => ' bā lōu', + '才子' => ' cái zǐ', + '咪唑' => ' mǐ zuò', + '痴子' => ' chī zǐ', + '扒窃' => ' pá qiè', + '喷薄' => ' pēn bó', + '吗啡' => ' mǎ fēi', + '处和' => ' chǔ hé', + '庐舍' => ' lú shè', + '路卡' => ' lù qiǎ', + '虏掠' => ' lǔ lüě', + '嘴子' => ' zuǐ zǐ', + '爱好' => ' ài hào', + '秘钥' => ' mì yuè', + '匙子' => ' chí zi', + '陆处' => ' lù chǔ', + '煤核' => ' méi hú', + '得靠' => ' de kào', + '微薄' => ' wēi bó', + '喜好' => ' xǐ hào', + '闷热' => ' mēn rè', + '喷子' => ' pēn zǐ', + '媒子' => ' méi zǐ', + '糜子' => ' méi zǐ', + '戽斗' => ' hù dǒu', + '戒子' => ' jiè zǐ', + '懒得' => ' lǎn de', + '所得' => ' suǒ de', + '门把' => ' mén bà', + '没得' => ' méi de', + '毛呢' => ' máo ní', + '单个' => ' dān ge', + '锚泊' => ' máo bó', + '满子' => ' mǎn zǐ', + '单曲' => ' dān qǔ', + '嫚骂' => ' màn mà', + '毛子' => ' máo zǐ', + '单薄' => ' dān bó', + '猝倒' => ' cù dǎo', + '篓子' => ' lǒu zǐ', + '力尽' => ' lì jìn', + '不尽' => ' bù jìn', + '制曲' => ' zhì qǔ', + '空额' => ' kòng é', + '不会' => ' bú huì', + '枝子' => ' zhī zǐ', + '朱子' => ' zhū zǐ', + '甲子' => ' jiǎ zǐ', + '不是' => ' bú shì', + '只鸡' => ' zhī jī', + '男子' => ' nán zǐ', + '款儿' => ' kuǎn r', + '果脯' => ' guǒ fǔ', + '幼子' => ' yòu zǐ', + '不禁' => ' bù jīn', + '倒卧' => ' dǎo wò', + '优子' => ' yōu zǐ', + '留都' => ' liú dū', + '只如' => ' zhī rú', + '枯萎' => ' kū wěi', + '著录' => ' zhù lù', + '臊气' => ' sào qì', + '卧倒' => ' wò dǎo', + '自尽' => ' zì jìn', + '勒戒' => ' lè jiè', + '恶少' => ' è shào', + '马仔' => ' mǎ zǎi', + '绺子' => ' liǔ zǐ', + '溜子' => ' liū zǐ', + '六子' => ' liù zǐ', + '情儿' => ' qíng r', + '漏子' => ' lòu zǐ', + '嗑药' => ' kè yào', + '唠嗑' => ' lào kè', + '哗啦' => ' huā lā', + '马扎' => ' mǎ zhá', + '抹澡' => ' mā zǎo', + '马杆' => ' mǎ gǎn', + '蚕子' => ' cán zǐ', + '麻杆' => ' má gǎn', + '滀仕' => ' xù shì', + '刺溜' => ' cī liū', + '雏子' => ' chú zǐ', + '落发' => ' luò fà', + '憨子' => ' hān zǐ', + '掠地' => ' lüě dì', + '恺撒' => ' kǎi sǎ', + '恺弟' => ' kǎi tì', + '处子' => ' chǔ zǐ', + '得对' => ' de duì', + '地处' => ' dì chǔ', + '咋舌' => ' zé shé', + '恨恶' => ' hèn wù', + '贝子' => ' bèi zǐ', + '褙子' => ' bèi zǐ', + '背子' => ' bēi zi', + '锛子' => ' bēn zǐ', + '北曲' => ' běi qǔ', + '底处' => ' dǐ chǔ', + '车仆' => ' chē pú', + '喜帖' => ' xǐ tiě', + '大说' => ' dà yuè', + '答允' => ' dā yǔn', + '呵禁' => ' hē jìn', + '待得' => ' dài de', + '赕佛' => ' dǎn fó', + '恁么' => ' rèn me', + '急难' => ' jí nàn', + '恰似' => ' qià sì', + '蛋子' => ' dàn zǐ', + '刀把' => ' dāo bà', + '回鹘' => ' huí hú', + '因子' => ' yīn zǐ', + '到得' => ' dào de', + '捏脊' => ' niē jǐ', + '贝勒' => ' bèi lè', + '囤积' => ' tún jī', + '喷撒' => ' pēn sǎ', + '挨宰' => ' ái zǎi', + '和洽' => ' hé qià', + '芘芣' => ' pí fǒu', + '和牌' => ' hú pái', + '咋呼' => ' zhā hū', + '挨克' => ' ái kēi', + '膍胵' => ' pí zhì', + '瘪子' => ' biě zǐ', + '别子' => ' bié zǐ', + '胚子' => ' pēi zǐ', + '挨斗' => ' ái dòu', + '咔嚓' => ' kā chā', + '僻处' => ' pì chǔ', + '呢喃' => ' ní nán', + '鼻血' => ' bí xiě', + '呼号' => ' hū háo', + '保子' => ' bǎo zǐ', + '挨延' => ' ái yán', + '呵叱' => ' hē chì', + '舍得' => ' shè de', + '呵斥' => ' hē chì', + '倒底' => ' dǎo dǐ', + '回纥' => ' huí hé', + '地窨' => ' dì yìn', + '叨咕' => ' dáo gu', + '得时' => ' de shí', + '肋膜' => ' lèi mó', + '勒掯' => ' lè kèn', + '得使' => ' de shǐ', + '累及' => ' lěi jí', + '累次' => ' lěi cì', + '勒石' => ' lè shí', + '勒派' => ' lè pài', + '埋没' => ' mái mò', + '坎子' => ' kǎn zǐ', + '檑木' => ' lèi mù', + '得来' => ' de lái', + '必得' => ' bì děi', + '德干' => ' dé gān', + '土著' => ' tǔ zhù', + '礼乐' => ' lǐ yuè', + '倒牙' => ' dǎo yá', + '在乎' => ' zài hu', + '坋粒' => ' fèn lì', + '礼帖' => ' lǐ tiě', + '道子' => ' dào zǐ', + '肋木' => ' lèi mù', + '得这' => ' de zhè', + '倒阁' => ' dǎo gé', + '吓唬' => ' xià hu', + '淋滤' => ' lìn lǜ', + '大溜' => ' dà liù', + '遛马' => ' liù mǎ', + '六路' => ' liù lù', + '锉子' => ' cuò zǐ', + '瘩背' => ' dá bèi', + '答茬' => ' dā chá', + '六个' => ' liù ge', + '大落' => ' dà luō', + '噶厦' => ' gá xià', + '大哗' => ' dà huá', + '得月' => ' de yuè', + '大尽' => ' dà jìn', + '檩子' => ' lǐn zǐ', + '悉数' => ' xī shǔ', + '打擂' => ' dǎ lèi', + '大斗' => ' dà dǒu', + '矬子' => ' cuó zǐ', + '国都' => ' guó dū', + '得数' => ' de shù', + '得位' => ' de wèi', + '坨子' => ' tuó zǐ', + '会得' => ' huì de', + '子孙' => ' zǐ sūn', + '折辱' => ' shé rǔ', + '叔子' => ' shū zǐ', + '铁耙' => ' tiě pá', + '谩骂' => ' màn mà', + '识得' => ' shí de', + '馓子' => ' sǎn zǐ', + '余干' => ' yú gān', + '余切' => ' yú qiē', + '饿莩' => ' è piǎo', + '誉为' => ' yù wéi', + '出没' => ' chū mò', + '责难' => ' zé nàn', + '贩子' => ' fàn zǐ', + '食肆' => ' shí sì', + '类似' => ' lèi sì', + '击倒' => ' jī dǎo', + '秋子' => ' qiū zǐ', + '球子' => ' qiú zǐ', + '豁达' => ' huò dá', + '曲包' => ' qǔ bāo', + '颓靡' => ' tuí mǐ', + '豆皀' => ' dòu bī', + '曲池' => ' qǔ chí', + '谜儿' => ' mèi ér', + '贴吧' => ' tiē bā', + '出落' => ' chū là', + '参与' => ' cān yù', + '赤子' => ' chì zǐ', + '曲牌' => ' qǔ pái', + '书脊' => ' shū jǐ', + '走子' => ' zǒu zǐ', + '头儿' => ' tou er', + '颇为' => ' pō wéi', + '曲式' => ' qǔ shì', + '担搁' => ' dān ge', + '贲临' => ' bì lín', + '担荷' => ' dān hè', + '参差' => ' cēn cī', + '树子' => ' shù zǐ', + '擘划' => ' bò huà', + '擘画' => ' bò huà', + '擘开' => ' bò kāi', + '屈折' => ' qū shé', + '跂坐' => ' qì zuò', + '擦干' => ' cā gān', + '摆子' => ' bǎi zǐ', + '鞋子' => ' xié zǐ', + '曲坛' => ' qǔ tán', + '貌似' => ' mào sì', + '扑倒' => ' pū dǎo', + '担子' => ' dàn zi', + '厌恶' => ' yàn wù', + '陪都' => ' péi dū', + '切腹' => ' qiē fù', + '印子' => ' yìn zǐ', + '即为' => ' jí wéi', + '踏实' => ' tā shi', + '踏查' => ' tà zhā', + '厌薄' => ' yàn bó', + '踶跂' => ' dì zhī', + '群子' => ' qún zǐ', + '蹦儿' => ' bèng r', + '若夫' => ' ruò fú', + '切入' => ' qiē rù', + '热和' => ' rè huo', + '摊薄' => ' tān bó', + '轧机' => ' zhá jī', + '陶子' => ' táo zǐ', + '热轧' => ' rè zhá', + '切割' => ' qiē gē', + '露底' => ' lòu dǐ', + '切勿' => ' qiē wù', + '露富' => ' lòu fù', + '划子' => ' huá zǐ', + '散发' => ' sàn fà', + '除子' => ' chú zǐ', + '卡子' => ' qiǎ zi', + '踅摸' => ' xué mo', + '面儿' => ' miàn r', + '载入' => ' zǎi rù', + '载籍' => ' zǎi jí', + '鸡肋' => ' jī lèi', + '散居' => ' sǎn jū', + '隽誉' => ' jùn yù', + '载荷' => ' zài hè', + '隽语' => ' jùn yǔ', + '儒艮' => ' rú gèn', + '近似' => ' jìn sì', + '雅乐' => ' yǎ yuè', + '雅什' => ' yǎ shí', + '隐没' => ' yǐn mò', + '日晕' => ' rì yùn', + '切牙' => ' qiē yá', + '轗轲' => ' kǎn kě', + '散曲' => ' sǎn qǔ', + '辟邪' => ' bì xié', + '贪得' => ' tān de', + '乳臭' => ' rǔ xiù', + '质子' => ' zhì zǐ', + '头发' => ' tóu fà', + '任子' => ' rèn zǐ', + '阿盟' => ' a méng', + '划拉' => ' huá lā', + '这末' => ' zhè me', + '阿明' => ' a míng', + '穗子' => ' suì zǐ', + '这麽' => ' zhè me', + '卜问' => ' bǔ wèn', + '卜卦' => ' bǔ guà', + '阿莲' => ' a lián', + '阿胶' => ' ē jiāo', + '博白' => ' bó bái', + '锁子' => ' suǒ zǐ', + '色晕' => ' sè yùn', + '逮捕' => ' dài bǔ', + '杉木' => ' shā mù', + '痧子' => ' shā zǐ', + '三曲' => ' sān qǔ', + '胎发' => ' tāi fà', + '斗笠' => ' dǒu lì', + '勾勒' => ' gōu lè', + '削发' => ' xuē fà', + '配子' => ' pèi zǐ', + '散记' => ' sǎn jì', + '索子' => ' suǒ zǐ', + '铺排' => ' pū pái', + '包扎' => ' bāo zā', + '俗乐' => ' sú yuè', + '违拗' => ' wéi ào', + '递解' => ' dì jiè', + '区处' => ' qū chǔ', + '数伏' => ' shǔ fú', + '化子' => ' huā zi', + '文蛤' => ' wén gé', + '包袱' => ' bāo fú', + '遗著' => ' yí zhù', + '刨子' => ' bào zǐ', + '三个' => ' sān ge', + '避难' => ' bì nàn', + '门子' => ' mén zǐ', + '斗大' => ' dǒu dà', + '阿扁' => ' a biǎn', + '散体' => ' sǎn tǐ', + '卜筮' => ' bǔ shì', + '嗒然' => ' tà rán', + '阘懦' => ' tà nuò', + '卜甲' => ' bǔ jiǎ', + '他处' => ' tā chǔ', + '铺摆' => ' pū bǎi', + '食邑' => ' shí yì', + '卖卜' => ' mài bǔ', + '日头' => ' rì tou', + '那是' => ' nà shi', + '那知' => ' nǎ zhī', + '旄期' => ' mào qī', + '旅舍' => ' lǚ shè', + '孕吐' => ' yùn tù', + '什物' => ' shí wù', + '是得' => ' shì de', + '石堤' => ' shí dī', + '都匀' => ' dū yún', + '识记' => ' zhì jì', + '凿子' => ' záo zǐ', + '錾子' => ' zàn zǐ', + '都尉' => ' dū wèi', + '镏子' => ' liù zǐ', + '都市' => ' dū shì', + '铺轨' => ' pū guǐ', + '铺设' => ' pū shè', + '部分' => ' bù fèn', + '贴子' => ' tiē zǐ', + '撒施' => ' sǎ shī', + '撒花' => ' sǎ huā', + '旄倪' => ' mào ní', + '勒索' => ' lè suǒ', + '配给' => ' pèi jǐ', + '昧没' => ' mèi mò', + '石子' => ' shí zǐ', + '酒吧' => ' jiǔ bā', + '世子' => ' shì zǐ', + '释子' => ' shì zǐ', + '刹那' => ' chà nà', + '剥皮' => ' bāo pí', + '铺摊' => ' pū tan', + '金发' => ' jīn fà', + '劈叉' => ' pǐ chà', + '划破' => ' huá pò', + '黍子' => ' shǔ zǐ', + '新都' => ' xīn dū', + '竖子' => ' shù zǐ', + '晏子' => ' yàn zǐ', + '普查' => ' pǔ zhā', + '晕乎' => ' yūn hu', + '晕糊' => ' yūn hu', + '力拓' => ' lì tuò', + '哀切' => ' āi qiē', + '都兰' => ' dū lán', + '铺盖' => ' pū gài', + '肆掠' => ' sì lüě', + '厚薄' => ' hòu bó', + '雹子' => ' báo zǐ', + '子规' => ' zǐ guī', + '兜子' => ' dōu zǐ', + '多得' => ' duō de', + '都试' => ' dū shì', + '铐子' => ' kào zǐ', + '科处' => ' kē chǔ', + '压板' => ' yà bǎn', + '都下' => ' dū xià', + '咳血' => ' ké xuè', + '压根' => ' yà gēn', + '客舍' => ' kè shè', + '尻子' => ' kāo zǐ', + '多子' => ' duō zǐ', + '斗子' => ' dǒu zi', + '篼子' => ' dōu zǐ', + '坷坎' => ' kē kǎn', + '士子' => ' shì zǐ', + '克尽' => ' kè jìn', + '刻划' => ' kè huá', + '壬子' => ' rén zǐ', + '痘子' => ' dòu zǐ', + '报子' => ' bào zǐ', + '拂过' => ' bì guò', + '恶寒' => ' wù hán', + '咯血' => ' kǎ xiě', + '国子' => ' guó zǐ', + '斗渠' => ' dǒu qú', + '堤围' => ' dī wéi', + '落儿' => ' lào ér', + '得很' => ' de hěn', + '塌实' => ' tā shi', + '塔刹' => ' tǎ chà', + '来子' => ' lái zǐ', + '赖子' => ' lài zǐ', + '老佛' => ' lǎo fó', + '阿房' => ' ē páng', + '都头' => ' dū tóu', + '多哇' => ' duō wā', + '靠泊' => ' kào bó', + '引得' => ' yǐn de', + '引子' => ' yǐn zǐ', + '墩子' => ' dūn zǐ', + '多佛' => ' duō fó', + '看得' => ' kàn de', + '砘子' => ' dùn zǐ', + '靠得' => ' kào de', + '式子' => ' shì zǐ', + '多咱' => ' duō zá', + '诘责' => ' jié zé', + '系带' => ' jì dài', + '老仆' => ' lǎo pú', + '孽子' => ' niè zǐ', + '磙子' => ' gǔn zǐ', + '极尽' => ' jí jìn', + '射干' => ' yè gān', + '急煞' => ' jí shà', + '会子' => ' huì zǐ', + '毫子' => ' háo zǐ', + '很子' => ' hěn zǐ', + '子爵' => ' zǐ jué', + '安分' => ' ān fèn', + '学摸' => ' xué mo', + '桂子' => ' guì zǐ', + '学子' => ' xué zǐ', + '子群' => ' zǐ qún', + '子民' => ' zǐ mín', + '侯波' => ' hòu bō', + '厚朴' => ' hòu pò', + '季肋' => ' jì lèi', + '子音' => ' zǐ yīn', + '胡为' => ' hú wéi', + '子鼠' => ' zǐ shǔ', + '字帖' => ' zì tiè', + '辊子' => ' gǔn zǐ', + '脊檩' => ' jǐ lǐn', + '极处' => ' jí chǔ', + '浑似' => ' hún sì', + '馃子' => ' guǒ zǐ', + '哈什' => ' hà shí', + '海曲' => ' hǎi qǔ', + '宝坻' => ' bǎo dǐ', + '扞格' => ' hàn gé', + '镬子' => ' huò zǐ', + '耠子' => ' huō zǐ', + '豁子' => ' huō zǐ', + '娄子' => ' lóu zǐ', + '好古' => ' hào gǔ', + '喊呀' => ' hǎn yā', + '贵子' => ' guì zǐ', + '子代' => ' zǐ dài', + '威吓' => ' wēi hè', + '委靡' => ' wěi mǐ', + '对子' => ' duì zǐ', + '寻的' => ' xún dì', + '妗子' => ' jìn zǐ', + '寻死' => ' xín sǐ', + '尉迟' => ' yù chí', + '妹子' => ' mèi zǐ', + '姥爷' => ' lǎo ye', + '莱塞' => ' lái sè', + '老区' => ' lǎo ōu', + '体查' => ' tǐ zhā', + '法帖' => ' fǎ tiè', + '庶几' => ' shù jī', + '庶子' => ' shù zǐ', + '二舍' => ' èr shè', + '发鬓' => ' fà bìn', + '座子' => ' zuò zǐ', + '几微' => ' jī wēi', + '倔巴' => ' juè bā', + '度假' => ' dù jià', + '庇荫' => ' bì yìn', + '发噱' => ' fā xué', + '几至' => ' jī zhì', + '几近' => ' jī jìn', + '发指' => ' fà zhǐ', + '夥计' => ' huǒ ji', + '蹶子' => ' jué zǐ', + '九曲' => ' jiǔ qǔ', + '分得' => ' fēn de', + '金铺' => ' jīn pū', + '太仆' => ' tài pú', + '佛头' => ' fó tóu', + '太子' => ' tài zǐ', + '橛子' => ' jué zǐ', + '干吗' => ' gàn má', + '佛心' => ' fó xīn', + '大夫' => ' dài fū', + '切尼' => ' qiē ní', + '难得' => ' nán de', + '散剂' => ' sǎn jì', + '忍得' => ' rěn de', + '叉子' => ' chā zǐ', + '读为' => ' dú wéi', + '安处' => ' ān chǔ', + '幡子' => ' fān zǐ', + '干邑' => ' gān yì', + '柜柳' => ' jǔ liǔ', + '榧子' => ' fěi zǐ', + '菲薄' => ' fěi bó', + '凡子' => ' fán zǐ', + '干与' => ' gān yù', + '舅子' => ' jiù zǐ', + '干戈' => ' gān gē', + '大为' => ' dà wéi', + '菲仪' => ' fěi yí', + '干系' => ' gān xì', + '飞子' => ' fēi zǐ', + '九子' => ' jiǔ zǐ', + '进给' => ' jìn jǐ', + '佛印' => ' fó yìn', + '得要' => ' de yào', + '弹子' => ' dàn zǐ', + '盔子' => ' kuī zǐ', + '铫子' => ' yáo zǐ', + '弹词' => ' tán cí', + '塞语' => ' sài yǔ', + '塞音' => ' sè yīn', + '弹牙' => ' tán yá', + '弯子' => ' wān zǐ', + '胯子' => ' kuà zǐ', + '墨斗' => ' mò dǒu', + '弹压' => ' tán yā', + '杰子' => ' jié zǐ', + '豆秸' => ' dòu jí', + '压倒' => ' yā dǎo', + '弹劾' => ' tán hé', + '弹拨' => ' tán bō', + '弹力' => ' tán lì', + '影儿' => ' yǐng r', + '癞子' => ' lài zǐ', + '复查' => ' fù zhā', + '低徊' => ' dī huí', + '地煞' => ' dì shà', + '厨子' => ' chú zǐ', + '巴斗' => ' bā dǒu', + '佛事' => ' fó shì', + '纷沓' => ' fēn tà', + '佛珠' => ' fó zhū', + '褯子' => ' jiè zǐ', + '节子' => ' jiē zǐ', + '疖子' => ' jiē zǐ', + '芥子' => ' jiè zǐ', + '街子' => ' jiē zǐ', + '佛座' => ' fó zuò', + '幔子' => ' màn zǐ', + '佛人' => ' fó rén', + '佛老' => ' fó lǎo', + '师傅' => ' shī fū', + '佛界' => ' fó jiè', + '帖子' => ' tiě zǐ', + '大只' => ' dà zhī', + '大难' => ' dà nàn', + '禁曲' => ' jìn qǔ', + '布什' => ' bù shí', + '禁子' => ' jìn zǐ', + '天儿' => ' tiān r', + '佛宝' => ' fó bǎo', + '佛国' => ' fó guó', + '佛化' => ' fó huà', + '间隙' => ' jiàn xì', + '圆晕' => ' yuán yùn', + '沈思' => ' chén sī', + '惩处' => ' chéng chǔ', + '手缝' => ' shǒu féng', + '蒙哄' => ' méng hǒng', + '手卷' => ' shǒu juàn', + '上分' => ' shàng fèn', + '靓妹' => ' liàng mèi', + '上切' => ' shàng qiē', + '升斗' => ' shēng dǒu', + '门框' => ' mén kuàng', + '重头' => ' chóng tóu', + '丧葬' => ' sāng zàng', + '阮元' => ' ruǎn yuán', + '应点' => ' yìng diǎn', + '长号' => ' cháng hào', + '应酬' => ' yìng chóu', + '投中' => ' tóu zhòng', + '长门' => ' cháng mén', + '音长' => ' yīn cháng', + '长处' => ' cháng chù', + '长眉' => ' cháng méi', + '沈朴' => ' shěn piáo', + '靓仔' => ' liàng zǎi', + '找着' => ' zhǎo zháo', + '长毛' => ' cháng máo', + '阆苑' => ' làng yuàn', + '白相' => ' bái xiàng', + '重获' => ' chóng huò', + '耍横' => ' shuǎ hèng', + '重版' => ' chóng bǎn', + '丧棒' => ' sāng bàng', + '重出' => ' chóng chū', + '绕梁' => ' rǎo liáng', + '少康' => ' shào kāng', + '穰穰' => ' rǎng rǎng', + '闷声' => ' mēn shēng', + '杓棒' => ' sháo bàng', + '间苗' => ' jiàn miáo', + '扁舟' => ' piān zhōu', + '间断' => ' jiàn duàn', + '少小' => ' shào xiǎo', + '烧着' => ' shāo zháo', + '闲空' => ' xián kòng', + '戴上' => ' dài shang', + '长排' => ' cháng pái', + '扇动' => ' shān dòng', + '长牌' => ' cháng pái', + '应变' => ' yìng biàn', + '长舌' => ' cháng shé', + '吵吵' => ' chāo chao', + '降妖' => ' xiáng yāo', + '扮相' => ' bàn xiàng', + '长醉' => ' cháng zuì', + '朝朝' => ' zhāo zhāo', + '雕琢' => ' diāo zhuó', + '南长' => ' nán cháng', + '双子' => ' shuāng zǐ', + '长随' => ' cháng suí', + '长夏' => ' cháng xià', + '三率' => ' sān shuài', + '长休' => ' cháng xiū', + '长头' => ' cháng tóu', + '三乘' => ' sān shèng', + '长心' => ' cháng xīn', + '长信' => ' cháng xìn', + '长音' => ' cháng yīn', + '长吟' => ' cháng yín', + '长云' => ' cháng yún', + '盛载' => ' shèng zǎi', + '盛满' => ' chéng mǎn', + '效应' => ' xiào yìng', + '长蛇' => ' cháng shé', + '长直' => ' cháng zhí', + '手钻' => ' shǒu zuàn', + '长至' => ' cháng zhì', + '摆晃' => ' bǎi huàng', + '应诏' => ' yìng zhào', + '重写' => ' chóng xiě', + '长袍' => ' cháng páo', + '长袖' => ' cháng xiù', + '舍长' => ' shè cháng', + '重来' => ' chóng lái', + '长靴' => ' cháng xuē', + '长队' => ' cháng duì', + '长逝' => ' cháng shì', + '重数' => ' chóng shù', + '青藏' => ' qīng zàng', + '重拍' => ' chóng pāi', + '深更' => ' shēn gēng', + '成分' => ' chéng fèn', + '长跑' => ' cháng pǎo', + '挑动' => ' tiǎo dòng', + '长谈' => ' cháng tán', + '长诗' => ' cháng shī', + '长裙' => ' cháng qún', + '成为' => ' chéng wéi', + '应诊' => ' yìng zhěn', + '哄骗' => ' hǒng piàn', + '切向' => ' qiē xiàng', + '电钻' => ' diàn zuàn', + '调卷' => ' diào juàn', + '摒挡' => ' bìng dàng', + '调调' => ' tiáo diào', + '调档' => ' tiáo dàng', + '挑灯' => ' tiǎo dēng', + '调料' => ' tiáo liào', + '调弄' => ' tiáo nòng', + '调教' => ' tiáo jiào', + '调挡' => ' tiáo dǎng', + '刁横' => ' diāo hèng', + '棚圈' => ' péng juàn', + '调情' => ' tiáo qíng', + '调控' => ' tiáo kòng', + '喷香' => ' pèn xiāng', + '挑明' => ' tiǎo míng', + '援藏' => ' yuán zàng', + '卖相' => ' mài xiàng', + '转差' => ' zhuǎn chā', + '转台' => ' zhuàn tái', + '换行' => ' huàn háng', + '更换' => ' gēng huàn', + '转位' => ' zhuàn wèi', + '调准' => ' tiáo zhǔn', + '冰凝' => ' bīng níng', + '轮种' => ' lún zhòng', + '曾参' => ' zēng shēn', + '碘酊' => ' diǎn dǐng', + '背筐' => ' bēi kuāng', + '贫相' => ' pín xiàng', + '琅孉' => ' láng huān', + '财相' => ' cái xiàng', + '调弦' => ' tiáo xián', + '振兴' => ' zhèn xīng', + '命名' => ' mìng míng', + '变更' => ' biàn gēng', + '兵差' => ' bīng chāi', + '调笑' => ' tiáo xiào', + '调焦' => ' tiáo jiāo', + '调停' => ' tiáo tíng', + '典当' => ' diǎn dàng', + '顶杠' => ' dǐng gàng', + '定更' => ' dìng gēng', + '曾巩' => ' zēng gǒng', + '挑战' => ' tiǎo zhàn', + '鼎兴' => ' dǐng xīng', + '挑弄' => ' tiǎo nòng', + '并州' => ' bīng zhōu', + '轮转' => ' lún zhuàn', + '插上' => ' chā shang', + '拙朴' => ' zhuō piáo', + '楞楞' => ' lèng lèng', + '掉色' => ' diào shǎi', + '天监' => ' tiān jiàn', + '刨光' => ' bào guāng', + '跳行' => ' tiào háng', + '走相' => ' zǒu xiàng', + '赚头' => ' zhuàn tou', + '账载' => ' zhàng zǎi', + '评卷' => ' píng juàn', + '平巷' => ' píng hàng', + '分量' => ' fèn liàng', + '哄劝' => ' hǒng quàn', + '接种' => ' jiē zhòng', + '白镪' => ' bái qiǎng', + '坦率' => ' tǎn shuài', + '更深' => ' gēng shēn', + '切成' => ' qiē chéng', + '挣脱' => ' zhèng tuō', + '挣扎' => ' zhēng zhá', + '切中' => ' qiè zhòng', + '轩槛' => ' xuān jiàn', + '百中' => ' bǎi zhòng', + '天应' => ' tiān yìng', + '分相' => ' fēn xiàng', + '重奏' => ' chóng zòu', + '拖长' => ' tuō cháng', + '镗孔' => ' táng kǒng', + '糖色' => ' táng shǎi', + '蛣蜣' => ' jié qiāng', + '转悠' => ' zhuàn yōu', + '转门' => ' zhuàn mén', + '转游' => ' zhuàn you', + '更动' => ' gēng dòng', + '转轮' => ' zhuàn lún', + '转载' => ' zhuǎn zǎi', + '更名' => ' gēng míng', + '招供' => ' zhāo gòng', + '转文' => ' zhuǎi wén', + '倒想' => ' dǎo xiǎng', + '倒箱' => ' dǎo xiāng', + '揣度' => ' chuǎi duó', + '转纽' => ' zhuàn nǐu', + '转筋' => ' zhuàn jīn', + '农行' => ' nóng háng', + '败将' => ' bài jiàng', + '叨光' => ' tāo guāng', + '蹭蹬' => ' cèng dèng', + '散光' => ' sǎn guāng', + '露相' => ' lòu xiàng', + '点着' => ' diǎn zháo', + '撩动' => ' liáo dòng', + '劲风' => ' jìng fēng', + '啜茗' => ' chuò míng', + '长褂' => ' cháng guà', + '旋工' => ' xuàn gōng', + '飞转' => ' fēi zhuàn', + '折衷' => ' shé zhōng', + '哮鸣' => ' xiào míng', + '劲挺' => ' jìng tǐng', + '长干' => ' cháng gàn', + '史乘' => ' shǐ shèng', + '春兴' => ' chūn xīng', + '劲峭' => ' jìng qiào', + '撬杠' => ' qiào gàng', + '应召' => ' yìng zhào', + '长恨' => ' cháng hèn', + '长尾' => ' cháng wěi', + '长存' => ' cháng cún', + '长多' => ' cháng duō', + '懂行' => ' dǒng háng', + '长叹' => ' cháng tàn', + '钻井' => ' zuàn jǐng', + '钟乐' => ' zhōng yuè', + '长跪' => ' cháng guì', + '折秤' => ' shé chèng', + '长男' => ' cháng nán', + '省方' => ' xǐng fāng', + '长街' => ' cháng jiē', + '长海' => ' cháng hǎi', + '长滨' => ' cháng bīn', + '单县' => ' shàn xiàn', + '长矛' => ' cháng máo', + '应用' => ' yìng yòng', + '参茸' => ' shēn róng', + '长白' => ' cháng bái', + '颈椎' => ' jǐng zhuī', + '长岛' => ' cháng dǎo', + '长流' => ' cháng liú', + '投降' => ' tóu xiáng', + '长班' => ' cháng bān', + '棽棽' => ' chēn chēn', + '长林' => ' cháng lín', + '应景' => ' yìng jǐng', + '长列' => ' cháng liè', + '长烟' => ' cháng yān', + '受禅' => ' shòu shàn', + '生角' => ' shēng jué', + '长假' => ' cháng jià', + '风钻' => ' fēng zuàn', + '长幼' => ' cháng yòu', + '粘着' => ' nián zhuó', + '悍将' => ' hàn jiàng', + '校订' => ' jiào dìng', + '缥囊' => ' piǎo náng', + '纤手' => ' qiàn shǒu', + '猜中' => ' cāi zhòng', + '宽甸' => ' kuān diàn', + '率然' => ' shuài rán', + '绗缝' => ' háng féng', + '桁杨' => ' háng yáng', + '射中' => ' shè zhòng', + '校准' => ' jiào zhǔn', + '奉还' => ' fèng huán', + '应典' => ' yìng diǎn', + '粘胶' => ' nián jiāo', + '交还' => ' jiāo huán', + '应供' => ' yìng gòng', + '粘粘' => ' nián nián', + '粘稠' => ' nián chóu', + '杆秤' => ' gǎn chèng', + '粘性' => ' nián xìng', + '粘缠' => ' nián chán', + '校监' => ' xiào jiàn', + '差商' => ' chā shāng', + '长活' => ' cháng huó', + '缝缀' => ' féng zhuì', + '长话' => ' cháng huà', + '慢长' => ' màn cháng', + '旋风' => ' xuàn fēng', + '长久' => ' cháng jiǔ', + '共工' => ' gòng gōng', + '戎行' => ' róng háng', + '杠杠' => ' gàng gàng', + '伄儅' => ' diào dāng', + '份量' => ' fèn liang', + '缅甸' => ' miǎn diàn', + '种菜' => ' zhòng cài', + '正旦' => ' zhēng dàn', + '干城' => ' gān chéng', + '率直' => ' shuài zhí', + '网杓' => ' wǎng sháo', + '正切' => ' zhèng qiē', + '经卷' => ' jīng juàn', + '栽种' => ' zāi zhòng', + '甚浓' => ' shèn nóng', + '正月' => ' zhēng yuè', + '缥渺' => ' piǎo miǎo', + '长杰' => ' cháng jié', + '身着' => ' shēn zhuó', + '城阙' => ' chéng què', + '量杯' => ' liáng bēi', + '重迭' => ' chóng dié', + '骗供' => ' piàn gòng', + '重蹈' => ' chóng dǎo', + '骄横' => ' jiāo hèng', + '重制' => ' chóng zhì', + '惊醒' => ' jīng xǐng', + '长材' => ' cháng cái', + '重返' => ' chóng fǎn', + '薯莨' => ' shǔ liáng', + '长刀' => ' cháng dāo', + '掸邦' => ' shàn bāng', + '世相' => ' shì xiàng', + '加长' => ' jiā cháng', + '铺床' => ' pū chuáng', + '明了' => ' míng liǎo', + '攒动' => ' cuán dòng', + '撩乱' => ' liáo luàn', + '馕嗓' => ' nǎng sǎng', + '香干' => ' xiāng gān', + '加上' => ' jiā shang', + '馕糠' => ' nǎng kāng', + '重述' => ' chóng shù', + '长车' => ' cháng chē', + '量规' => ' liáng guī', + '茸茸' => ' róng róng', + '称手' => ' chèn shǒu', + '尨茸' => ' méng róng', + '趁空' => ' chèn kòng', + '丧乱' => ' sāng luàn', + '卷轴' => ' juàn zhóu', + '称身' => ' chèn shēn', + '晨兴' => ' chén xīng', + '趁兴' => ' chèn xīng', + '扇风' => ' shān fēng', + '称愿' => ' chèn yuàn', + '重造' => ' chóng zào', + '钐镰' => ' shàn lián', + '卷宗' => ' juàn zōng', + '善卷' => ' shàn juàn', + '盛饭' => ' chéng fàn', + '山岗' => ' shān gāng', + '重婚' => ' chóng hūn', + '长石' => ' cháng shí', + '单相' => ' dān xiàng', + '重开' => ' chóng kāi', + '清供' => ' qīng gòng', + '想头' => ' xiǎng tou', + '隆兴' => ' lóng xīng', + '身著' => ' shēn zhuó', + '顺应' => ' shùn yìng', + '脏乱' => ' zāng luàn', + '晃悠' => ' huàng yōu', + '重温' => ' chóng wēn', + '嚷嚷' => ' rāng rɑng', + '划伤' => ' huá shāng', + '脏水' => ' zāng shuǐ', + '剿说' => ' chāo shuō', + '重楼' => ' chóng lóu', + '重演' => ' chóng yǎn', + '劲吹' => ' jìng chuī', + '重犯' => ' chóng fàn', + '应手' => ' yìng shǒu', + '应战' => ' yìng zhàn', + '长治' => ' cháng zhì', + '长沙' => ' cháng shā', + '应从' => ' yìng cóng', + '长俊' => ' cháng jùn', + '求降' => ' qiú xiáng', + '深省' => ' shēn xǐng', + '长泰' => ' cháng tài', + '长棍' => ' cháng gùn', + '重查' => ' chóng chá', + '悠长' => ' yōu cháng', + '寸长' => ' cùn cháng', + '重担' => ' zhòng dàn', + '龙兴' => ' lóng xīng', + '儱侗' => ' lǒng tǒng', + '重叠' => ' chóng dié', + '重瓣' => ' chóng bàn', + '脏病' => ' zāng bìng', + '重新' => ' chóng xīn', + '晕船' => ' yùn chuán', + '重文' => ' chóng wén', + '邻长' => ' lín cháng', + '重插' => ' chóng chā', + '长坂' => ' cháng bǎn', + '晕场' => ' yùn chǎng', + '脏脏' => ' zāng zāng', + '重拾' => ' chóng shí', + '重弹' => ' chóng tán', + '重孙' => ' chóng sūn', + '召陵' => ' shào líng', + '长才' => ' cháng cái', + '晾干' => ' liàng gān', + '重构' => ' chóng gòu', + '灵应' => ' líng yìng', + '漂漂' => ' piāo piāo', + '影片' => ' yǐng piān', + '精当' => ' jīng dàng', + '随兴' => ' suí xīng', + '消褪' => ' xiāo tùn', + '校书' => ' jiào shū', + '卷包' => ' juàn bāo', + '小舍' => ' xiǎo shè', + '卷丹' => ' juàn dān', + '圈肥' => ' juàn féi', + '圈舍' => ' juàn shè', + '划艇' => ' huá tǐng', + '卷柜' => ' juàn guì', + '划算' => ' huá suàn', + '倒换' => ' dǎo huàn', + '划拳' => ' huá quán', + '索还' => ' suǒ huán', + '小侯' => ' xiǎo hòu', + '销号' => ' xiāo hào', + '小斗' => ' xiǎo dǒu', + '小尽' => ' xiǎo jìn', + '丹参' => ' dān shēn', + '奔丧' => ' bēn sāng', + '曲张' => ' qǔ zhāng', + '两得' => ' liǎng de', + '连杆' => ' lián gǎn', + '参宿' => ' shēn xìu', + '切面' => ' qiē miàn', + '两都' => ' liǎng dū', + '像似' => ' xiàng sì', + '卷缩' => ' juàn suō', + '台钻' => ' tái zuàn', + '卷头' => ' juàn tóu', + '卷尾' => ' juàn wěi', + '朘削' => ' juān xuē', + '切点' => ' qiē diǎn', + '相如' => ' xiàng rú', + '太甚' => ' tài shèn', + '颉颃' => ' xié háng', + '降魔' => ' xiáng mó', + '切变' => ' qiē biàn', + '相里' => ' xiàng lǐ', + '相和' => ' xiāng hè', + '相得' => ' xiāng de', + '倒腾' => ' dǎo téng', + '切线' => ' qiē xiàn', + '在行' => ' zài háng', + '切痛' => ' qiē tòng', + '相马' => ' xiàng mǎ', + '执著' => ' zhí zhuó', + '昭著' => ' zhāo zhù', + '切片' => ' qiē piàn', + '原处' => ' yuán chǔ', + '中得' => ' zhōng de', + '著实' => ' zhuó shí', + '抢地' => ' qiāng dì', + '划行' => ' huá xíng', + '花朝' => ' huā zhāo', + '中意' => ' zhòng yì', + '三藏' => ' sān zàng', + '乾造' => ' qián zào', + '口供' => ' kǒu gòng', + '朝霞' => ' zhāo xiá', + '划水' => ' huá shuǐ', + '三通' => ' sān tòng', + '钻杆' => ' zuàn gǎn', + '黄子' => ' huáng zǐ', + '弄口' => ' lòng kǒu', + '受难' => ' shòu nàn', + '中子' => ' zhōng zǐ', + '染坊' => ' rǎn fáng', + '篇什' => ' piān shí', + '原著' => ' yuán zhù', + '子将' => ' zǐ jiāng', + '女将' => ' nǚ jiàng', + '群氓' => ' qún méng', + '周处' => ' zhōu chǔ', + '仔兽' => ' zǎi shòu', + '炔烃' => ' quē tīng', + '牛鞅' => ' niú yàng', + '并处' => ' bìng chǔ', + '评弹' => ' píng tán', + '折干' => ' shé qián', + '内省' => ' nèi xǐng', + '确当' => ' què dàng', + '朝云' => ' zhāo yún', + '折行' => ' shé xíng', + '牛圈' => ' niú juàn', + '属望' => ' zhǔ wàng', + '拧紧' => ' nǐng jǐn', + '吹干' => ' chuī gān', + '折减' => ' shé jiǎn', + '还丹' => ' huán dān', + '三更' => ' sān gēng', + '折受' => ' shé shòu', + '子姜' => ' zǐ jiāng', + '哄瞒' => ' hǒng mán', + '妥当' => ' tuǒ dàng', + '受累' => ' shòu lěi', + '初更' => ' chū gēng', + '前头' => ' qián tou', + '反间' => ' fǎn jiàn', + '肯綮' => ' kěn qìng', + '公石' => ' gōng dàn', + '偷空' => ' tōu kòng', + '冗散' => ' rǒng sǎn', + '筐子' => ' kuāng zǐ', + '拓宽' => ' tuò kuān', + '款识' => ' kuǎn zhì', + '拓边' => ' tuò biān', + '外应' => ' wài yìng', + '调休' => ' tiáo xiū', + '同好' => ' tóng hào', + '公假' => ' gōng jià', + '调治' => ' tiáo zhì', + '充塞' => ' chōng sè', + '兆头' => ' zhào tou', + '为甚' => ' wéi shèn', + '光子' => ' guāng zǐ', + '出差' => ' chū chāi', + '为善' => ' wéi shàn', + '出丧' => ' chū sāng', + '冷轧' => ' lěng zhá', + '来还' => ' lái huán', + '公斗' => ' gōng dǒu', + '铜臭' => ' tóng xiù', + '萎蔫' => ' wěi niān', + '开兴' => ' kāi xīng', + '假条' => ' jià tiáo', + '切块' => ' qiē kuài', + '乐工' => ' yuè gōng', + '累年' => ' lěi nián', + '偏差' => ' piān chā', + '埋怨' => ' mán yuàn', + '执着' => ' zhí zhuó', + '切削' => ' qiē xiāo', + '天阙' => ' tiān què', + '添头' => ' tiān tou', + '公仔' => ' gōng zǎi', + '累卵' => ' léi luǎn', + '瞎蒙' => ' xiā mēng', + '为限' => ' wéi xiàn', + '冠军' => ' guàn jūn', + '王舍' => ' wáng shè', + '阔少' => ' kuò shào', + '内蒙' => ' nèi měng', + '内行' => ' nèi háng', + '内哄' => ' nèi hòng', + '空载' => ' kōng zǎi', + '铁杠' => ' tiě gàng', + '亭侯' => ' tíng hòu', + '大王' => ' dài wáng', + '型芯' => ' xíng xìn', + '倒噍' => ' dǎo jiào', + '倒手' => ' dǎo shǒu', + '兴家' => ' xīng jiā', + '救应' => ' jiù yìng', + '供认' => ' gòng rèn', + '兴运' => ' xīng yùn', + '便人' => ' pián rén', + '剪切' => ' jiǎn qiē', + '兴修' => ' xīng xiū', + '率意' => ' shuài yì', + '因应' => ' yīn yìng', + '剥啄' => ' bāo zhuó', + '栓子' => ' shuān zǐ', + '便难' => ' biàn nàn', + '前燕' => ' qián yān', + '回佣' => ' huí yòng', + '前咽' => ' qián yān', + '兴叹' => ' xīng tàn', + '税卡' => ' shuì qiǎ', + '信差' => ' xìn chāi', + '削球' => ' xiāo qiú', + '卷柏' => ' juàn bǎi', + '倒扁' => ' dǎo biǎn', + '为命' => ' wéi mìng', + '回应' => ' huí yìng', + '为名' => ' wéi míng', + '课长' => ' kè cháng', + '同处' => ' tóng chǔ', + '为患' => ' wéi huàn', + '为情' => ' wéi qíng', + '精舍' => ' jīng shè', + '里长' => ' lǐ cháng', + '霰弹' => ' xiàn dàn', + '九钻' => ' jiǔ zuàn', + '说什' => ' shuō shí', + '刨冰' => ' bào bīng', + '杏干' => ' xìng gān', + '梁子' => ' liáng zǐ', + '兴废' => ' xīng fèi', + '倒仓' => ' dǎo cāng', + '大将' => ' dà jiàng', + '刺中' => ' cì zhòng', + '行处' => ' xíng chǔ', + '回还' => ' huí huán', + '倒嚼' => ' dǎo jiào', + '倒弄' => ' dǎo nòng', + '曲种' => ' qǔ zhǒng', + '黄柏' => ' huáng bò', + '铜钿' => ' tóng tián', + '吊杠' => ' diào gàng', + '刚劲' => ' gāng jìng', + '刷卷' => ' shuā juàn', + '同行' => ' tóng háng', + '重围' => ' chóng wéi', + '摇晃' => ' yáo huàng', + '重回' => ' chóng huí', + '重又' => ' chóng yòu', + '重印' => ' chóng yìn', + '重修' => ' chóng xiū', + '衰萎' => ' shuāi wěi', + '通红' => ' tòng hóng', + '太冲' => ' tài chòng', + '追还' => ' zhuī huán', + '吊卷' => ' diào juàn', + '俏销' => ' qiào xiāo', + '重九' => ' chóng jiǔ', + '党参' => ' dǎng shēn', + '靓白' => ' liàng bái', + '强人' => ' qiǎng rén', + '性行' => ' xìng xíng', + '单姓' => ' shàn xìng', + '当行' => ' dāng háng', + '乾龙' => ' qián lóng', + '膻中' => ' dàn zhōng', + '盅子' => ' zhōng zǐ', + '中牟' => ' zhōng mù', + '浑朴' => ' hún piáo', + '皮相' => ' pí xiàng', + '画片' => ' huà piān', + '中脊' => ' zhōng jǐ', + '豁朗' => ' huò lǎng', + '切手' => ' qiē shǒu', + '啁啾' => ' zhōu jiū', + '中计' => ' zhòng jì', + '儒将' => ' rú jiàng', + '划船' => ' huá chuán', + '中举' => ' zhòng jǔ', + '竹行' => ' zhú háng', + '裨将' => ' pí jiàng', + '作践' => ' zuó jiàn', + '惬当' => ' qiè dàng', + '昏蒙' => ' hūn mēng', + '划桨' => ' huá jiǎng', + '台长' => ' tái cháng', + '乾明' => ' qián míng', + '拓荒' => ' tuò huāng', + '两肋' => ' liǎng lèi', + '还债' => ' huán zhài', + '劈账' => ' pǐ zhàng', + '识相' => ' shí xiàng', + '别传' => ' bié zhuàn', + '难兄' => ' nàn xiōng', + '了断' => ' liǎo duàn', + '道长' => ' dào cháng', + '削尖' => ' xiāo jiān', + '男相' => ' nán xiàng', + '还手' => ' huán shǒu', + '占星' => ' zhān xīng', + '轰隆' => ' hōng lōng', + '便了' => ' biàn liǎo', + '摔倒' => ' shuāi dǎo', + '听差' => ' tīng chāi', + '咸兴' => ' xián xīng', + '往还' => ' wǎng huán', + '诸相' => ' zhū xiàng', + '诸将' => ' zhū jiàng', + '调养' => ' tiáo yǎng', + '凝冰' => ' níng bīng', + '彭彭' => ' bāng bāng', + '彷徉' => ' páng yáng', + '调羹' => ' tiáo gēng', + '运转' => ' yùn zhuàn', + '吃相' => ' chī xiàng', + '强切' => ' qiáng qiē', + '强留' => ' qiǎng liú', + '还原' => ' huán yuán', + '曹长' => ' cáo cháng', + '刨程' => ' bào chéng', + '还清' => ' huán qīng', + '钌铞' => ' liào diào', + '还愿' => ' huán yuàn', + '掺水' => ' chān shuǐ', + '当票' => ' dàng piào', + '当差' => ' dāng chāi', + '摊场' => ' tān cháng', + '内传' => ' nèi zhuàn', + '强颜' => ' qiǎng yán', + '黑框' => ' hēi kuàng', + '浅浅' => ' jiān jiān', + '乾乾' => ' qián qián', + '连翘' => ' lián qiáo', + '列传' => ' liè zhuàn', + '乾元' => ' qián yuán', + '酬应' => ' chóu yìng', + '酩酊' => ' mǐng dǐng', + '运将' => ' yùn jiàng', + '级长' => ' jí cháng', + '娘子' => ' niáng zǐ', + '吊斗' => ' diào dǒu', + '字长' => ' zì cháng', + '清隽' => ' qīng jùn', + '世行' => ' shì háng', + '还家' => ' huán jiā', + '受尽' => ' shòu jìn', + '木强' => ' mù jiàng', + '情切' => ' qíng qiē', + '千载' => ' qiān zǎi', + '能为' => ' néng wéi', + '颀长' => ' qí cháng', + '清切' => ' qīng qiē', + '商都' => ' shāng dū', + '受到' => ' shòu dào', + '只缘' => ' zhī yuán', + '轻载' => ' qīng zǎi', + '参校' => ' cān jiào', + '激将' => ' jī jiàng', + '正脊' => ' zhèng jǐ', + '子城' => ' zǐ chéng', + '只手' => ' zhī shǒu', + '黄发' => ' huáng fà', + '千石' => ' qiān dàn', + '曲中' => ' qǔ zhōng', + '商贾' => ' shāng gǔ', + '乾道' => ' qián dào', + '名角' => ' míng jué', + '反省' => ' fǎn xǐng', + '参薯' => ' shēn shǔ', + '始兴' => ' shǐ xīng', + '乞降' => ' qǐ xiáng', + '只从' => ' zhī cóng', + '值当' => ' zhí dàng', + '遒劲' => ' qiú jìng', + '唱曲' => ' chàng qǔ', + '曲梁' => ' qǔ liáng', + '指缝' => ' zhǐ féng', + '内应' => ' nèi yìng', + '只见' => ' zhī jiàn', + '名著' => ' míng zhù', + '还珠' => ' huán zhū', + '乾嘉' => ' qián jiā', + '唱喏' => ' chàng rě', + '唱和' => ' chàng hè', + '名为' => ' míng wéi', + '准头' => ' zhǔn tou', + '名帖' => ' míng tiě', + '反应' => ' fǎn yìng', + '子长' => ' zǐ cháng', + '钻修' => ' zuàn xiū', + '绘卷' => ' huì juàn', + '障子' => ' zhàng zǐ', + '入相' => ' rù xiàng', + '啜饮' => ' chuò yǐn', + '软和' => ' ruǎn huo', + '咳呛' => ' ké qiàng', + '掌子' => ' zhǎng zǐ', + '画供' => ' huà gòng', + '礃子' => ' zhǎng zǐ', + '哨卡' => ' shào qiǎ', + '獐子' => ' zhāng zǐ', + '作甚' => ' zuò shèn', + '作弄' => ' zuō nòng', + '和弄' => ' huò nòng', + '劈成' => ' pǐ chéng', + '妄为' => ' wàng wéi', + '咆哮' => ' páo xiào', + '钻台' => ' zuàn tái', + '猪圈' => ' zhū juàn', + '系绳' => ' jì shéng', + '钻饰' => ' zuàn shì', + '上去' => ' shǎng qù', + '上个' => ' shàng ge', + '难侨' => ' nàn qiáo', + '问卷' => ' wèn juàn', + '中魔' => ' zhòng mó', + '卷帙' => ' juàn zhì', + '钻凿' => ' zuàn záo', + '钻穴' => ' zuàn xué', + '竹杠' => ' zhú gàng', + '衷曲' => ' zhōng qǔ', + '杖子' => ' zhàng zǐ', + '卵泡' => ' luǎn pāo', + '俏头' => ' qiào tou', + '软肋' => ' ruǎn lèi', + '抨弹' => ' pēng tán', + '弱冠' => ' ruò guàn', + '火燎' => ' huǒ liǎo', + '长机' => ' cháng jī', + '切当' => ' qiē dāng', + '和面' => ' huó miàn', + '幛子' => ' zhàng zǐ', + '棚舍' => ' péng shè', + '南甸' => ' nán diàn', + '叩应' => ' kòu yìng', + '切用' => ' qiē yòng', + '中毒' => ' zhòng dú', + '柞绸' => ' zuò chóu', + '嚼用' => ' jiáo yòng', + '桑葚' => ' sāng shèn', + '肋条' => ' lèi tiáo', + '长籼' => ' cháng xiān', + '少成' => ' shào chéng', + '商参' => ' shāng shēn', + '种田' => ' zhòng tián', + '长顺' => ' cháng shùn', + '应承' => ' yìng chéng', + '长眠' => ' cháng mián', + '粘虫' => ' nián chóng', + '长短' => ' cháng duǎn', + '长篇' => ' cháng piān', + '长线' => ' cháng xiàn', + '少长' => ' shào zhǎng', + '长兴' => ' cháng xīng', + '长龙' => ' cháng lóng', + '长虹' => ' cháng hóng', + '长衫' => ' cháng shān', + '长丰' => ' cháng fēng', + '长远' => ' cháng yuǎn', + '长钉' => ' cháng dìng', + '粮行' => ' liáng háng', + '真相' => ' zhēn xiàng', + '少相' => ' shào xiāng', + '闲晃' => ' xián huàng', + '战将' => ' zhàn jiàng', + '转圈' => ' zhuàn quān', + '全长' => ' quán cháng', + '转行' => ' zhuǎn háng', + '调光' => ' tiáo guāng', + '较长' => ' jiào cháng', + '更正' => ' gēng zhèng', + '轻率' => ' qīng shuài', + '纤长' => ' xiān cháng', + '仙长' => ' xiān cháng', + '转动' => ' zhuàn dòng', + '更张' => ' gēng zhāng', + '重阳' => ' chóng yáng', + '倘佯' => ' cháng yáng', + '辗转' => ' zhǎn zhuǎn', + '偏将' => ' piān jiàng', + '天相' => ' tiān xiàng', + '更生' => ' gēng shēng', + '天将' => ' tiān jiàng', + '身长' => ' shēn cháng', + '藏象' => ' zàng xiàng', + '相间' => ' xiāng jiàn', + '胸椎' => ' xiōng zhuī', + '真率' => ' zhēn shuài', + '散装' => ' sǎn zhuāng', + '调整' => ' tiáo zhěng', + '统率' => ' tǒng shuài', + '长条' => ' cháng tiáo', + '经传' => ' jīng zhuàn', + '长拳' => ' cháng quán', + '长荣' => ' cháng róng', + '长汀' => ' cháng tīng', + '终了' => ' zhōng liǎo', + '长清' => ' cháng qīng', + '长漂' => ' cháng piāo', + '长片' => ' cháng piàn', + '勉强' => ' miǎn qiǎng', + '长春' => ' cháng chūn', + '长廊' => ' cháng láng', + '伸长' => ' shēn cháng', + '长凳' => ' cháng dèng', + '长垣' => ' cháng yuán', + '长庚' => ' cháng gēng', + '长寿' => ' cháng shòu', + '绷簧' => ' bēng huáng', + '长宁' => ' cháng níng', + '长岭' => ' cháng lǐng', + '长平' => ' cháng píng', + '长柄' => ' cháng bǐng', + '长效' => ' cháng xiào', + '院长' => ' yuàn cháng', + '镜框' => ' jìng kuàng', + '睡相' => ' shuì xiàng', + '干爽' => ' gān shuǎng', + '山长' => ' shān cháng', + '晒场' => ' shài cháng', + '重逢' => ' chóng féng', + '亭长' => ' tíng cháng', + '耕种' => ' gēng zhòng', + '镇长' => ' zhèn cháng', + '校场' => ' jiào chǎng', + '重霄' => ' chóng xiāo', + '校正' => ' jiào zhèng', + '星相' => ' xīng xiàng', + '量瓶' => ' liáng píng', + '量筒' => ' liáng tǒng', + '扬长' => ' yáng cháng', + '扬场' => ' yáng cháng', + '量身' => ' liáng shēn', + '收降' => ' shōu xiáng', + '手长' => ' shǒu cháng', + '量表' => ' liáng biǎo', + '参商' => ' shēn shāng', + '王长' => ' wáng cháng', + '冗长' => ' rǒng cháng', + '长空' => ' cháng kōng', + '相面' => ' xiàng miàn', + '受降' => ' shòu xiáng', + '猛将' => ' měng jiàng', + '率先' => ' shuài xiān', + '饼铛' => ' bǐng chēng', + '卿相' => ' qīng xiàng', + '请降' => ' qǐng xiáng', + '率领' => ' shuài lǐng', + '率真' => ' shuài zhēn', + '正着' => ' zhèng zháo', + '边框' => ' biān kuàng', + '牵强' => ' qiān qiǎng', + '边长' => ' biān cháng', + '芒种' => ' máng zhòng', + '色钟' => ' shǎi zhōng', + '说项' => ' shuì xiàng', + '色盅' => ' shǎi zhōng', + '前传' => ' qián zhuàn', + '丑相' => ' chǒu xiàng', + '还乡' => ' huán xiāng', + '着凉' => ' zháo liáng', + '着慌' => ' zháo huāng', + '首相' => ' shǒu xiàng', + '准将' => ' zhǔn jiàng', + '小将' => ' xiǎo jiàng', + '黄漂' => ' huáng piāo', + '千乘' => ' qiān shèng', + '点将' => ' diǎn jiàng', + '纤绳' => ' qiàn shéng', + '抢风' => ' qiāng fēng', + '黄兴' => ' huáng xīng', + '强笑' => ' qiǎng xiào', + '照相' => ' zhào xiàng', + '戗面' => ' qiàng miàn', + '上供' => ' shàng gòng', + '响应' => ' xiǎng yìng', + '水相' => ' shuǐ xiàng', + '涨红' => ' zhàng hóng', + '清长' => ' qīng cháng', + '擅长' => ' shàn cháng', + '生还' => ' shēng huán', + '挣钱' => ' zhèng qián', + '挣命' => ' zhèng mìng', + '评传' => ' píng zhuàn', + '挣断' => ' zhèng duàn', + '正朔' => ' zhēng shuò', + '当成' => ' dàng chéng', + '着想' => ' zhuó xiǎng', + '骁将' => ' xiāo jiàng', + '公转' => ' gōng zhuàn', + '通长' => ' tōng cháng', + '雄劲' => ' xióng jìng', + '重样' => ' chóng yàng', + '兴城' => ' xīng chéng', + '创口' => ' chuāng kǒu', + '重映' => ' chóng yìng', + '供称' => ' gòng chēng', + '降龙' => ' xiáng lóng', + '着重' => ' zhuó zhòng', + '表率' => ' biǎo shuài', + '衡量' => ' héng liáng', + '凶横' => ' xiōng hèng', + '行长' => ' háng zhǎng', + '挑中' => ' tiāo zhòng', + '行商' => ' háng shāng', + '变相' => ' biàn xiàng', + '元长' => ' yuán cháng', + '偿还' => ' cháng huán', + '贤相' => ' xián xiàng', + '赚哄' => ' zhuàn hǒng', + '贯串' => ' guàn chuàn', + '诚朴' => ' chéng piáo', + '重庆' => ' chóng qìng', + '重影' => ' chóng yǐng', + '还账' => ' huán zhàng', + '晃动' => ' huàng dòng', + '连长' => ' lián cháng', + '选中' => ' xuǎn zhòng', + '刨床' => ' bào chuáng', + '兴昌' => ' xīng chāng', + '箱笼' => ' xiāng lǒng', + '率性' => ' shuài xìng', + '兴衰' => ' xīng shuāi', + '悬揣' => ' xuán chuǎi', + '重洋' => ' chóng yáng', + '脸相' => ' liǎn xiàng', + '重建' => ' chóng jiàn', + '重现' => ' chóng xiàn', + '膀胱' => ' páng guāng', + '晃荡' => ' huàng dàng', + '重申' => ' chóng shēn', + '重算' => ' chóng suàn', + '重编' => ' chóng biān', + '重访' => ' chóng fǎng', + '重评' => ' chóng píng', + '兴盛' => ' xīng shèng', + '重审' => ' chóng shěn', + '长年' => ' cháng nián', + '绵长' => ' mián cháng', + '上当' => ' shàng dàng', + '相中' => ' xiāng zhòng', + '长成' => ' cháng chéng', + '长枪' => ' cháng qiāng', + '重整' => ' chóng zhěng', + '将将' => ' qiāng qiāng', + '穿上' => ' chuān shang', + '将相' => ' jiàng xiàng', + '倡狂' => ' chāng kuáng', + '着装' => ' zhuó zhuāng', + '相长' => ' xiāng cháng', + '着床' => ' zhuó chuáng', + '长相' => ' zhǎng xiàng', + '相率' => ' xiāng shuài', + '凶相' => ' xiōng xiàng', + '窘相' => ' jiǒng xiàng', + '两重' => ' liǎng chóng', + '良将' => ' liáng jiàng', + '供状' => ' gòng zhuàng', + '强征' => ' qiǎng zhēng', + '怔忪' => ' zhēng zhōng', + '怔怔' => ' zhèng zhèng', + '跄踉' => ' qiàng liàng', + '长江' => ' cháng jiāng', + '长生' => ' cháng shēng', + '踉蹡' => ' liàng qiāng', + '重重' => ' chóng chóng', + '旋床' => ' xuàn chuáng', + '创面' => ' chuāng miàn', + '长城' => ' cháng chéng', + '创兴' => ' chuàng xīng', + '生相' => ' shēng xiàng', + '亮相' => ' liàng xiàng', + '框框' => ' kuàng kuàng', + '铛铛' => ' chēng chēng', + '长帅' => ' cháng shuài', + '乡长' => ' xiāng cháng', + '掌相' => ' zhǎng xiàng', + '钻床' => ' zuàn chuáng', + '创痛' => ' chuāng tòng', + '长征' => ' cháng zhēng', + '更爽' => ' gēng shuǎng', + '称量' => ' chēng liáng', + '重光' => ' chóng guāng', + '重唱' => ' chóng chàng', + '上相' => ' shàng xiàng', + '汤汤' => ' shāng shāng', + '少壮' => ' shào zhuàng', + '踉跄' => ' liàng qiàng', + '征传' => ' zhēng zhuàn', + '绷床' => ' bēng chuáng', + '乘胜' => ' chéng shèng', + '将帅' => ' jiàng shuài', + '丈量' => ' zhàng liáng', + '中伤' => ' zhòng shāng', + '商量' => ' shāng liáng', + '正传' => ' zhèng zhuàn', + '上将' => ' shàng jiàng', + '泷水' => ' shuāng shuǐ', + '上声' => ' shǎng shēng', + '降将' => ' xiáng jiàng', + '政争' => ' zhèng zhēng', + '挣揣' => ' zhèng chuài', + '场长' => ' cháng zhǎng', + '靓装' => ' liàng zhuāng', + '闯将' => ' chuǎng jiàng', + '窗框' => ' chuāng kuàng', + '创伤' => ' chuāng shāng', + '重创' => ' zhòng chuāng', + '装相' => ' zhuāng xiàng', + '冲床' => ' chòng chuáng', + '双重' => ' shuāng chóng', + '间壁' => ' jiàn bì', + '长川' => ' cháng chuān', + '专长' => ' zhuān cháng', + '镗床' => ' táng chuáng', + '中奖' => ' zhòng jiǎng', + '相向' => ' xiāng xiàng', + '长长' => ' cháng cháng', + '种粮' => ' zhòng liáng', + '揣想' => ' chuǎi xiǎng', + '相像' => ' xiāng xiàng', + '中将' => ' zhōng jiàng', + '丞相' => ' chéng xiàng', + '相声' => ' xiàng sheng', + '相框' => ' xiàng kuàng', + '黄裳' => ' huáng cháng', + '中长' => ' zhōng cháng', + '长虫' => ' cháng chóng', + '双杠' => ' shuāng gàng', + '晃晃' => ' huàng huǎng', + '正长' => ' zhèng cháng', + '长程' => ' cháng chéng', + '抢种' => ' qiǎng zhòng', + '中枪' => ' zhòng qiāng', + '强抢' => ' qiáng qiǎng', + '强将' => ' qiáng jiàng', + '跄跄' => ' qiàng qiàng', + '量程' => ' liáng chéng', + '兵长' => ' bīng cháng', + '少将' => ' shào jiàng', + '长剑' => ' cháng jiàn', + '间种' => ' jiàn zhòng', + '重茧' => ' chóng jiǎn', + '监生' => ' jiàn shēng', + '重放' => ' chóng fàng', + '手相' => ' shǒu xiàng', + '健将' => ' jiàn jiàng', + '将校' => ' jiàng xiào', + '唱片' => ' chàng piān', + '丧荒' => ' sāng huāng', + '长红' => ' cháng hóng', + '乘间' => ' chéng jiàn', + '长工' => ' cháng gōng', + '长风' => ' cháng fēng', + '长方' => ' cháng fāng', + '长调' => ' cháng diào', + '厂甸' => ' chǎng diàn', + '商汤' => ' shāng tāng', + '哮喘' => ' xiào chuǎn', + '抽中' => ' chōu zhòng', + '长编' => ' cháng biān', + '长康' => ' cháng kāng', + '重趼' => ' chóng jiǎn', + '冲孔' => ' chòng kǒng', + '长勺' => ' cháng sháo', + '长啸' => ' cháng xiào', + '长洲' => ' cháng zhōu', + '长斋' => ' cháng zhāi', + '长圆' => ' cháng yuán', + '长缨' => ' cháng yīng', + '工长' => ' gōng cháng', + '长行' => ' cháng xíng', + '昌兴' => ' chāng xīng', + '长性' => ' cháng xìng', + '长笑' => ' cháng xiào', + '小传' => ' xiǎo zhuàn', + '成行' => ' chéng háng', + '长痛' => ' cháng tòng', + '长亭' => ' cháng tíng', + '长天' => ' cháng tiān', + '长松' => ' cháng sōng', + '盟长' => ' méng cháng', + '更香' => ' gēng xiāng', + '丧钟' => ' sāng zhōng', + '重瞳' => ' zhòng tóng', + '重山' => ' chóng shān', + '将令' => ' jiàng lìng', + '长身' => ' cháng shēn', + '长山' => ' cháng shān', + '悄声' => ' qiǎo shēng', + '峥巆' => ' zhēng yíng', + '彷徨' => ' páng huáng', + '点种' => ' diǎn zhòng', + '重圆' => ' chóng yuán', + '春种' => ' chūn zhòng', + '创痕' => ' chuāng hén', + '揣情' => ' chuǎi qíng', + '应征' => ' yìng zhēng', + '杆状' => ' gǎn zhuàng', + '川藏' => ' chuān zàng', + '莨绸' => ' liáng chóu', + '端量' => ' duān liáng', + '倒床' => ' dǎo chuáng', + '巷弄' => ' xiàng lòng', + '冯生' => ' píng shēng', + '警长' => ' jǐng cháng', + '疯长' => ' fēng cháng', + '天长' => ' tiān cháng', + '怪相' => ' guài xiàng', + '了账' => ' liǎo zhàng', + '囊膪' => ' nāng chuài', + '囊揣' => ' nāng chuài', + '鼎铛' => ' dǐng chēng', + '空转' => ' kōng zhuàn', + '光栅' => ' guāng shān', + '长陵' => ' cháng líng', + '专横' => ' zhuān hèng', + '长卷' => ' cháng juàn', + '长庆' => ' cháng qìng', + '长桥' => ' cháng qiáo', + '承应' => ' chéng yìng', + '将领' => ' jiàng lǐng', + '将官' => ' jiàng guān', + '长鸣' => ' cháng míng', + '长命' => ' cháng mìng', + '商行' => ' shāng háng', + '短长' => ' duǎn cháng', + '长青' => ' cháng qīng', + '黄钻' => ' huáng zuàn', + '重行' => ' chóng xíng', + '场院' => ' cháng yuàn', + '方框' => ' fāng kuàng', + '帐棚' => ' zhàng peng', + '连中' => ' lián zhòng', + '强横' => ' qiáng hèng', + '冲调' => ' chōng tiáo', + '强劲' => ' qiáng jìng', + '专差' => ' zhuān chāi', + '名将' => ' míng jiàng', + '饮场' => ' yìn chǎng', + '肇兴' => ' zhào xīng', + '矫情' => ' jiáo qíng', + '供桌' => ' gòng zhuō', + '杠荡' => ' gàng dàng', + '杠铃' => ' gàng líng', + '相干' => ' xiāng gān', + '瞭望' => ' liào wàng', + '枞阳' => ' zōng yáng', + '干重' => ' gān zhòng', + '秤杆' => ' chèng gǎn', + '疏率' => ' shū shuài', + '相士' => ' xiàng shì', + '应选' => ' yìng xuǎn', + '崆峒' => ' kōng tóng', + '筵上' => ' yán shǎng', + '将佐' => ' jiàng zuǒ', + '套种' => ' tào zhòng', + '当真' => ' dàng zhēn', + '槟榔' => ' bīng láng', + '应援' => ' yìng yuán', + '专著' => ' zhuān zhù', + '睡觉' => ' shuì jiào', + '相国' => ' xiàng guó', + '光晕' => ' guāng yùn', + '看相' => ' kàn xiàng', + '直率' => ' zhí shuài', + '拱券' => ' gǒng xuàn', + '长眼' => ' cháng yǎn', + '炸酱' => ' zhá jiàng', + '扎挣' => ' zhá zhēng', + '斋供' => ' zhāi gòng', + '张拓' => ' zhāng tuò', + '侧棱' => ' zhāi léng', + '睡着' => ' shuì zháo', + '看中' => ' kàn zhòng', + '干娘' => ' gān niáng', + '占梦' => ' zhān mèng', + '眄眄' => ' miàn miàn', + '久长' => ' jiǔ cháng', + '相纸' => ' xiàng zhǐ', + '相处' => ' xiāng chǔ', + '乐章' => ' yuè zhāng', + '少年' => ' shào nián', + '相架' => ' xiàng jià', + '瞭哨' => ' liào shào', + '相位' => ' xiàng wèi', + '奠定' => ' diàn dìng', + '正处' => ' zhèng chǔ', + '好胜' => ' hào shèng', + '广为' => ' guǎng wéi', + '了了' => ' liǎo liǎo', + '乾陵' => ' qián líng', + '称为' => ' chēng wéi', + '干姜' => ' gān jiāng', + '种花' => ' zhòng huā', + '横行' => ' héng xíng', + '种树' => ' zhòng shù', + '种植' => ' zhòng zhí', + '称钱' => ' chèn qián', + '椆苕' => ' diào tiáo', + '乾隆' => ' qián lóng', + '症结' => ' zhēng jié', + '广乐' => ' guǎng yuè', + '将门' => ' jiàng mén', + '了当' => ' liǎo dàng', + '屏营' => ' bīng yíng', + '光杆' => ' guāng gǎn', + '棕绷' => ' zōng bēng', + '穷尽' => ' qióng jìn', + '槟州' => ' bīng zhōu', + '检校' => ' jiǎn jiào', + '减削' => ' jiǎn xiāo', + '穹肋' => ' qíong lèi', + '痴长' => ' chī cháng', + '岭巆' => ' lǐng yíng', + '将牌' => ' jiàng pái', + '校雠' => ' jiào chóu', + '交卷' => ' jiāo juàn', + '校点' => ' jiào diǎn', + '应身' => ' yìng shēn', + '扛鼎' => ' gāng dǐng', + '笼罩' => ' lǒng zhào', + '杠房' => ' gàng fáng', + '交差' => ' jiāo chāi', + '归降' => ' guī xiáng', + '空调' => ' kōng tiáo', + '挣开' => ' zhèng kāi', + '笼槛' => ' lóng jiàn', + '应天' => ' yìng tiān', + '简朴' => ' jiǎn piáo', + '当天' => ' dàng tiān', + '笼统' => ' lǒng tǒng', + '更变' => ' gēng biàn', + '属相' => ' shǔ xiàng', + '将尉' => ' jiàng wèi', + '空闲' => ' kòng xián', + '乾县' => ' qián xiàn', + '中暑' => ' zhòng shǔ', + '兴建' => ' xīng jiàn', + '为生' => ' wéi shēng', + '晃着' => ' huàng zhe', + '兴平' => ' xīng píng', + '黄晕' => ' huáng yùn', + '兴山' => ' xīng shān', + '兴宁' => ' xīng níng', + '封禅' => ' fēng shàn', + '抓差' => ' zhuā chāi', + '兴亡' => ' xīng wáng', + '唪经' => ' běng jīng', + '为重' => ' wéi zhòng', + '照片' => ' zhào piān', + '兴兵' => ' xīng bīng', + '校样' => ' jiào yàng', + '好尚' => ' hào shàng', + '考量' => ' kǎo liáng', + '行款' => ' háng kuǎn', + '老将' => ' lǎo jiàng', + '央行' => ' yāng háng', + '种瓜' => ' zhòng guā', + '审校' => ' shěn jiào', + '种痘' => ' zhòng dòu', + '烹调' => ' pēng tiáo', + '佣钱' => ' yòng qián', + '行帮' => ' háng bāng', + '纯朴' => ' chún piáo', + '左传' => ' zuǒ zhuàn', + '工行' => ' gōng háng', + '绍兴' => ' shào xīng', + '框架' => ' kuàng jià', + '滚转' => ' gǔn zhuàn', + '用间' => ' yòng jiàn', + '缴卷' => ' jiǎo juàn', + '工坊' => ' gōng fáng', + '过长' => ' guò cháng', + '应龙' => ' yìng lóng', + '主将' => ' zhǔ jiàng', + '狭长' => ' xiá cháng', + '伙种' => ' huǒ zhòng', + '三重' => ' sān chóng', + '大黄' => ' dài huáng', + '供应' => ' gōng yìng', + '供奉' => ' gòng fèng', + '胶粘' => ' jiāo nián', + '金相' => ' jīn xiàng', + '照应' => ' zhào yìng', + '脑涨' => ' nǎo zhàng', + '翘棱' => ' qiáo lēng', + '翘望' => ' qiáo wàng', + '中彩' => ' zhòng cǎi', + '远兴' => ' yuǎn xīng', + '社长' => ' shè cháng', + '少校' => ' shào xiào', + '乐正' => ' yuè zhèng', + '供款' => ' gòng kuǎn', + '中邪' => ' zhòng xié', + '好强' => ' hào qiáng', + '供暖' => ' gòng nuǎn', + '中肯' => ' zhòng kěn', + '云裳' => ' yún cháng', + '更行' => ' gēng xíng', + '碾坊' => ' niǎn fáng', + '横杠' => ' héng gàng', + '更定' => ' gēng dìng', + '干粮' => ' gān liáng', + '更更' => ' gēng gēng', + '见年' => ' xiàn nián', + '原卷' => ' yuán juàn', + '枪杆' => ' qiāng gǎn', + '禅让' => ' shàn ràng', + '中弹' => ' zhòng dàn', + '尽忠' => ' jìn zhōng', + '少间' => ' shǎo jiàn', + '家种' => ' jiā zhòng', + '翘首' => ' qiáo shǒu', + '羊圈' => ' yáng juàn', + '上头' => ' shàng tou', + '实相' => ' shí xiàng', + '肖邦' => ' xiāo bāng', + '肖扬' => ' xiāo yáng', + '爪蟾' => ' zhuǎ chán', + '婵媛' => ' chán yuán', + '种牛' => ' zhòng niú', + '洋行' => ' yáng háng', + '片名' => ' piān míng', + '差遣' => ' chāi qiǎn', + '乐声' => ' yuè shēng', + '豁亮' => ' huò liàng', + '中舍' => ' zhōng shè', + '宁愿' => ' nìng yuàn', + '声乐' => ' shēng yuè', + '帑藏' => ' tǎng zàng', + '画框' => ' huà kuàng', + '将士' => ' jiàng shì', + '矿难' => ' kuàng nàn', + '越长' => ' yuè cháng', + '兼差' => ' jiān chāi', + '诱降' => ' yòu xiáng', + '胸闷' => ' xīong mēn', + '乾光' => ' qián guāng', + '拖拽' => ' tuō zhuài', + '转来' => ' zhuàn lái', + '炸两' => ' zhá liǎng', + '左强' => ' zuǒ jiàng', + '航行' => ' háng xíng', + '修长' => ' xiū cháng', + '垫圈' => ' diàn juàn', + '嘴长' => ' zuǐ cháng', + '炒更' => ' chǎo gēng', + '转为' => ' zhuǎn wéi', + '填空' => ' tián kòng', + '宰相' => ' zǎi xiàng', + '兴隆' => ' xīng lóng', + '兴坏' => ' xīng huài', + '话长' => ' huà cháng', + '详尽' => ' xiáng jìn', + '便嬛' => ' pián xuān', + '干证' => ' gān zhèng', + '萎黄' => ' wěi huáng', + '浆纸' => ' jiàng zhǐ', + '弹簧' => ' tán huáng', + '便便' => ' pián pián', + '兴讼' => ' xīng sòng', + '保长' => ' bǎo cháng', + '兴荣' => ' xīng róng', + '便溺' => ' biàn niào', + '倥侗' => ' kōng tóng', + '苍劲' => ' cāng jìng', + '年少' => ' nián shào', + '月相' => ' yuè xiàng', + '公差' => ' gōng chāi', + '兴庆' => ' xīng qìng', + '九转' => ' jiǔ zhuàn', + '公共' => ' gōng gòng', + '观塘' => ' guàn táng', + '船只' => ' chuán zhī', + '宫观' => ' gōng guàn', + '都长' => ' dōu cháng', + '钻钻' => ' zuàn zuàn', + '东兴' => ' dōng xīng', + '兴贤' => ' xīng xián', + '万乘' => ' wàn shèng', + '诈降' => ' zhà xiáng', + '和熊' => ' huó xióng', + '兴能' => ' xīng néng', + '洋壳' => ' yáng qiào', + '东渐' => ' dōng jiān', + '兴邦' => ' xīng bāng', + '芳甸' => ' fāng diàn', + '夏种' => ' xià zhòng', + '偏长' => ' piān cháng', + '中签' => ' zhòng qiān', + '中甸' => ' zhōng diàn', + '中标' => ' zhòng biāo', + '折床' => ' shé chuáng', + '中风' => ' zhòng fēng', + '串供' => ' chuàn gòng', + '相应' => ' xiāng yìng', + '攘场' => ' rǎng cháng', + '命中' => ' mìng zhòng', + '相公' => ' xiàng gōng', + '招降' => ' zhāo xiáng', + '深长' => ' shēn cháng', + '遣将' => ' qiǎn jiàng', + '戆直' => ' zhuàng zhí', + '营长' => ' yíng cháng', + '幢幡' => ' zhuàng fān', + '周正' => ' zhōu zhēng', + '相角' => ' xiàng jiǎo', + '撞倒' => ' zhuàng dǎo', + '中觉' => ' zhōng jiào', + '片长' => ' piàn cháng', + '中招' => ' zhòng zhāo', + '中兴' => ' zhōng xīng', + '涨满' => ' zhàng mǎn', + '状子' => ' zhuàng zǐ', + '倒帐' => ' dǎo zhàng', + '松茸' => ' sōng róng', + '兜率' => ' dōu shuài', + '兴行' => ' xīng xíng', + '会长' => ' huì cháng', + '弹唱' => ' tán chàng', + '桩子' => ' zhuāng zǐ', + '兴工' => ' xīng gōng', + '飞将' => ' fēi jiàng', + '兴县' => ' xīng xiàn', + '中选' => ' zhòng xuǎn', + '垦种' => ' kěn zhòng', + '试种' => ' shì zhòng', + '掂量' => ' diān liang', + '周长' => ' zhōu cháng', + '相片' => ' xiàng piān', + '相称' => ' xiāng chèn', + '劝降' => ' quàn xiáng', + '瘦长' => ' shòu cháng', + '长钱' => ' cháng qián', + '洋相' => ' yáng xiàng', + '钻工' => ' zuàn gōng', + '廊坊' => ' láng fáng', + '相貌' => ' xiàng mào', + '行间' => ' háng jiān', + '卷面' => ' juàn miàn', + '张角' => ' zhāng jué', + '强求' => ' qiǎng qiú', + '漫长' => ' màn cháng', + '屯邅' => ' zhūn zhān', + '斗转' => ' dǒu zhuǎn', + '幸甚' => ' xìng shèn', + '冲劲' => ' chòng jìn', + '草率' => ' cǎo shuài', + '吊丧' => ' diào sāng', + '还钱' => ' huán qián', + '行当' => ' háng dāng', + '卷帘' => ' juàn lián', + '漂零' => ' piāo líng', + '削面' => ' xiāo miàn', + '强嘴' => ' jiàng zuǐ', + '行情' => ' háng qíng', + '小卷' => ' xiǎo juàn', + '凶煞' => ' xiōng shà', + '朝阳' => ' zhāo yáng', + '还阳' => ' huán yáng', + '相切' => ' xiāng qiē', + '着边' => ' zhuó biān', + '漂荡' => ' piāo dàng', + '着手' => ' zhuó shǒu', + '小差' => ' xiǎo chāi', + '倒相' => ' dào xiàng', + '定兴' => ' dìng xīng', + '着忙' => ' zháo máng', + '海相' => ' hǎi xiàng', + '卷首' => ' juàn shǒu', + '定当' => ' dìng dàng', + '位相' => ' wèi xiàng', + '蟏蛸' => ' xiāo shāo', + '外传' => ' wài zhuàn', + '乡侯' => ' xiāng hòu', + '圈养' => ' juàn yǎng', + '张华' => ' zhāng huà', + '为政' => ' wéi zhèng', + '螵蛸' => ' piāo xiāo', + '康乾' => ' kāng qián', + '够戗' => ' gòu qiàng', + '著称' => ' zhù chēng', + '倒账' => ' dǎo zhàng', + '还童' => ' huán tóng', + '官差' => ' guān chāi', + '充分' => ' chōng fèn', + '卷卷' => ' juàn juàn', + '短供' => ' duǎn gòng', + '蒙山' => ' měng shān', + '钻攻' => ' zuàn gōng', + '下乘' => ' xià shèng', + '荫凉' => ' yìn liáng', + '傧相' => ' bīn xiàng', + '僮族' => ' zhuàng zú', + '本相' => ' běn xiàng', + '多重' => ' duō chóng', + '朝鲜' => ' cháo xiǎn', + '兴旺' => ' xīng wàng', + '蒙茸' => ' méng róng', + '看场' => ' kān chǎng', + '蜀相' => ' shǔ xiàng', + '荥经' => ' yíng jīng', + '鲜见' => ' xiǎn jiàn', + '倥偬' => ' kǒng zǒng', + '蒜茸' => ' suàn róng', + '盖上' => ' gài shang', + '滇藏' => ' diān zàng', + '沧浪' => ' cāng láng', + '蓬茸' => ' péng róng', + '守更' => ' shǒu gēng', + '浅鲜' => ' qiǎn xiǎn', + '传略' => ' zhuàn lüè', + '干将' => ' gàn jiàng', + '延长' => ' yán cháng', + '大城' => ' dài chéng', + '倔强' => ' jué jiàng', + '漂洋' => ' piāo yáng', + '沉着' => ' chén zhuó', + '萧乾' => ' xiāo qián', + '蚌山' => ' bèng shān', + '哗众' => ' huá zhòng', + '守丧' => ' shǒu sāng', + '嬛嬛' => ' xuān xuān', + '荐椎' => ' jiàn zhuī', + '淳朴' => ' chún piáo', + '永兴' => ' yǒng xīng', + '哄哄' => ' hǒng hǒng', + '建行' => ' jiàn háng', + '哄弄' => ' hǒng nòng', + '弄堂' => ' lòng táng', + '藏羚' => ' zàng líng', + '江干' => ' jiāng gān', + '藏青' => ' zàng qīng', + '传赞' => ' zhuàn zàn', + '弄脏' => ' nòng zāng', + '螣蛇' => ' téng shé', + '拽文' => ' zhuài wén', + '坤甸' => ' kūn diàn', + '崩倒' => ' bēng dǎo', + '供词' => ' gòng cí', + '飘泊' => ' piāo bó', + '朴雅' => ' piáo yǎ', + '散匪' => ' sǎn fěi', + '马圈' => ' mǎ juàn', + '口角' => ' kǒu jué', + '脊椎' => ' jǐ zhuī', + '兴安' => ' xīng ān', + '书卷' => ' shū juàn', + '撩人' => ' liáo rén', + '离间' => ' lí jiàn', + '燕赵' => ' yān zhào', + '背榜' => ' bēi bǎng', + '不揣' => ' bù chuǎi', + '为准' => ' wéi zhǔn', + '更阑' => ' gēng lán', + '馕糟' => ' nǎng zāo', + '笼络' => ' lǒng luò', + '案称' => ' àn chèng', + '为首' => ' wéi shǒu', + '移行' => ' yí háng', + '兴业' => ' xīng yè', + '跂想' => ' qǐ xiǎng', + '擂台' => ' lèi tái', + '羞臊' => ' xiū sào', + '掖庭' => ' yè tíng', + '一刬' => ' yī chàn', + '掖垣' => ' yè yuán', + '卵子' => ' luǎn zǐ', + '盐分' => ' yán fèn', + '修为' => ' xiū wéi', + '难弹' => ' nán tán', + '何曾' => ' hé zēng', + '风靡' => ' fēng mǐ', + '散架' => ' sǎn jià', + '雀子' => ' qiāo zǐ', + '撩拨' => ' liáo bō', + '首都' => ' shǒu dū', + '雅片' => ' yā piàn', + '养子' => ' yǎng zǐ', + '散文' => ' sǎn wén', + '起更' => ' qǐ gēng', + '推度' => ' tuī duó', + '择刺' => ' zhái cì', + '散射' => ' sǎn shè', + '感兴' => ' gǎn xīng', + '步长' => ' bù cháng', + '危难' => ' wēi nàn', + '拼攒' => ' pīn cuán', + '质朴' => ' zhì piáo', + '狼头' => ' láng tou', + '风镐' => ' fēng hào', + '风斗' => ' fēng dǒu', + '犍为' => ' qián wéi', + '武将' => ' wǔ jiàng', + '脱壳' => ' tuō qiào', + '拽步' => ' zhuài bù', + '本色' => ' běn shǎi', + '牵累' => ' qiān lěi', + '累坠' => ' léi zhuì', + '转距' => ' zhuàn jù', + '干甚' => ' gàn shèn', + '更番' => ' gēng fān', + '轧钢' => ' zhá gāng', + '体长' => ' tǐ cháng', + '粘滑' => ' nián huá', + '残卷' => ' cán juàn', + '煞神' => ' shà shén', + '粘菌' => ' nián jūn', + '撒种' => ' sǎ zhǒng', + '丰镐' => ' fēng hào', + '返还' => ' fǎn huán', + '辩难' => ' biàn nàn', + '比量' => ' bǐ liáng', + '高更' => ' gāo gēng', + '纳降' => ' nà xiáng', + '屏除' => ' bǐng chú', + '高挑' => ' gāo tiǎo', + '梗咽' => ' gěng yān', + '屏退' => ' bǐng tuì', + '追查' => ' zhuī zhā', + '撂倒' => ' liào dǎo', + '财会' => ' cái kuài', + '高着' => ' gāo zhāo', + '蒙文' => ' měng wén', + '细长' => ' xì cháng', + '退税' => ' tuì shuì', + '掺假' => ' chān jiǎ', + '退色' => ' tuì shǎi', + '箭杆' => ' jiàn gǎn', + '框图' => ' kuàng tú', + '框子' => ' kuàng zi', + '农舍' => ' nóng shè', + '累赘' => ' léi zhuì', + '山查' => ' shān zhā', + '散沙' => ' sǎn shā', + '压卷' => ' yā juàn', + '更新' => ' gēng xīn', + '撩惹' => ' liáo rě', + '背篓' => ' bēi lǒu', + '背包' => ' bēi bāo', + '背带' => ' bēi dài', + '瓤子' => ' ráng zǐ', + '自省' => ' zì xǐng', + '枝蔓' => ' zhī wàn', + '非分' => ' fēi fèn', + '魏巍' => ' wèi wēi', + '眼泡' => ' yǎn pāo', + '摒弃' => ' bìng qì', + '咽音' => ' yān yīn', + '杨子' => ' yáng zǐ', + '悄寂' => ' qiǎo jì', + '齿龈' => ' chǐ yín', + '阳子' => ' yáng zǐ', + '乾图' => ' qián tú', + '诮呵' => ' qiào hē', + '曲工' => ' qǔ gōng', + '丽水' => ' lí shuǐ', + '稽首' => ' qǐ shǒu', + '翘曲' => ' qiáo qū', + '龈炎' => ' yín yán', + '佛教' => ' fó jiào', + '脾脏' => ' pí zāng', + '羊子' => ' yáng zǐ', + '浅子' => ' qiǎn zǐ', + '撇嘴' => ' piě zuǐ', + '签子' => ' qiān zǐ', + '查探' => ' zhā tàn', + '发辫' => ' fà biàn', + '佛经' => ' fó jīng', + '胄子' => ' zhòu zǐ', + '选曲' => ' xuǎn qǔ', + '龈音' => ' yín yīn', + '来头' => ' lái tou', + '发钗' => ' fà chāi', + '玄子' => ' xuán zǐ', + '轩子' => ' xuān zǐ', + '露背' => ' lòu bèi', + '背头' => ' bēi tóu', + '露白' => ' lòu bái', + '腰杆' => ' yāo gǎn', + '任丘' => ' rén qiū', + '龟裂' => ' jūn liè', + '镟子' => ' xuàn zǐ', + '切分' => ' qiē fēn', + '推倒' => ' tuī dǎo', + '一朝' => ' yī zhāo', + '询查' => ' xún zhā', + '作乐' => ' zuò yuè', + '显得' => ' xiǎn de', + '反诘' => ' fǎn jié', + '提防' => ' dī fáng', + '馒头' => ' mán tou', + '推杆' => ' tuī gǎn', + '严处' => ' yán chǔ', + '扑棱' => ' pū lēng', + '青子' => ' qīng zǐ', + '翻倒' => ' fān dǎo', + '骑缝' => ' qí féng', + '佝瞀' => ' kòu mào', + '禽舍' => ' qín shè', + '供给' => ' gōng jǐ', + '脊线' => ' jǐ xiàn', + '肉干' => ' ròu gān', + '使尽' => ' shǐ jìn', + '探查' => ' tàn zhā', + '难倒' => ' nán dǎo', + '脱脱' => ' tuì tuì', + '切至' => ' qiē zhì', + '兴许' => ' xīng xǔ', + '掰扯' => ' bāi che', + '食菌' => ' shí jùn', + '鸡枞' => ' jī zōng', + '隐潭' => ' yǐn tán', + '压轴' => ' yā zhòu', + '颤栗' => ' zhàn lì', + '押当' => ' yā dàng', + '栅极' => ' shān jí', + '马蹬' => ' mǎ dèng', + '栅格' => ' shān gé', + '兴义' => ' xīng yì', + '翘足' => ' qiáo zú', + '伊甸' => ' yī diàn', + '翘企' => ' qiáo qǐ', + '摆划' => ' bǎi huá', + '兴和' => ' xīng hé', + '古朴' => ' gǔ piáo', + '馆子' => ' guǎn zǐ', + '八行' => ' bā háng', + '馏分' => ' liú fèn', + '住舍' => ' zhù shè', + '散板' => ' sǎn bǎn', + '扑扇' => ' pū shān', + '作为' => ' zuò wéi', + '拥塞' => ' yōng sè', + '脏话' => ' zāng huà', + '差使' => ' chāi shǐ', + '扦子' => ' qiān zǐ', + '校阅' => ' jiào yuè', + '审度' => ' shěn duó', + '处方' => ' chǔ fāng', + '栓塞' => ' shuān sè', + '校勘' => ' jiào kān', + '行号' => ' háng háo', + '孱弱' => ' chán ruò', + '泡桐' => ' pāo tóng', + '黉舍' => ' hóng shè', + '宏拓' => ' hóng tuò', + '行为' => ' xíng wéi', + '豪横' => ' háo hèng', + '翘材' => ' qiáo cái', + '行市' => ' háng shì', + '校舍' => ' xiào shè', + '行家' => ' háng jiā', + '侯刚' => ' hòu gāng', + '行话' => ' háng huà', + '行列' => ' háng liè', + '朝珠' => ' cháo zhū', + '接应' => ' jiē yìng', + '血钻' => ' xuè zuàn', + '号叫' => ' háo jiào', + '老少' => ' lǎo shào', + '美差' => ' měi chāi', + '审查' => ' shěn zhā', + '混浊' => ' hún zhuó', + '氹仔' => ' dàng zǎi', + '计量' => ' jì liáng', + '解调' => ' jiě tiáo', + '寒颤' => ' hán zhàn', + '背债' => ' bēi zhài', + '巷道' => ' hàng dào', + '行道' => ' háng dào', + '行规' => ' háng guī', + '行会' => ' háng huì', + '柴火' => ' chái huō', + '亲家' => ' qìng jia', + '深知' => ' shēn zhì', + '解元' => ' jiè yuán', + '编著' => ' biān zhù', + '栟茶' => ' bīng chá', + '考卷' => ' kǎo juàn', + '江都' => ' jiāng dū', + '好还' => ' hǎo huán', + '缝纫' => ' féng rèn', + '宁肯' => ' nìng kěn', + '挣得' => ' zhèng dé', + '治丧' => ' zhì sāng', + '混熟' => ' hùn shóu', + '行货' => ' háng huò', + '行头' => ' xíng tou', + '官倒' => ' guān dǎo', + '横事' => ' hèng shì', + '行辈' => ' háng bèi', + '守分' => ' shǒu fèn', + '宅舍' => ' zhái shè', + '翘楚' => ' qiáo chǔ', + '哄逗' => ' hǒng dòu', + '罄尽' => ' qìng jìn', + '稖头' => ' bàng tou', + '横蛮' => ' hèng mán', + '炳著' => ' bǐng zhù', + '混蒙' => ' hùn mēng', + '混行' => ' hún xíng', + '调酒' => ' tiáo jǐu', + '调适' => ' tiáo shì', + '供稿' => ' gòng gǎo', + '摔打' => ' shuāi dá', + '摒绝' => ' bìng jué', + '供果' => ' gòng guǒ', + '潦倒' => ' liáo dǎo', + '翘盼' => ' qiáo pàn', + '系上' => ' jì shang', + '季相' => ' jì xiàng', + '绷带' => ' bēng dài', + '罗甸' => ' luó diàn', + '蛮横' => ' mán hèng', + '办差' => ' bàn chāi', + '户长' => ' hù cháng', + '沙参' => ' shā shēn', + '校对' => ' jiào duì', + '宝钻' => ' bǎo zuàn', + '罐头' => ' guàn tou', + '波长' => ' bō cháng', + '补种' => ' bǔ zhòng', + '泡面' => ' pāo miàn', + '雹霰' => ' báo xiàn', + '袅娜' => ' niǎo nuó', + '扫帚' => ' sào zhǒu', + '裁缝' => ' cái féng', + '绷紧' => ' bēng jǐn', + '室町' => ' shì tǐng', + '抱朴' => ' bào piáo', + '校验' => ' jiào yàn', + '绕腾' => ' rào teng', + '泰兴' => ' tài xīng', + '审处' => ' shěn chǔ', + '号丧' => ' háo sāng', + '差事' => ' chāi shì', + '拶刑' => ' zǎn xíng', + '燕京' => ' yān jīng', + '蒙难' => ' méng nàn', + '煎炸' => ' jiān zhá', + '转去' => ' zhuàn qù', + '干冷' => ' gān lěng', + '干连' => ' gān lián', + '跟差' => ' gēn chāi', + '壳牌' => ' qiào pái', + '较著' => ' jiào zhù', + '转速' => ' zhuàn sù', + '差派' => ' chāi pài', + '转鼓' => ' zhuàn gǔ', + '轘裂' => ' huàn liè', + '粗率' => ' cū shuài', + '松散' => ' sōng sǎn', + '烘干' => ' hōng gān', + '香几' => ' xiāng jī', + '红晕' => ' hóng yùn', + '特长' => ' tè cháng', + '拓展' => ' tuò zhǎn', + '更代' => ' gēng dài', + '更楼' => ' gēng lóu', + '脏弹' => ' zāng dàn', + '糨糊' => ' jiàng hù', + '载明' => ' zǎi míng', + '该着' => ' gāi zháo', + '更改' => ' gēng gǎi', + '烟卷' => ' yān juàn', + '殷红' => ' yān hóng', + '桔梗' => ' jié gěng', + '干柴' => ' gān chái', + '蔡甸' => ' cài diàn', + '脏煤' => ' zāng méi', + '更漏' => ' gēng lòu', + '载湉' => ' zǎi tián', + '脊梁' => ' jǐ liáng', + '燕山' => ' yān shān', + '粘结' => ' nián jié', + '肮脏' => ' āng zāng', + '更为' => ' gèng wéi', + '拙著' => ' zhuō zhù', + '粘滞' => ' nián zhì', + '归还' => ' guī huán', + '归省' => ' guī xǐng', + '蔓菁' => ' mán jīng', + '较为' => ' jiào wéi', + '能耐' => ' néng nài', + '转矩' => ' zhuàn jǔ', + '浪头' => ' làng tou', + '宿将' => ' sù jiàng', + '揣摩' => ' chuǎi mó', + '净尽' => ' jìng jìn', + '有空' => ' yǒu kòng', + '换帖' => ' huàn tiě', + '好闲' => ' hào xián', + '被卷' => ' bèi juàn', + '嚎丧' => ' háo sāng', + '淋病' => ' lìn bìng', + '蜕壳' => ' tuì qiào', + '蜚蠊' => ' fěi lián', + '柴门' => ' zhài mén', + '蝃蝥' => ' zhuō máo', + '家当' => ' jiā dàng', + '海兴' => ' hǎi xīng', + '翻供' => ' fān gòng', + '缝制' => ' féng zhì', + '宫阙' => ' gōng què', + '海参' => ' hǎi shēn', + '总得' => ' zǒng děi', + '褪色' => ' tuì shǎi', + '要功' => ' yāo gōng', + '寒伧' => ' hán chen', + '揣测' => ' chuǎi cè', + '争得' => ' zhēng de', + '转炉' => ' zhuàn lú', + '转塔' => ' zhuàn tǎ', + '转子' => ' zhuàn zǐ', + '更迭' => ' gēng dié', + '更始' => ' gēng shǐ', + '片头' => ' piān tóu', + '转椅' => ' zhuàn yǐ', + '转磨' => ' zhuàn mò', + '身处' => ' shēn chǔ', + '身分' => ' shēn fèn', + '糨子' => ' jiàng zǐ', + '缝絍' => ' féng rèn', + '部将' => ' bù jiàng', + '蕉萃' => ' qiáo cuì', + '未曾' => ' wèi zēng', + '拳头' => ' quán tou', + '身为' => ' shēn wéi', + '煮熟' => ' zhǔ shóu', + '处身' => ' chǔ shēn', + '曾孙' => ' zēng sūn', + '有朝' => ' yǒu zhāo', + '深信' => ' shēn xìn', + '肖似' => ' xiào sì', + '鸿鹄' => ' hóng hú', + '攻难' => ' gōng nàn', + '视为' => ' shì wéi', + '为学' => ' wéi xué', + '为是' => ' wéi shì', + '公子' => ' gōng zǐ', + '裁切' => ' cái qiē', + '裁处' => ' cái chǔ', + '处处' => ' chǔ chù', + '公仆' => ' gōng pú', + '号啕' => ' háo táo', + '婉娩' => ' wǎn wǎn', + '万石' => ' wàn dàn', + '蝇子' => ' yíng zǐ', + '有为' => ' yǒu wéi', + '见得' => ' jiàn de', + '蚌埠' => ' bèng bù', + '要求' => ' yāo qiú', + '要晕' => ' yāo yūn', + '要挟' => ' yāo xié', + '要好' => ' yāo hǎo', + '要塞' => ' yào sài', + '裁度' => ' cái duó', + '萎谢' => ' wěi xiè', + '朝雨' => ' zhāo yǔ', + '虾干' => ' xiā gān', + '朝歌' => ' zhāo gē', + '望都' => ' wàng dū', + '螺杆' => ' luó gǎn', + '萎落' => ' wěi luò', + '衣着' => ' yī zhuó', + '衣冠' => ' yì guān', + '朝夕' => ' zhāo xī', + '行距' => ' háng jù', + '为然' => ' wéi rán', + '行语' => ' háng yǔ', + '行业' => ' háng yè', + '为下' => ' wéi xià', + '朝气' => ' zhāo qì', + '问难' => ' wèn nàn', + '为什' => ' wèi shí', + '为数' => ' wéi shù', + '为头' => ' wéi tóu', + '唯唯' => ' wěi wěi', + '崴嵬' => ' wēi wéi', + '血晕' => ' xiě yùn', + '元曲' => ' yuán qǔ', + '为文' => ' wéi wén', + '西藏' => ' xī zàng', + '僮仆' => ' tóng pú', + '蚂螂' => ' mā láng', + '下处' => ' xià chǔ', + '软呢' => ' ruǎn ní', + '蔚县' => ' yù xiàn', + '藤子' => ' téng zǐ', + '切杆' => ' qiē gān', + '轩掖' => ' xuān yè', + '俶傥' => ' tì tǎng', + '轧辊' => ' zhá gǔn', + '轧制' => ' zhá zhì', + '切口' => ' qiē kǒu', + '更衣' => ' gēng yī', + '特供' => ' tè gòng', + '切切' => ' qiē qiē', + '切刀' => ' qiē dāo', + '天得' => ' tiān de', + '蕃茄' => ' fān qié', + '天都' => ' tiān dū', + '车斗' => ' chē dǒu', + '躯壳' => ' qū qiào', + '躲难' => ' duǒ nàn', + '蹄髈' => ' tí pǎng', + '蕃衍' => ' fán yǎn', + '蔚为' => ' wèi wéi', + '下切' => ' xià qiē', + '望子' => ' wàng zǐ', + '为害' => ' wéi hài', + '汪子' => ' wāng zǐ', + '萎败' => ' wěi bài', + '免得' => ' miǎn de', + '褪下' => ' tùn xià', + '俭薄' => ' jiǎn bó', + '萎顿' => ' wěi dùn', + '尽早' => ' jìn zǎo', + '蝳蝐' => ' dài mào', + '服丧' => ' fú sāng', + '嗡子' => ' wēng zǐ', + '叨扰' => ' tāo rǎo', + '着迷' => ' zháo mí', + '藏族' => ' zàng zú', + '梃子' => ' tǐng zǐ', + '轻薄' => ' qīng bó', + '轻子' => ' qīng zǐ', + '更次' => ' gēng cì', + '堂子' => ' táng zǐ', + '唐子' => ' táng zǐ', + '下头' => ' xià tou', + '下帖' => ' xià tiě', + '优游' => ' yōu yóu', + '角子' => ' jiǎo zǐ', + '切碎' => ' qiē suì', + '蕴藉' => ' yùn jiè', + '斗六' => ' dǒu lìu', + '挑拔' => ' tiǎo bá', + '包干' => ' bāo gān', + '神似' => ' shén sì', + '舌苔' => ' shé tāi', + '兴起' => ' xīng qǐ', + '逃难' => ' táo nàn', + '曲靖' => ' qǔ jìng', + '木椆' => ' mù zhòu', + '腿杆' => ' tuǐ gǎn', + '敛巴' => ' liǎn ba', + '最为' => ' zuì wéi', + '认识' => ' rèn shi', + '会计' => ' kuài jì', + '认为' => ' rèn wéi', + '伞菌' => ' sǎn jùn', + '备查' => ' bèi zhā', + '处世' => ' chǔ shì', + '诘问' => ' jié wèn', + '月分' => ' yuè fèn', + '木栅' => ' mù shān', + '村舍' => ' cūn shè', + '北斗' => ' běi dǒu', + '傀儡' => ' kuǐ lěi', + '舍亲' => ' shè qīn', + '梢子' => ' shāo zǐ', + '少子' => ' shǎo zǐ', + '叨教' => ' tāo jiào', + '半分' => ' bàn fēn', + '午觉' => ' wǔ jiào', + '开衩' => ' kāi chà', + '开杆' => ' kāi gǎn', + '开拓' => ' kāi tuò', + '数九' => ' shǔ jiǔ', + '舍人' => ' shè rén', + '数落' => ' shǔ luò', + '苦差' => ' kǔ chāi', + '杓子' => ' sháo zi', + '十干' => ' shí gān', + '斗胆' => ' dǒu dǎn', + '倒坍' => ' dǎo tān', + '汇差' => ' huì chā', + '数出' => ' shǔ chū', + '北碚' => ' běi bèi', + '数数' => ' shǔ shù', + '甚的' => ' shèn de', + '处事' => ' chǔ shì', + '家伙' => ' jiā huo', + '处置' => ' chǔ zhì', + '冠以' => ' guàn yǐ', + '筒子' => ' tǒng zǐ', + '桶子' => ' tǒng zǐ', + '桐子' => ' tóng zǐ', + '铜子' => ' tóng zǐ', + '瞳子' => ' tóng zǐ', + '最差' => ' zuì chā', + '冠词' => ' guàn cí', + '调皮' => ' tiáo pí', + '调理' => ' tiáo lǐ', + '做为' => ' zuò wéi', + '痛恶' => ' tòng wù', + '调拨' => ' tiáo bō', + '调戏' => ' tiáo xì', + '调和' => ' tiáo hé', + '透切' => ' tòu qiē', + '调剂' => ' tiáo jì', + '曾都' => ' zēng dū', + '图卷' => ' tú juàn', + '停泊' => ' tíng bó', + '冠子' => ' guàn zi', + '藏獒' => ' zàng áo', + '调色' => ' tiáo sè', + '调资' => ' tiáo zī', + '拓殖' => ' tuò zhí', + '外切' => ' wài qiē', + '处在' => ' chǔ zài', + '月晕' => ' yuè yùn', + '月氏' => ' ròu zhī', + '处暑' => ' chǔ shǔ', + '解数' => ' xiè shù', + '解廌' => ' xiè zhì', + '角斗' => ' jué dòu', + '歪货' => ' wǎi huò', + '处决' => ' chǔ jué', + '角逐' => ' jué zhú', + '藏独' => ' zàng dú', + '处治' => ' chǔ zhì', + '处分' => ' chǔ fèn', + '处男' => ' chǔ nán', + '五通' => ' wǔ tòng', + '藏语' => ' zàng yǔ', + '铜佛' => ' tóng fó', + '铜模' => ' tóng mú', + '凳子' => ' dèng zǐ', + '童仆' => ' tóng pú', + '调速' => ' tiáo sù', + '更替' => ' gēng tì', + '逃奔' => ' táo bèn', + '面糊' => ' miàn hù', + '名曲' => ' míng qǔ', + '搭挡' => ' dā dàng', + '曲蟮' => ' qǔ shàn', + '挨冻' => ' ái dòng', + '搭当' => ' dā dàng', + '佛性' => ' fó xìng', + '架式' => ' jià shi', + '供物' => ' gòng wù', + '脏字' => ' zāng zì', + '揭载' => ' jiē zǎi', + '脏土' => ' zāng tǔ', + '电锯' => ' diàn jū', + '曲静' => ' qǔ jìng', + '散列' => ' sǎn liè', + '难处' => ' nán chǔ', + '卷须' => ' juàn xū', + '恰切' => ' qià qiē', + '难为' => ' nán wéi', + '查看' => ' zhā kàn', + '考查' => ' kǎo zhā', + '颓萎' => ' tuí wěi', + '查处' => ' chá chǔ', + '发卷' => ' fā juàn', + '腰折' => ' yāo shé', + '袷袢' => ' qiā pàn', + '发小' => ' fà xiǎo', + '改为' => ' gǎi wéi', + '佛坪' => ' fó píng', + '切开' => ' qiē kāi', + '佛典' => ' fó diǎn', + '摇杆' => ' yáo gǎn', + '耍子' => ' shuǎ zǐ', + '担懮' => ' dān yōu', + '发型' => ' fà xíng', + '掖县' => ' yè xiàn', + '教子' => ' jiào zǐ', + '佛山' => ' fó shān', + '耗尽' => ' hào jìn', + '也曾' => ' yě zēng', + '发脚' => ' fà jiǎo', + '切花' => ' qiē huā', + '耳片' => ' ěr piān', + '胶子' => ' jiāo zǐ', + '胡同' => ' hú tòng', + '要切' => ' yào qiē', + '佛冈' => ' fó gāng', + '电荷' => ' diàn hè', + '难民' => ' nàn mín', + '暗杠' => ' àn gàng', + '搜查' => ' sōu zhā', + '鲁甸' => ' lǔ diàn', + '鬈发' => ' quán fà', + '音乐' => ' yīn yuè', + '阙下' => ' què xià', + '非难' => ' fēi nàn', + '鮟鱇' => ' ān kāng', + '臣子' => ' chén zǐ', + '攒簇' => ' cuán cù', + '臣仆' => ' chén pú', + '非得' => ' fēi děi', + '让子' => ' ràng zǐ', + '临帖' => ' lín tiè', + '临难' => ' lín nàn', + '圩镇' => ' xū zhèn', + '洋落' => ' yáng là', + '犬子' => ' quǎn zǐ', + '鹿角' => ' lù jiǎo', + '鹿茸' => ' lù róng', + '职分' => ' zhí fèn', + '叶韵' => ' xié yùn', + '面的' => ' miàn dí', + '肖恩' => ' xiāo ēn', + '畜养' => ' xù yǎng', + '撇号' => ' piě hào', + '颈子' => ' jǐng zǐ', + '教课' => ' jiāo kè', + '学舍' => ' xué shè', + '叶心' => ' xié xīn', + '切人' => ' qiē rén', + '枸橼' => ' jǔ yuán', + '高头' => ' gāo tou', + '却倒' => ' què dǎo', + '脊令' => ' jí líng', + '乾德' => ' qián dé', + '腱子' => ' jiàn zǐ', + '仰给' => ' yǎng jǐ', + '可调' => ' kě tiáo', + '敢为' => ' gǎn wéi', + '脚本' => ' jué běn', + '脏污' => ' zāng wū', + '卸载' => ' xiè zǎi', + '电子' => ' diàn zǐ', + '烟杆' => ' yān gǎn', + '切屑' => ' qiē xiè', + '露出' => ' lòu chū', + '露怯' => ' lòu qiè', + '叱吒' => ' chì zhà', + '畜产' => ' xù chǎn', + '发廊' => ' fà láng', + '着陆' => ' zhuó lù', + '贡嘎' => ' gòng gá', + '切磋' => ' qiē cuō', + '瘫倒' => ' tān dǎo', + '曾祖' => ' zēng zǔ', + '天姥' => ' tiān mǔ', + '西兴' => ' xī xīng', + '调气' => ' tiáo qì', + '薄晓' => ' bó xiǎo', + '曾子' => ' zēng zǐ', + '薄产' => ' bó chǎn', + '猫腰' => ' máo yāo', + '切糕' => ' qiē gāo', + '调息' => ' tiáo xī', + '苕溪' => ' tiáo xī', + '薄田' => ' bó tián', + '丰都' => ' fēng dū', + '豉油' => ' chǐ yóu', + '豆豉' => ' dòu chǐ', + '调序' => ' tiáo xù', + '蹊径' => ' xī jìng', + '豆佉' => ' dòu qiā', + '豁然' => ' huò rán', + '澹台' => ' tán tái', + '更夫' => ' gēng fū', + '假藉' => ' jiǎ jiè', + '曲面' => ' qǔ miàn', + '着魔' => ' zháo mó', + '倒头' => ' dǎo tóu', + '阘茸' => ' tà róng', + '嗒丧' => ' tà sàng', + '线子' => ' xiàn zǐ', + '蚬子' => ' xiǎn zǐ', + '馅子' => ' xiàn zǐ', + '弦子' => ' xián zǐ', + '本分' => ' běn fèn', + '刊载' => ' kān zǎi', + '莲都' => ' lián dū', + '辗轧' => ' zhǎn yà', + '蒙覆' => ' měng fù', + '探杆' => ' tàn gǎn', + '辟雍' => ' bì yōng', + '仙佛' => ' xiān fó', + '切韵' => ' qiē yùn', + '末了' => ' mò liǎo', + '莲子' => ' lián zǐ', + '切除' => ' qiē chú', + '更卒' => ' gēng zú', + '变得' => ' biàn de', + '藉口' => ' jiè kǒu', + '原子' => ' yuán zǐ', + '薄情' => ' bó qíng', + '薄命' => ' bó mìng', + '分内' => ' fèn nèi', + '趼子' => ' jiǎn zǐ', + '更鼓' => ' gēng gǔ', + '调合' => ' tiáo hé', + '起哄' => ' qǐ hòng', + '薄幸' => ' bó xìng', + '调护' => ' tiáo hù', + '调级' => ' tiáo jí', + '赢得' => ' yíng de', + '调达' => ' tiáo dá', + '赚得' => ' zuàn de', + '质难' => ' zhì nàn', + '薄明' => ' bó míng', + '分外' => ' fèn wài', + '调幅' => ' tiáo fú', + '散漫' => ' sǎn màn', + '录供' => ' lù gòng', + '点子' => ' diǎn zǐ', + '搪塞' => ' táng sè', + '胡芫' => ' hú yuán', + '跂望' => ' qì wàng', + '分散' => ' fēn sǎn', + '藉由' => ' jiè yóu', + '踺子' => ' jiàn zǐ', + '藉着' => ' jiè zhe', + '谢帖' => ' xiè tiě', + '帖文' => ' tiě wén', + '帖学' => ' tiè xué', + '藏掖' => ' cáng yè', + '曾朴' => ' zēng pǔ', + '厅子' => ' tīng zǐ', + '听子' => ' tīng zǐ', + '薄酬' => ' bó chóu', + '资兴' => ' zī xīng', + '跌倒' => ' diē dǎo', + '分叉' => ' fēn chà', + '跪倒' => ' guì dǎo', + '跟头' => ' gēn tou', + '天雨' => ' tiān yù', + '分为' => ' fēn wéi', + '铣削' => ' xǐ xiāo', + '田子' => ' tián zǐ', + '佻薄' => ' tiāo bó', + '调测' => ' tiáo cè', + '更递' => ' gēng dì', + '冰斗' => ' bīng dǒu', + '攘辟' => ' rǎng bì', + '供事' => ' gòng shì', + '重估' => ' chóng gū', + '长股' => ' cháng gǔ', + '春假' => ' chūn jià', + '重覆' => ' chóng fù', + '重合' => ' chóng hé', + '重午' => ' chóng wǔ', + '发怔' => ' fā zhèng', + '斗方' => ' dǒu fāng', + '斗拱' => ' dǒu gǒng', + '长局' => ' cháng jú', + '长句' => ' cháng jù', + '铅山' => ' yán shān', + '页框' => ' yè kuàng', + '释卷' => ' shì juàn', + '丑角' => ' chǒu jué', + '醒豁' => ' xǐng huò', + '顶杆' => ' dǐng gǎn', + '顺差' => ' shùn chā', + '重启' => ' chóng qǐ', + '强逼' => ' qiǎng bī', + '强迫' => ' qiǎng pò', + '登载' => ' dēng zǎi', + '长图' => ' cháng tú', + '橙子' => ' chéng zǐ', + '落枕' => ' lào zhěn', + '昌都' => ' chāng dū', + '靓丽' => ' liàng lì', + '空缺' => ' kòng quē', + '长衣' => ' cháng yī', + '空难' => ' kōng nàn', + '打量' => ' dǎ liang', + '菌柄' => ' jùn bǐng', + '长吁' => ' cháng xū', + '长须' => ' cháng xū', + '扣应' => ' kòu yìng', + '铺张' => ' pū zhāng', + '长嘶' => ' cháng sī', + '得中' => ' dé zhòng', + '尽兴' => ' jìn xìng', + '桦甸' => ' huà diàn', + '搀和' => ' chān huo', + '窨井' => ' yìn jǐng', + '销假' => ' xiāo jià', + '铣铁' => ' xiǎn tiě', + '银行' => ' yín háng', + '地转' => ' dì zhuàn', + '华山' => ' huà shān', + '竺乾' => ' zhú qián', + '配称' => ' pèi chèn', + '潮差' => ' cháo chā', + '长理' => ' cháng lǐ', + '攀供' => ' pān gòng', + '冻干' => ' dòng gān', + '掷色' => ' zhì shǎi', + '拧开' => ' nǐng kāi', + '长路' => ' cháng lù', + '长利' => ' cháng lì', + '长丽' => ' cháng lì', + '重提' => ' chóng tí', + '晕针' => ' yùn zhēn', + '白卷' => ' bái juàn', + '章子' => ' zhāng zǐ', + '重屋' => ' chóng wū', + '暖和' => ' nuǎn huo', + '重沓' => ' chóng tà', + '应聘' => ' yìng pìn', + '冲压' => ' chòng yā', + '纶巾' => ' guān jīn', + '伎俩' => ' jì liǎng', + '解款' => ' jiè kuǎn', + '估量' => ' gū liang', + '失调' => ' shī tiáo', + '重子' => ' zhòng zǐ', + '时兴' => ' shí xīng', + '酌处' => ' zhuó chǔ', + '重塑' => ' chóng sù', + '长戟' => ' cháng jǐ', + '强拉' => ' qiǎng lā', + '强子' => ' qiáng zǐ', + '邓拓' => ' dèng tuò', + '长河' => ' cháng hé', + '落炕' => ' lào kàng', + '强似' => ' qiáng sì', + '酬酢' => ' chóu zuò', + '横财' => ' hèng cái', + '弹性' => ' tán xìng', + '重组' => ' chóng zǔ', + '场圃' => ' cháng pǔ', + '重复' => ' chóng fù', + '重荷' => ' zhòng hè', + '重聚' => ' chóng jù', + '菲酌' => ' fěi zhuó', + '自传' => ' zì zhuàn', + '螲蟷' => ' dié dāng', + '技俩' => ' jì liǎng', + '弹跳' => ' tán tiào', + '抄查' => ' chāo zhā', + '长足' => ' cháng zú', + '朝晖' => ' zhāo huī', + '胜子' => ' shèng zǐ', + '甚或' => ' shèn huò', + '养分' => ' yǎng fèn', + '王著' => ' wáng zhù', + '撩开' => ' liáo kāi', + '撩逗' => ' liáo dòu', + '退还' => ' tuì huán', + '苍术' => ' cāng zhú', + '正子' => ' zhèng zǐ', + '甲壳' => ' jiǎ qiào', + '甚微' => ' shèn wēi', + '道观' => ' dào guàn', + '筠连' => ' jūn lián', + '田舍' => ' tián shè', + '用处' => ' yòng chǔ', + '用尽' => ' yòng jìn', + '产假' => ' chǎn jià', + '产仔' => ' chǎn zǎi', + '生发' => ' shēng fà', + '欢势' => ' huān shi', + '道行' => ' dào héng', + '道藏' => ' dào zàng', + '生子' => ' shēng zǐ', + '岑参' => ' cén shēn', + '正意' => ' zhēng yì', + '本行' => ' běn háng', + '着落' => ' zhuó luò', + '择菜' => ' zhái cài', + '颠茄' => ' diān qié', + '放倒' => ' fàng dǎo', + '答腔' => ' dā qiāng', + '甚且' => ' shèn qiě', + '着紧' => ' zháo jǐn', + '风头' => ' fēng tou', + '查检' => ' zhā jiǎn', + '着花' => ' zhuó huā', + '连载' => ' lián zǎi', + '正安' => ' zhēng an', + '连累' => ' lián lěi', + '查岗' => ' zhā gǎng', + '腔子' => ' qiāng zǐ', + '废卷' => ' fèi juàn', + '通什' => ' tōng shí', + '年假' => ' nián jià', + '着处' => ' zhuó chù', + '饼干' => ' bǐng gān', + '这些' => ' zhèi xiē', + '查边' => ' zhā biān', + '掺和' => ' chān huo', + '甜头' => ' tián tou', + '尽情' => ' jìn qíng', + '量器' => ' liáng qì', + '横折' => ' héng shé', + '量测' => ' liáng cè', + '横暴' => ' hèng bào', + '橡子' => ' xiàng zǐ', + '华龙' => ' huà lóng', + '量度' => ' liáng dù', + '盛器' => ' chéng qì', + '星斗' => ' xīng dǒu', + '量子' => ' liàng zǐ', + '斗柄' => ' dǒu bǐng', + '量计' => ' liáng jì', + '皇子' => ' huáng zǐ', + '量具' => ' liáng jù', + '重译' => ' chóng yì', + '雕刀' => ' diāo dāo', + '刁斗' => ' diāo dǒu', + '立传' => ' lì zhuàn', + '横祸' => ' hèng huò', + '长驱' => ' cháng qū', + '凋萎' => ' diāo wěi', + '尽性' => ' jìn xìng', + '尽孝' => ' jìn xiào', + '星宿' => ' xīng xiù', + '显豁' => ' xiǎn huò', + '着眼' => ' zhuó yǎn', + '查照' => ' zhā zhào', + '风干' => ' fēng gān', + '曲江' => ' qǔ jiāng', + '游说' => ' yóu shuì', + '差人' => ' chāi rén', + '甚至' => ' shèn zhì', + '枪子' => ' qiāng zǐ', + '着然' => ' zhuó rán', + '年载' => ' nián zǎi', + '甚为' => ' shèn wéi', + '鞅牛' => ' yǎng níu', + '显著' => ' xiǎn zhù', + '空白' => ' kòng bái', + '落色' => ' lào shǎi', + '晒干' => ' shài gān', + '横切' => ' héng qiē', + '钓杆' => ' diào gǎn', + '长日' => ' cháng rì', + '找头' => ' zhǎo tou', + '易传' => ' yì zhuàn', + '电镐' => ' diàn hào', + '失着' => ' shī zhāo', + '掠阵' => ' lüě zhèn', + '畹町' => ' wǎn dīng', + '舍监' => ' shè jiān', + '油坊' => ' yóu fáng', + '舍间' => ' shè jiān', + '耩子' => ' jiǎng zǐ', + '膙子' => ' jiǎng zǐ', + '应约' => ' yìng yuē', + '没空' => ' méi kòng', + '应心' => ' yìng xīn', + '北漂' => ' běi piāo', + '甚佳' => ' shèn jiā', + '应是' => ' yìng shì', + '干笑' => ' gān xiào', + '奔命' => ' bèn mìng', + '应会' => ' yìng huì', + '应化' => ' yìng huà', + '神乐' => ' shén yuè', + '应给' => ' yìng gěi', + '单杠' => ' dān gàng', + '校改' => ' jiào gǎi', + '脚杆' => ' jiǎo gǎn', + '共处' => ' gòng chǔ', + '干等' => ' gān děng', + '丧假' => ' sāng jià', + '教给' => ' jiāo gěi', + '二重' => ' èr chóng', + '曾加' => ' zēng jiā', + '好战' => ' hào zhàn', + '再兴' => ' zài xīng', + '榠楂' => ' míng chá', + '占卦' => ' zhān guà', + '侧歪' => ' zhāi wāi', + '斋舍' => ' zhāi shè', + '卜征' => ' bǔ zhēng', + '曾云' => ' zēng yún', + '三省' => ' sān xǐng', + '桑干' => ' sāng gān', + '干俸' => ' gān fèng', + '唤头' => ' huàn tou', + '曾华' => ' zēng huá', + '好氧' => ' hào yǎng', + '干冰' => ' gān bīng', + '乾坤' => ' qián kūn', + '黾勉' => ' mǐn miǎn', + '僧舍' => ' sēng shè', + '藏民' => ' zàng mín', + '藏蓝' => ' zàng lán', + '家雀' => ' jiā qiǎo', + '诱供' => ' yòu gòng', + '渐染' => ' jiān rǎn', + '喧哗' => ' xuān huá', + '抗折' => ' kàng shé', + '倾倒' => ' qīng dǎo', + '倾尽' => ' qīng jìn', + '报应' => ' bào yìng', + '捞着' => ' lāo zháo', + '僬侥' => ' jiāo yáo', + '刁难' => ' diāo nàn', + '调琴' => ' tiáo qín', + '文卷' => ' wén juàn', + '尾椎' => ' wěi zhuī', + '为真' => ' wéi zhēn', + '调处' => ' tiáo chǔ', + '咳喘' => ' ké chuǎn', + '允当' => ' yǔn dàng', + '外行' => ' wài háng', + '为性' => ' wéi xìng', + '报丧' => ' bào sāng', + '分行' => ' fēn háng', + '挑檐' => ' tiǎo yán', + '躺倒' => ' tǎng dǎo', + '切断' => ' qiē duàn', + '切换' => ' qiē huàn', + '挑担' => ' tiāo dàn', + '勘校' => ' kān jiào', + '丧尸' => ' sāng shī', + '夺冠' => ' duó guàn', + '柬帖' => ' jiǎn tiě', + '卓著' => ' zhuó zhù', + '卒中' => ' cù zhòng', + '羽裳' => ' yǔ cháng', + '上齐' => ' shàng jì', + '了事' => ' liǎo shì', + '诱哄' => ' yòu hòng', + '了却' => ' liǎo què', + '了结' => ' liǎo jié', + '少奶' => ' shào nǎi', + '分当' => ' fèn dāng', + '了解' => ' liǎo jiě', + '有甚' => ' yǒu shèn', + '稍为' => ' shāo wéi', + '丧尽' => ' sàng jìn', + '有间' => ' yǒu jiàn', + '了然' => ' liǎo rán', + '塞翁' => ' sài wēng', + '精尽' => ' jīng jìn', + '兴筑' => ' xīng zhù', + '传记' => ' zhuàn jì', + '干癣' => ' gān xuǎn', + '干瘦' => ' gān shòu', + '失当' => ' shī dàng', + '巡更' => ' xún gēng', + '介壳' => ' jiè qiào', + '校注' => ' jiào zhù', + '甚者' => ' shèn zhě', + '动弹' => ' dòng tan', + '生得' => ' shēng de', + '什邡' => ' shí fāng', + '神煞' => ' shén shà', + '哗变' => ' huá biàn', + '食甚' => ' shí shèn', + '六行' => ' liù háng', + '勤朴' => ' qín piáo', + '舒卷' => ' shū juàn', + '雪糁' => ' xuě shēn', + '吓声' => ' hè shēng', + '天台' => ' tiān tāi', + '来朝' => ' lái zhāo', + '天分' => ' tiān fèn', + '悬瀑' => ' xuán bào', + '赎当' => ' shú dàng', + '供品' => ' gòng pǐn', + '惊倒' => ' jīng dǎo', + '胜似' => ' shèng sì', + '今朝' => ' jīn zhāo', + '数算' => ' shǔ suàn', + '乱弹' => ' luàn tán', + '嘉兴' => ' jiā xīng', + '解送' => ' jiè sòng', + '十行' => ' shí háng', + '劲草' => ' jìng cǎo', + '劲直' => ' jìng zhí', + '劲烈' => ' jìng liè', + '一唱' => ' yí chàng', + '一长' => ' yī cháng', + '诗卷' => ' shī juàn', + '哗笑' => ' huá xiào', + '匀称' => ' yún chèn', + '乘舆' => ' shèng yú', + '人参' => ' rén shēn', + '甚是' => ' shèn shì', + '娇娜' => ' jiāo nuó', + '呛咕' => ' qiāng gu', + '嚼头' => ' jiáo tóu', + '浇头' => ' jiāo tou', + '引吭' => ' yǐn háng', + '勾当' => ' gòu dàng', + '解差' => ' jiè chāi', + '数说' => ' shǔ shuō', + '圩场' => ' xū cháng', + '协调' => ' xié tiáo', + '苫盖' => ' shàn gài', + '作兴' => ' zuò xīng', + '龙湫' => ' lóng qiū', + '刷白' => ' shuà bái', + '漫卷' => ' màn juàn', + '焦干' => ' jiāo gān', + '晕圈' => ' yùn quān', + '善处' => ' shàn chǔ', + '佳兴' => ' jiā xīng', + '干净' => ' gān jìng', + '嘉应' => ' jiā yìng', + '作料' => ' zuó liào', + '监本' => ' jiàn běn', + '干潮' => ' gān cháo', + '干煸' => ' gān biān', + '煞星' => ' shà xīng', + '乐段' => ' yuè duàn', + '丧事' => ' sāng shì', + '蒙事' => ' mēng shì', + '园舍' => ' yuán shè', + '圜丘' => ' yuán qiū', + '好动' => ' hào dòng', + '遛鸟' => ' liù niǎo', + '作坊' => ' zuō fang', + '副相' => ' fù xiàng', + '佛光' => ' fó guāng', + '供职' => ' gòng zhí', + '供花' => ' gòng huā', + '茎杆' => ' jīng gǎn', + '嚼舌' => ' jiáo shé', + '是甚' => ' shì shèn', + '天干' => ' tiān gān', + '佣金' => ' yòng jīn', + '佛像' => ' fó xiàng', + '劲射' => ' jìng shè', + '嘲哳' => ' zhāo zhā', + '雁行' => ' yàn háng', + '太监' => ' tài jiàn', + '路长' => ' lù cháng', + '龙仔' => ' lóng zǎi', + '加冠' => ' jiā guàn', + '洋镐' => ' yáng hào', + '尽让' => ' jìn ràng', + '收载' => ' shōu zǎi', + '尽欢' => ' jìn huān', + '掩卷' => ' yǎn juàn', + '磟碡' => ' liù zhóu', + '都昌' => ' dū chāng', + '诗行' => ' shī háng', + '槛车' => ' jiàn chē', + '斜杠' => ' xié gàng', + '倒找' => ' dǎo zhǎo', + '斗篷' => ' dǒu péng', + '长物' => ' cháng wù', + '微调' => ' wēi tiáo', + '长牙' => ' cháng yá', + '乐清' => ' yuè qīng', + '收假' => ' shōu jià', + '料斗' => ' liào dǒu', + '长武' => ' cháng wǔ', + '困觉' => ' kùn jiào', + '改行' => ' gǎi háng', + '楞严' => ' lèng yán', + '瞒哄' => ' mán hǒng', + '数清' => ' shǔ qīng', + '称职' => ' chèn zhí', + '开钻' => ' kāi zuàn', + '杠头' => ' gàng tóu', + '零散' => ' líng sǎn', + '知了' => ' zhī liǎo', + '扁担' => ' biǎn dàn', + '开卷' => ' kāi juàn', + '斗筲' => ' dǒu shāo', + '刀削' => ' dāo xiāo', + '当是' => ' dàng shì', + '倒剪' => ' dǎo jiǎn', + '馆舍' => ' guǎn shè', + '管乐' => ' guǎn yuè', + '水分' => ' shuǐ fèn', + '藤蔓' => ' téng wàn', + '调价' => ' tiáo jià', + '奖掖' => ' jiǎng yè', + '着火' => ' zháo huǒ', + '拉长' => ' lā cháng', + '数点' => ' shǔ diǎn', + '瞓觉' => ' xùn jiào', + '放假' => ' fàng jià', + '教书' => ' jiāo shū', + '长袜' => ' cháng wà', + '倒卷' => ' dǎo juàn', + '长裤' => ' cháng kù', + '长葛' => ' cháng gě', + '倒嗓' => ' dǎo sǎng', + '兴仁' => ' xīng rén', + '倒算' => ' dǎo suàn', + '长笛' => ' cháng dí', + '长途' => ' cháng tú', + '散兵' => ' sǎn bīng', + '思量' => ' sī liang', + '冠带' => ' guàn dài', + '省察' => ' xǐng chá', + '的证' => ' dí zhèng', + '打场' => ' dǎ cháng', + '露苗' => ' lòu miáo', + '露丑' => ' lòu chǒu', + '相机' => ' xiàng jī', + '相扑' => ' xiàng pū', + '露面' => ' lòu miàn', + '车削' => ' chē xiāo', + '镐京' => ' hào jīng', + '露风' => ' lòu fēng', + '草甸' => ' cǎo diàn', + '露馅' => ' lòu xiàn', + '空暇' => ' kòng xiá', + '等衰' => ' děng cuī', + '搅混' => ' jiǎo gǔn', + '撤差' => ' chè chāi', + '搅和' => ' jiǎo huo', + '相图' => ' xiàng tú', + '相册' => ' xiàng cè', + '靓女' => ' liàng nǚ', + '澄沙' => ' dèng shā', + '露脸' => ' lòu liǎn', + '茸毛' => ' róng máo', + '手弹' => ' shǒu tán', + '数念' => ' shǔ niàn', + '重播' => ' chóng bō', + '散件' => ' sǎn jiàn', + '乐团' => ' yuè tuán', + '蛏子' => ' chēng zǐ', + '闭上' => ' bì shang', + '倒产' => ' dǎo chǎn', + '散乱' => ' sǎn luàn', + '倒垂' => ' dǎo chuí', + '长发' => ' cháng fà', + '倒吊' => ' dǎo diào', + '钻卡' => ' zuàn qiǎ', + '长野' => ' cháng yě', + '长达' => ' cháng dá', + '倒动' => ' dǎo dòng', + '呈子' => ' chéng zǐ', + '倒风' => ' dǎo fēng', + '蒙牛' => ' měng níu', + '念头' => ' niàn tou', + '称心' => ' chèn xīn', + '长乐' => ' cháng lè', + '支行' => ' zhī háng', + '冠绝' => ' guàn jué', + '满拧' => ' mǎn nǐng', + '相似' => ' xiāng sì', + '更老' => ' gēng lǎo', + '挑唆' => ' tiǎo suō', + '未了' => ' wèi liǎo', + '藏人' => ' zàng rén', + '挑嘴' => ' tiǎo zuǐ', + '禀帖' => ' bǐng tiě', + '麻将' => ' má jiàng', + '羹匙' => ' gēng chí', + '藏文' => ' zàng wén', + '潦草' => ' liáo cǎo', + '黄埔' => ' huáng pǔ', + '查拳' => ' zhā quán', + '圣子' => ' shèng zǐ', + '尿样' => ' suī yàng', + '黄陂' => ' huáng pō', + '澎湃' => ' péng pài', + '溃脓' => ' huì nóng', + '庚帖' => ' gēng tiě', + '请帖' => ' qǐng tiě', + '搂钱' => ' lōu qián', + '请假' => ' qǐng jià', + '调驯' => ' tiáo xùn', + '议长' => ' yì cháng', + '变为' => ' biàn wéi', + '冰瀑' => ' bīng bào', + '调解' => ' tiáo jiě', + '挑花' => ' tiǎo huā', + '调音' => ' tiáo yīn', + '摒除' => ' bìng chú', + '调谑' => ' tiáo xuè', + '边塞' => ' biān sài', + '漫应' => ' màn yìng', + '调谐' => ' tiáo xié', + '调试' => ' tiáo shì', + '调训' => ' tiáo xùn', + '句芒' => ' gōu máng', + '豁免' => ' huò miǎn', + '挑逗' => ' tiǎo dòu', + '齿更' => ' chǐ gēng', + '挑衅' => ' tiǎo xìn', + '漂雷' => ' piāo léi', + '漂游' => ' piāo yóu', + '少尉' => ' shào wèi', + '够着' => ' gòu zháo', + '供饭' => ' gòng fàn', + '调频' => ' tiáo pín', + '炮炼' => ' páo liàn', + '工尺' => ' gōng chě', + '挺杆' => ' tǐng gǎn', + '刮削' => ' guā xiāo', + '调唆' => ' tiáo suō', + '搋子' => ' chuāi zǐ', + '处境' => ' chǔ jìng', + '评为' => ' píng wéi', + '龈病' => ' yín bìng', + '桄子' => ' guàng zǐ', + '挨整' => ' ái zhěng', + '漂白' => ' piǎo bái', + '调嘴' => ' tiáo zuǐ', + '漂流' => ' piāo liú', + '固相' => ' gù xiàng', + '讨还' => ' tǎo huán', + '调味' => ' tiáo wèi', + '调协' => ' tiáo xié', + '调匀' => ' tiáo yún', + '漂染' => ' piǎo rǎn', + '漂摇' => ' piāo yáo', + '汤匙' => ' tāng chí', + '调制' => ' tiáo zhì', + '对称' => ' duì chèn', + '调侃' => ' tiáo kǎn', + '对应' => ' duì yìng', + '缘分' => ' yuán fèn', + '记传' => ' jì zhuàn', + '调节' => ' tiáo jié', + '宝藏' => ' bǎo zàng', + '调摄' => ' tiáo shè', + '藓苔' => ' xiǎn tāi', + '将棋' => ' jiàng qí', + '将伯' => ' qiāng bó', + '虎将' => ' hǔ jiàng', + '测量' => ' cè liáng', + '滚杠' => ' gǔn gàng', + '该应' => ' gāi yìng', + '臂长' => ' bì cháng', + '木框' => ' mù kuàng', + '清查' => ' qīng zhā', + '涡阳' => ' guō yáng', + '试卷' => ' shì juàn', + '紧绷' => ' jǐn bēng', + '线杆' => ' xiàn gǎn', + '宝兴' => ' bǎo xīng', + '访查' => ' fǎng zhā', + '国丧' => ' guó sāng', + '过甚' => ' guò shèn', + '宝应' => ' bǎo yìng', + '安广' => ' ān guǎng', + '朝来' => ' zhāo lái', + '谁知' => ' shéi zhī', + '自转' => ' zì zhuàn', + '大仓' => ' tài cāng', + '兴奋' => ' xīng fèn', + '情分' => ' qíng fèn', + '应运' => ' yìng yùn', + '程子' => ' chéng zǐ', + '芫花' => ' yuán huā', + '应门' => ' yìng mén', + '应验' => ' yìng yàn', + '破相' => ' pò xiàng', + '杠刀' => ' gàng dāo', + '应邀' => ' yìng yāo', + '应诺' => ' yìng nuò', + '粉坊' => ' fěn fáng', + '排行' => ' pái háng', + '等为' => ' děng wéi', + '着恼' => ' zhuó nǎo', + '栏栅' => ' lán shān', + '还魂' => ' huán hún', + '画卷' => ' huà juàn', + '弄混' => ' nòng hún', + '还贷' => ' huán dài', + '还给' => ' huán gěi', + '恶创' => ' è chuāng', + '碌碡' => ' liù zhou', + '长策' => ' cháng cè', + '间歇' => ' jiàn xiē', + '关塞' => ' guān sài', + '关卡' => ' guān qiǎ', + '悄然' => ' qiǎo rán', + '恰当' => ' qià dàng', + '称多' => ' chèn duō', + '大率' => ' dà shuài', + '兴化' => ' xīng huà', + '阅卷' => ' yuè juàn', + '间谍' => ' jiàn dié', + '杠杆' => ' gàng gǎn', + '应试' => ' yìng shì', + '间接' => ' jiàn jiē', + '间或' => ' jiàn huò', + '间奏' => ' jiàn zòu', + '种地' => ' zhòng dì', + '碎钻' => ' suì zuàn', + '患难' => ' huàn nàn', + '兴学' => ' xīng xué', + '散工' => ' sǎn gōng', + '攒眉' => ' cuán méi', + '攒射' => ' cuán shè', + '还本' => ' huán běn', + '还书' => ' huán shū', + '敌将' => ' dí jiàng', + '张掖' => ' zhāng yè', + '长歌' => ' cháng gē', + '邮差' => ' yóu chāi', + '督率' => ' dū shuài', + '长斧' => ' cháng fǔ', + '病假' => ' bìng jià', + '病倒' => ' bìng dǎo', + '长法' => ' cháng fǎ', + '敦朴' => ' dūn piáo', + '折钱' => ' shé qián', + '著名' => ' zhù míng', + '折缝' => ' zhé féng', + '弧长' => ' hú cháng', + '当月' => ' dàng yuè', + '长辞' => ' cháng cí', + '折腾' => ' zhē téng', + '引着' => ' yǐn zháo', + '荷重' => ' hè zhòng', + '弦乐' => ' xián yuè', + '当晚' => ' dàng wǎn', + '着实' => ' zhuó shí', + '抬杠' => ' tái gàng', + '都城' => ' dū chéng', + '长骨' => ' cháng gǔ', + '抹胸' => ' mò xiōng', + '留空' => ' liú kòng', + '广安' => ' guǎng ān', + '厂子' => ' chǎng zǐ', + '当作' => ' dàng zuò', + '长波' => ' cháng bō', + '还口' => ' huán kǒu', + '还价' => ' huán jià', + '抽斗' => ' chōu dǒu', + '抽查' => ' chōu zhā', + '着数' => ' zhāo shù', + '频数' => ' pín shuò', + '长鼓' => ' cháng gǔ', + '筊杯' => ' jiǎo bēi', + '脑杓' => ' nǎo sháo', + '款新' => ' kuǎn xīn', + '击中' => ' jī zhòng', + '颠倒' => ' diān dǎo', + '适当' => ' shì dàng', + '还嘴' => ' huán zuǐ', + '适应' => ' shì yìng', + '当做' => ' dàng zuò', + '边卡' => ' biān qiǎ', + '成都' => ' chéng dū', + '钦差' => ' qīn chāi', + '冲模' => ' chòng mú', + '榔头' => ' láng tou', + '长女' => ' cháng nǚ', + '施甸' => ' shī diàn', + '支差' => ' zhī chāi', + '霓裳' => ' ní cháng', + '钻头' => ' zuàn tóu', + '茄萣' => ' qié dìng', + '录相' => ' lù xiàng', + '房舍' => ' fáng shè', + '间作' => ' jiàn zuò', + '感应' => ' gǎn yìng', + '省亲' => ' xǐng qīn', + '应接' => ' yìng jiē', + '只身' => ' zhī shēn', + '散见' => ' sǎn jiàn', + '般桓' => ' pán huán', + '舰只' => ' jiàn zhī', + '应时' => ' yìng shí', + '枕头' => ' zhěn tou', + '愁煞' => ' chóu shà', + '枕藉' => ' zhěn jiè', + '色相' => ' sè xiàng', + '省视' => ' xǐng shì', + '长子' => ' zhǎng zǐ', + '兴文' => ' xīng wén', + '看守' => ' kān shǒu', + '钻戒' => ' zuàn jiè', + '椽子' => ' chuán zǐ', + '钻石' => ' zuàn shí', + '长椅' => ' cháng yǐ', + '空匮' => ' kòng kuì', + '看管' => ' kān guǎn', + '长期' => ' cháng qī', + '长于' => ' cháng yú', + '长揖' => ' cháng yī', + '长德' => ' cháng dé', + '眄睐' => ' miàn lài', + '长得' => ' zhǎng de', + '乐经' => ' yuè jīng', + '支应' => ' zhī yìng', + '长度' => ' cháng dù', + '新款' => ' xīn kuǎn', + '省得' => ' shěng de', + '新兴' => ' xīn xīng', + '长崎' => ' cháng qí', + '打中' => ' dǎ zhòng', + '长安' => ' cháng ān', + '色长' => ' sè cháng', + '长夜' => ' cháng yè', + '杯筊' => ' bēi jiǎo', + '吹擂' => ' chuī lèi', + '应分' => ' yīng fèn', + '兴办' => ' xīng bàn', + '应对' => ' yìng duì', + '兴头' => ' xìng tou', + '串子' => ' chuàn zǐ', + '凭藉' => ' píng jiè', + '应市' => ' yìng shì', + '兴尽' => ' xìng jìn', + '陡削' => ' dǒu xiāo', + '降服' => ' xiáng fú', + '随行' => ' suí háng', + '降伏' => ' xiáng fú', + '兴海' => ' xīng hǎi', + '吹弹' => ' chuī tán', + '应报' => ' yìng bào', + '闲散' => ' xián sǎn', + '雅相' => ' yǎ xiàng', + '碰倒' => ' pèng dǎo', + '兴国' => ' xīng guó', + '林甸' => ' lín diàn', + '成佛' => ' chéng fó', + '应制' => ' yìng zhì', + '松菌' => ' sōng jùn', + '禁受' => ' jīn shòu', + '禅位' => ' shàn wèi', + '槐荫' => ' huái yìn', + '抽干' => ' chōu gān', + '应考' => ' yìng kǎo', + '腰椎' => ' yāo zhuī', + '散养' => ' sǎn yǎng', + '兴师' => ' xīng shī', + '爱将' => ' ài jiàng', + '冲子' => ' chòng zi', + '锤头' => ' chuí tou', + '慌得' => ' huāng de', + '花着' => ' huā zhāo', + '榜笞' => ' bàng chī', + '福相' => ' fú xiàng', + '处斩' => ' chǔ zhǎn', + '应卯' => ' yìng mǎo', + '兴宾' => ' xīng bīn', + '禾场' => ' hé cháng', + '处刑' => ' chǔ xíng', + '禁用' => ' jīn yòng', + '阳萎' => ' yáng wěi', + '糳' => ' zuò', + '糵' => ' niè', + '糶' => ' tiào', + '糹' => ' sī', + '糷' => ' làn', + '糸' => ' mì', + '系' => ' xì', + '糺' => ' jiū', + '糴' => ' dí', + '紖' => ' zhèn', + '糲' => ' lì', + '糱' => ' niè', + '糰' => ' tuán', + '糯' => ' nuò', + '糮' => ' xiàn', + '糭' => ' zòng', + '糬' => ' shu', + '糫' => ' huán', + '糪' => ' bò', + '糽' => ' zhěng', + '糩' => ' kuài', + '糨' => ' jiàng', + '糼' => ' gōng', + '紒' => ' jì', + '糾' => ' jiū', + '紋' => ' wén', + '紕' => ' pī', + '糦' => ' xī', + '純' => ' chún', + '紓' => ' shū', + '紑' => ' fóu', + '紐' => ' niǔ', + '紏' => ' tǒu', + '紎' => ' zī', + '納' => ' nà', + '紌' => ' qiú', + '紊' => ' wěn', + '糿' => ' yòu', + '紉' => ' rèn', + '紈' => ' wán', + '紇' => ' hé', + '紆' => ' yū', + '紅' => ' hóng', + '約' => ' yuē', + '紃' => ' xún', + '紂' => ' zhòu', + '紁' => ' chà', + '紀' => ' jì', + '糧' => ' liáng', + '粺' => ' bài', + '糥' => ' nuò', + '粿' => ' guǒ', + '糉' => ' zòng', + '糈' => ' xǔ', + '糇' => ' hóu', + '糆' => ' miàn', + '糅' => ' róu', + '糄' => ' biǎn', + '糃' => ' táng', + '糂' => ' sǎn', + '糁' => ' sǎn', + '糀' => ' hua', + '粴' => ' lǐ', + '糋' => ' jiàn', + '精' => ' jīng', + '粽' => ' zòng', + '粼' => ' lín', + '粻' => ' zhāng', + '粵' => ' yuè', + '粹' => ' cuì', + '粸' => ' qí', + '粷' => ' jú', + '粶' => ' lù', + '紘' => ' hóng', + '糊' => ' hú', + '糌' => ' zān', + '糤' => ' sǎn', + '糙' => ' cāo', + '糣' => ' sǎn', + '糢' => ' mó', + '糡' => ' jiàng', + '糠' => ' kāng', + '糟' => ' zāo', + '糞' => ' fèn', + '糝' => ' sǎn', + '糜' => ' mí', + '糛' => ' táng', + '糚' => ' zhuāng', + '糘' => ' jia', + '糍' => ' cí', + '糗' => ' qiǔ', + '糖' => ' táng', + '糕' => ' gāo', + '糔' => ' xiǔ', + '糓' => ' gǔ', + '糒' => ' bèi', + '糑' => ' nuò', + '糐' => ' fū', + '糏' => ' xiè', + '糎' => ' lí', + '紗' => ' shā', + '絶' => ' jué', + '紙' => ' zhǐ', + '絚' => ' gēng', + '絤' => ' xiàn', + '絣' => ' bēng', + '絢' => ' xuàn', + '絡' => ' luò', + '絠' => ' gǎi', + '絟' => ' quán', + '絞' => ' jiǎo', + '絝' => ' kù', + '絜' => ' jié', + '絛' => ' tāo', + '絙' => ' huán', + '給' => ' gěi', + '絘' => ' cì', + '絗' => ' hú', + '絖' => ' kuàng', + '絕' => ' jué', + '絔' => ' bǎi', + '絓' => ' guà', + '絒' => ' chóu', + '絑' => ' zhū', + '結' => ' jié', + '絏' => ' xiè', + '絥' => ' fú', + '絧' => ' dòng', + '絍' => ' rèn', + '絴' => ' xiáng', + '絽' => ' lǚ', + '粲' => ' càn', + '絼' => ' zhěn', + '絾' => ' chéng', + '絻' => ' miǎn', + '絺' => ' chī', + '絹' => ' juàn', + '絸' => ' jiǎn', + '絷' => ' zhí', + '絵' => ' huì', + '絳' => ' jiàng', + '絨' => ' róng', + '絲' => ' sī', + '統' => ' tǒng', + '絰' => ' dié', + '絯' => ' gāi', + '絮' => ' xù', + '絭' => ' juàn', + '絬' => ' xiè', + '絫' => ' lěi', + '絪' => ' yīn', + '絩' => ' tiào', + '絎' => ' háng', + '経' => ' jīng', + '級' => ' jí', + '紦' => ' ba', + '細' => ' xì', + '累' => ' lèi', + '紮' => ' zā', + '紭' => ' hóng', + '紬' => ' chóu', + '紫' => ' zǐ', + '紪' => ' cǐ', + '紩' => ' zhì', + '紨' => ' fū', + '紧' => ' jǐn', + '紥' => ' zā', + '紲' => ' xiè', + '紤' => ' jiǔ', + '紣' => ' cuì', + '索' => ' suǒ', + '紡' => ' fǎng', + '素' => ' sù', + '紟' => ' jīn', + '紞' => ' dǎn', + '紝' => ' rèn', + '紜' => ' yún', + '紛' => ' fēn', + '紱' => ' fú', + '紳' => ' shēn', + '絋' => ' kuang', + '絀' => ' chù', + '絊' => ' zuì', + '絉' => ' shù', + '絈' => ' mò', + '絇' => ' qú', + '絆' => ' bàn', + '絅' => ' jiōng', + '組' => ' zǔ', + '絃' => ' xián', + '終' => ' zhōng', + '絁' => ' shī', + '紿' => ' dài', + '紴' => ' bō', + '紾' => ' zhěn', + '紽' => ' tuó', + '紼' => ' fú', + '紻' => ' yǎng', + '紺' => ' gàn', + '紹' => ' shào', + '紸' => ' zhù', + '紷' => ' líng', + '紶' => ' qū', + '紵' => ' zhù', + '粳' => ' jīng', + '篯' => ' jiān', + '粱' => ' liáng', + '簣' => ' kuì', + '簭' => ' shì', + '簬' => ' lù', + '簫' => ' xiāo', + '簪' => ' zān', + '簩' => ' láo', + '簨' => ' sǔn', + '簧' => ' huáng', + '簦' => ' dēng', + '簥' => ' jiāo', + '簤' => ' dài', + '簢' => ' mǐn', + '簯' => ' qi', + '簡' => ' jiǎn', + '簠' => ' fǔ', + '簟' => ' diàn', + '簞' => ' dān', + '簝' => ' liáo', + '簜' => ' dàng', + '簛' => ' shāi', + '簚' => ' mì', + '簙' => ' bó', + '簘' => ' xiāo', + '簮' => ' zān', + '簰' => ' pái', + '簖' => ' duàn', + '簽' => ' qiān', + '籇' => ' háo', + '籆' => ' yuè', + '籅' => ' yú', + '籄' => ' kuì', + '籃' => ' lán', + '籂' => ' shi', + '籁' => ' lài', + '籀' => ' zhòu', + '簿' => ' bù', + '簾' => ' lián', + '簼' => ' lóng', + '簱' => ' qi', + '簻' => ' zhuā', + '簺' => ' sài', + '簹' => ' dāng', + '簸' => ' bǒ', + '簷' => ' yán', + '簶' => ' lù', + '簵' => ' dù', + '簴' => ' jù', + '簳' => ' gǎn', + '簲' => ' pái', + '簗' => ' liang', + '簕' => ' lè', + '籉' => ' tái', + '篮' => ' lán', + '篹' => ' zhuàn', + '篸' => ' cǎn', + '篷' => ' péng', + '篶' => ' yān', + '篵' => ' cōng', + '篴' => ' dí', + '篳' => ' bì', + '篲' => ' huì', + '篱' => ' lí', + '篰' => ' bù', + '篭' => ' lóng', + '篻' => ' piǎo', + '篬' => ' qiāng', + '篫' => ' zhù', + '篪' => ' chí', + '篩' => ' shāi', + '篨' => ' chú', + '篧' => ' zhuó', + '篦' => ' bì', + '篤' => ' dǔ', + '綀' => ' shū', + '篥' => ' lì', + '篺' => ' pí', + '篼' => ' dōu', + '簔' => ' suō', + '簉' => ' zào', + '簓' => ' diao', + '簒' => ' cuàn', + '簑' => ' suō', + '簐' => ' niǎn', + '簏' => ' lù', + '簎' => ' cè', + '簍' => ' lǒu', + '簌' => ' sù', + '簋' => ' guǐ', + '簊' => ' jī', + '簈' => ' píng', + '篽' => ' yù', + '簇' => ' cù', + '簆' => ' kòu', + '簅' => ' chǎn', + '簄' => ' hù', + '簃' => ' yí', + '簂' => ' guì', + '簁' => ' shāi', + '簀' => ' zé', + '篿' => ' tuán', + '篾' => ' miè', + '籈' => ' zhēn', + '籊' => ' tì', + '粰' => ' fú', + '粋' => ' cuì', + '粕' => ' pò', + '粔' => ' jù', + '粓' => ' gān', + '粒' => ' lì', + '粑' => ' bā', + '粐' => ' hu', + '粏' => ' tai', + '粎' => ' chǐ', + '粍' => ' zhé', + '粌' => ' yin', + '粊' => ' bì', + '粗' => ' cū', + '粉' => ' fěn', + '粈' => ' rǒu', + '粇' => ' kāng', + '粆' => ' shā', + '粅' => ' wù', + '粄' => ' bǎn', + '粃' => ' bǐ', + '粂' => ' zhai', + '粁' => ' qiān', + '粀' => ' zhang', + '粖' => ' mò', + '粘' => ' zhān', + '籾' => ' ní', + '粥' => ' zhōu', + '粯' => ' xiàn', + '粮' => ' liáng', + '粭' => ' he', + '粬' => ' qū', + '粫' => ' ér', + '粪' => ' fèn', + '粩' => ' lao', + '粨' => ' bǎi', + '粧' => ' zhuāng', + '粦' => ' lín', + '粤' => ' yuè', + '粙' => ' zhòu', + '粣' => ' cè', + '粢' => ' zī', + '粡' => ' tóng', + '粠' => ' hóng', + '粟' => ' sù', + '粞' => ' xī', + '粝' => ' lì', + '粜' => ' tiào', + '粛' => ' sù', + '粚' => ' lí', + '籿' => ' cùn', + '籽' => ' zǐ', + '籋' => ' niè', + '籗' => ' zhuó', + '籡' => ' qie', + '籠' => ' lóng', + '籟' => ' lài', + '籞' => ' yù', + '籝' => ' yíng', + '籜' => ' tuò', + '籛' => ' jiǎn', + '籚' => ' lú', + '籙' => ' lù', + '籘' => ' téng', + '籖' => ' qian', + '籣' => ' lán', + '籕' => ' zhòu', + '籔' => ' sǒu', + '籓' => ' fān', + '籒' => ' zhòu', + '籑' => ' zhuàn', + '籐' => ' téng', + '籏' => ' qi', + '籎' => ' yi', + '籍' => ' jí', + '籌' => ' chóu', + '籢' => ' lián', + '籤' => ' qiān', + '籼' => ' xiān', + '籱' => ' zhuó', + '类' => ' lèi', + '籺' => ' hé', + '籹' => ' nǚ', + '籸' => ' shēn', + '籷' => ' zhé', + '籶' => ' shēn', + '籵' => ' fán', + '籴' => ' dí', + '米' => ' mǐ', + '籲' => ' yù', + '籰' => ' yuè', + '籥' => ' yuè', + '籯' => ' yíng', + '籮' => ' luó', + '籭' => ' sī', + '籬' => ' lí', + '籫' => ' zuǎn', + '籪' => ' duàn', + '籩' => ' biān', + '籨' => ' lián', + '籧' => ' qú', + '籦' => ' zhōng', + '絿' => ' qiú', + '缌' => ' sī', + '綁' => ' bǎng', + '纒' => ' chán', + '纜' => ' lǎn', + '纛' => ' dào', + '纚' => ' lí', + '纙' => ' luò', + '纘' => ' zuǎn', + '纗' => ' zuī', + '纖' => ' xiān', + '纕' => ' rǎng', + '纔' => ' cái', + '纓' => ' yīng', + '纑' => ' lú', + '纞' => ' liàn', + '纐' => ' jiao', + '纏' => ' chán', + '纎' => ' xiān', + '纍' => ' léi', + '續' => ' xù', + '纋' => ' yōu', + '纊' => ' kuàng', + '纉' => ' zuǎn', + '纈' => ' xié', + '纇' => ' lèi', + '纝' => ' léi', + '纟' => ' sī', + '纅' => ' yào', + '纬' => ' wěi', + '纶' => ' lún', + '纵' => ' zòng', + '纴' => ' rèn', + '纳' => ' nà', + '纲' => ' gāng', + '纱' => ' shā', + '纰' => ' pī', + '纯' => ' chún', + '纮' => ' hóng', + '纭' => ' yún', + '纫' => ' rèn', + '纠' => ' jiū', + '纪' => ' jì', + '纩' => ' kuàng', + '纨' => ' wán', + '级' => ' jí', + '约' => ' yuē', + '纥' => ' gē', + '纤' => ' xiān', + '纣' => ' zhòu', + '红' => ' hóng', + '纡' => ' yū', + '纆' => ' mò', + '纄' => ' péng', + '纸' => ' zhǐ', + '繞' => ' rào', + '繨' => ' da', + '繧' => ' yun', + '繦' => ' qiǎng', + '繥' => ' xī', + '繤' => ' zuǎn', + '繣' => ' huà', + '繢' => ' huì', + '繡' => ' xiù', + '繠' => ' ruǐ', + '繟' => ' chǎn', + '繝' => ' jiàn', + '繪' => ' huì', + '繜' => ' zūn', + '繛' => ' chuò', + '繚' => ' liáo', + '繙' => ' fān', + '繘' => ' yù', + '繗' => ' lín', + '繖' => ' sǎn', + '繕' => ' shàn', + '織' => ' zhī', + '繓' => ' zuǒ', + '繩' => ' shéng', + '繫' => ' xì', + '纃' => ' qí', + '繸' => ' suì', + '纂' => ' zuǎn', + '纁' => ' xūn', + '纀' => ' pú', + '繿' => ' lán', + '繾' => ' qiǎn', + '繽' => ' bīn', + '繼' => ' jì', + '繻' => ' xū', + '繺' => ' shǎi', + '繹' => ' yì', + '繷' => ' nǒng', + '繬' => ' sè', + '繶' => ' yì', + '繵' => ' dàn', + '繴' => ' bì', + '繳' => ' jiǎo', + '繲' => ' xiè', + '繱' => ' cōng', + '繰' => ' zǎo', + '繯' => ' huán', + '繮' => ' jiāng', + '繭' => ' jiǎn', + '纷' => ' fēn', + '纹' => ' wén', + '繑' => ' qiāo', + '绺' => ' liǔ', + '缄' => ' jiān', + '缃' => ' xiāng', + '缂' => ' kè', + '缁' => ' zī', + '缀' => ' zhuì', + '绿' => ' lǜ', + '绾' => ' wǎn', + '绽' => ' zhàn', + '综' => ' zōng', + '绻' => ' quǎn', + '绹' => ' táo', + '缆' => ' lǎn', + '绸' => ' chóu', + '绷' => ' běng', + '绶' => ' shòu', + '绵' => ' mián', + '维' => ' wéi', + '绳' => ' shéng', + '绲' => ' gǔn', + '绱' => ' shàng', + '绰' => ' chuò', + '绯' => ' fēi', + '缅' => ' miǎn', + '缇' => ' tí', + '续' => ' xù', + '缕' => ' lǚ', + '缞' => ' cuī', + '篢' => ' lǒng', + '缝' => ' fèng', + '缜' => ' zhěn', + '缛' => ' rù', + '缚' => ' fù', + '缙' => ' jìn', + '缘' => ' yuán', + '缗' => ' mín', + '编' => ' biān', + '缔' => ' dì', + '缈' => ' miǎo', + '缓' => ' huǎn', + '缒' => ' zhuì', + '缑' => ' gōu', + '缐' => ' xiàn', + '缏' => ' biàn', + '缎' => ' duàn', + '缍' => ' duǒ', + '缋' => ' huì', + '缊' => ' yūn', + '缉' => ' jī', + '绮' => ' qǐ', + '绬' => ' yīng', + '纺' => ' fǎng', + '细' => ' xì', + '绐' => ' dài', + '经' => ' jīng', + '绎' => ' yì', + '绍' => ' shào', + '绌' => ' chù', + '绋' => ' fú', + '绊' => ' bàn', + '绉' => ' zhòu', + '终' => ' zhōng', + '织' => ' zhī', + '绅' => ' shēn', + '绒' => ' róng', + '组' => ' zǔ', + '练' => ' liàn', + '绂' => ' fú', + '绁' => ' xiè', + '绀' => ' gàn', + '线' => ' xiàn', + '纾' => ' shū', + '纽' => ' niǔ', + '纼' => ' zhèn', + '纻' => ' zhù', + '绑' => ' bǎng', + '结' => ' jié', + '绫' => ' líng', + '绠' => ' gěng', + '绪' => ' xù', + '绩' => ' jī', +); \ No newline at end of file diff --git a/vendor/overtrue/pinyin/data/words_2 b/vendor/overtrue/pinyin/data/words_2 new file mode 100644 index 0000000..68438c2 --- /dev/null +++ b/vendor/overtrue/pinyin/data/words_2 @@ -0,0 +1,8003 @@ + ' tí', + '继' => ' jì', + '绦' => ' tāo', + '绥' => ' suí', + '绤' => ' xì', + '绣' => ' xiù', + '绢' => ' juàn', + '绡' => ' xiāo', + '统' => ' tǒng', + '绔' => ' kù', + '绞' => ' jiǎo', + '绝' => ' jué', + '络' => ' luò', + '绛' => ' jiàng', + '绚' => ' xuàn', + '给' => ' gěi', + '绘' => ' huì', + '绗' => ' háng', + '绖' => ' dié', + '绕' => ' rào', + '繒' => ' zēng', + '繐' => ' suì', + '綂' => ' tǒng', + '緂' => ' tián', + '緌' => ' ruí', + '緋' => ' fēi', + '緊' => ' jǐn', + '緉' => ' liǎng', + '緈' => ' xìng', + '緇' => ' zī', + '緆' => ' xī', + '緅' => ' zōu', + '緄' => ' gǔn', + '緃' => ' zōng', + '緁' => ' qiè', + '緎' => ' yù', + '緀' => ' qī', + '綿' => ' mián', + '綾' => ' líng', + '綽' => ' chuò', + '綼' => ' bì', + '綻' => ' zhàn', + '綺' => ' qǐ', + '綹' => ' liǔ', + '綸' => ' lún', + '綷' => ' cuì', + '緍' => ' mín', + '総' => ' zǒng', + '綵' => ' cǎi', + '緜' => ' mián', + '緦' => ' sī', + '緥' => ' bǎo', + '緤' => ' xiè', + '緣' => ' yuán', + '緢' => ' miáo', + '緡' => ' mín', + '締' => ' dì', + '緟' => ' chóng', + '緞' => ' duàn', + '緝' => ' jī', + '緛' => ' ruǎn', + '緐' => ' fán', + '線' => ' xiàn', + '緙' => ' kè', + '緘' => ' jiān', + '緗' => ' xiāng', + '緖' => ' xù', + '緕' => ' qi', + '緔' => ' shàng', + '緓' => ' yīng', + '緒' => ' xù', + '緑' => ' lǜ', + '綶' => ' guǒ', + '綴' => ' zhuì', + '編' => ' biān', + '綎' => ' tīng', + '綘' => ' féng', + '綗' => ' jiǒng', + '綖' => ' yán', + '綕' => ' zhī', + '綔' => ' hù', + '經' => ' jīng', + '綒' => ' fū', + '綑' => ' kǔn', + '綐' => ' duì', + '綏' => ' suī', + '綍' => ' fú', + '続' => ' xu', + '綌' => ' xì', + '綋' => ' hóng', + '綊' => ' xié', + '綉' => ' tòu', + '綈' => ' tí', + '綇' => ' xiǔ', + '綆' => ' gěng', + '綅' => ' qīn', + '綄' => ' huán', + '綃' => ' xiāo', + '継' => ' jì', + '綛' => ' ren', + '綳' => ' bēng', + '綨' => ' qí', + '網' => ' wǎng', + '綱' => ' gāng', + '綰' => ' wǎn', + '綯' => ' táo', + '綮' => ' qǐ', + '維' => ' wéi', + '綬' => ' shòu', + '綫' => ' xiàn', + '綪' => ' qiàn', + '綩' => ' wǎn', + '綧' => ' zhǔn', + '綜' => ' zōng', + '綦' => ' qí', + '綥' => ' qí', + '綤' => ' shào', + '綣' => ' quǎn', + '綢' => ' chóu', + '綡' => ' liáng', + '綠' => ' lǜ', + '綟' => ' lì', + '綞' => ' duǒ', + '綝' => ' chēn', + '緧' => ' qiū', + '緩' => ' huǎn', + '繏' => ' xuàn', + '縪' => ' bì', + '縴' => ' qiàn', + '縳' => ' juàn', + '縲' => ' léi', + '縱' => ' zòng', + '縰' => ' xǐ', + '縯' => ' yǎn', + '縮' => ' suō', + '縭' => ' lí', + '縬' => ' cù', + '縫' => ' fèng', + '縩' => ' cài', + '縶' => ' zhí', + '縨' => ' huang', + '縧' => ' tāo', + '縦' => ' zòng', + '縥' => ' zhěn', + '縤' => ' sù', + '縣' => ' xiàn', + '縢' => ' téng', + '縡' => ' zài', + '縠' => ' hú', + '縟' => ' rù', + '縵' => ' màn', + '縷' => ' lǚ', + '縝' => ' chēn', + '繄' => ' yī', + '繎' => ' rán', + '繍' => ' xiù', + '繌' => ' sha', + '繋' => ' jì', + '繊' => ' xian', + '繉' => ' hún', + '繈' => ' qiǎng', + '繇' => ' yáo', + '繆' => ' móu', + '繅' => ' sāo', + '繃' => ' běng', + '縸' => ' mù', + '繂' => ' lǜ', + '繁' => ' fán', + '繀' => ' suì', + '縿' => ' shān', + '績' => ' jī', + '總' => ' zǒng', + '縼' => ' xuàn', + '縻' => ' mí', + '縺' => ' lián', + '縹' => ' piǎo', + '縞' => ' gǎo', + '縜' => ' yún', + '緪' => ' gēng', + '緶' => ' biàn', + '縀' => ' xiá', + '緿' => ' dài', + '緾' => ' chán', + '緽' => ' chēng', + '緼' => ' yùn', + '緻' => ' zhì', + '緺' => ' guā', + '緹' => ' tí', + '緸' => ' yīn', + '緷' => ' yùn', + '緵' => ' zōng', + '縂' => ' zǒng', + '練' => ' liàn', + '緳' => ' xié', + '緲' => ' miǎo', + '緱' => ' gōu', + '緰' => ' tóu', + '緯' => ' wěi', + '緮' => ' fù', + '緭' => ' wèi', + '緬' => ' miǎn', + '緫' => ' cōng', + '縁' => ' yuán', + '縃' => ' xū', + '縛' => ' fù', + '縐' => ' zhòu', + '縚' => ' tāo', + '縙' => ' róng', + '縘' => ' xī', + '縗' => ' cuī', + '縖' => ' xiá', + '縕' => ' yūn', + '縔' => ' shuǎng', + '縓' => ' quán', + '縒' => ' cī', + '縑' => ' jiān', + '縏' => ' pán', + '縄' => ' ying', + '縎' => ' gǔ', + '縍' => ' bāng', + '縌' => ' nì', + '縋' => ' zhuì', + '縊' => ' yì', + '縉' => ' jìn', + '縈' => ' yíng', + '縇' => ' xuān', + '縆' => ' gēng', + '縅' => ' wei', + '篣' => ' péng', + '磅' => ' bàng', + '篡' => ' cuàn', + '禲' => ' lì', + '禼' => ' xiè', + '离' => ' lí', + '禺' => ' yú', + '禹' => ' yǔ', + '禸' => ' róu', + '禷' => ' lèi', + '禶' => ' zàn', + '禵' => ' tí', + '禴' => ' yuè', + '禳' => ' ráng', + '禱' => ' dǎo', + '禾' => ' hé', + '禰' => ' mí', + '禯' => ' nóng', + '禮' => ' lǐ', + '禭' => ' suì', + '禬' => ' guì', + '禫' => ' dàn', + '禪' => ' chán', + '禩' => ' sì', + '禨' => ' jī', + '禧' => ' xǐ', + '禽' => ' qín', + '禿' => ' tū', + '禥' => ' qí', + '秌' => ' qiū', + '秖' => ' zhī', + '秕' => ' bǐ', + '秔' => ' jīng', + '秓' => ' zhī', + '秒' => ' miǎo', + '科' => ' kē', + '秐' => ' yún', + '秏' => ' hào', + '秎' => ' fèn', + '种' => ' zhǒng', + '秋' => ' qiū', + '秀' => ' xiù', + '秊' => ' nián', + '秉' => ' bǐng', + '秈' => ' xiān', + '秇' => ' yì', + '秆' => ' gǎn', + '秅' => ' chá', + '秄' => ' zǐ', + '秃' => ' tū', + '秂' => ' rén', + '私' => ' sī', + '禦' => ' yù', + '禤' => ' xuān', + '秘' => ' mì', + '祾' => ' líng', + '禈' => ' huī', + '禇' => ' zhě', + '禆' => ' bēi', + '禅' => ' chán', + '禄' => ' lù', + '禃' => ' zhí', + '禂' => ' dǎo', + '禁' => ' jìn', + '禀' => ' bǐng', + '祿' => ' lù', + '祽' => ' zuì', + '禊' => ' xì', + '祼' => ' guàn', + '祻' => ' gù', + '祺' => ' qí', + '祹' => ' táo', + '祸' => ' huò', + '祷' => ' dǎo', + '祶' => ' dì', + '祵' => ' kǔn', + '祴' => ' gāi', + '祳' => ' shèn', + '禉' => ' yǒu', + '禋' => ' yīn', + '禣' => ' fu', + '禘' => ' dì', + '禢' => ' tà', + '禡' => ' mà', + '禠' => ' sī', + '禟' => ' táng', + '禞' => ' gào', + '禝' => ' jí', + '禜' => ' yǒng', + '禛' => ' zhēn', + '禚' => ' zhuó', + '禙' => ' bei', + '禗' => ' sī', + '禌' => ' zī', + '禖' => ' méi', + '禕' => ' yī', + '禔' => ' zhī', + '禓' => ' yáng', + '禒' => ' xiǎn', + '禑' => ' wú', + '禐' => ' yuàn', + '福' => ' fú', + '禎' => ' zhēn', + '禍' => ' huò', + '秗' => ' yù', + '秙' => ' kù', + '祱' => ' shuì', + '稚' => ' zhì', + '稤' => ' lüè', + '稣' => ' sū', + '稢' => ' yù', + '稡' => ' zuì', + '稠' => ' chóu', + '稟' => ' bǐng', + '稞' => ' kē', + '稝' => ' péng', + '稜' => ' léng', + '稛' => ' kǔn', + '稙' => ' zhī', + '稦' => ' yī', + '稘' => ' jī', + '稗' => ' bài', + '稖' => ' bàng', + '稕' => ' zhùn', + '稔' => ' rěn', + '稓' => ' zuó', + '稒' => ' gù', + '稑' => ' lù', + '稐' => ' lǔn', + '稏' => ' yà', + '稥' => ' xiāng', + '稧' => ' xì', + '稍' => ' shāo', + '稴' => ' xián', + '稾' => ' gǎo', + '稽' => ' jī', + '稼' => ' jià', + '稻' => ' dào', + '稺' => ' zhì', + '稹' => ' zhěn', + '稸' => ' xù', + '稷' => ' jì', + '稶' => ' yù', + '稵' => ' zī', + '稳' => ' wěn', + '稨' => ' biǎn', + '稲' => ' dào', + '稱' => ' chēng', + '稰' => ' xǔ', + '稯' => ' zōng', + '種' => ' zhǒng', + '稭' => ' jiē', + '稬' => ' nuò', + '稫' => ' pì', + '稪' => ' fú', + '稩' => ' jì', + '税' => ' shuì', + '稌' => ' tú', + '秚' => ' bàn', + '秦' => ' qín', + '称' => ' chēng', + '积' => ' jī', + '秮' => ' huó', + '秭' => ' zǐ', + '秬' => ' jù', + '秫' => ' shú', + '秪' => ' zhī', + '秩' => ' zhì', + '秨' => ' zuó', + '秧' => ' yāng', + '秥' => ' nián', + '秲' => ' zhì', + '秤' => ' chèng', + '秣' => ' mò', + '秢' => ' líng', + '秡' => ' bó', + '秠' => ' pī', + '租' => ' zū', + '秞' => ' yóu', + '秝' => ' lì', + '秜' => ' ní', + '秛' => ' pī', + '秱' => ' tóng', + '秳' => ' huó', + '程' => ' chéng', + '稀' => ' xī', + '稊' => ' tí', + '稉' => ' jīng', + '稈' => ' gǎn', + '稇' => ' kǔn', + '稆' => ' lǚ', + '稅' => ' shuì', + '稄' => ' xùn', + '稃' => ' fū', + '稂' => ' láng', + '稁' => ' kǎo', + '秿' => ' fù', + '秴' => ' hé', + '秾' => ' nóng', + '秽' => ' huì', + '秼' => ' zhū', + '移' => ' yí', + '秺' => ' dù', + '秹' => ' rěn', + '秸' => ' jiē', + '秷' => ' zhì', + '秶' => ' zī', + '秵' => ' yīn', + '祲' => ' jìn', + '祰' => ' gào', + '穀' => ' gǔ', + '磡' => ' kàn', + '磫' => ' zōng', + '磪' => ' cuī', + '磩' => ' qì', + '磨' => ' mó', + '磧' => ' qì', + '磦' => ' biāo', + '磥' => ' lěi', + '磤' => ' yǐn', + '磣' => ' chěn', + '磢' => ' chuǎng', + '磠' => ' lǔ', + '磭' => ' chuò', + '磟' => ' liù', + '磞' => ' pēng', + '磝' => ' áo', + '磜' => ' qì', + '磛' => ' chán', + '磚' => ' zhuān', + '磙' => ' gǔn', + '磘' => ' qìng', + '磗' => ' zhuān', + '磖' => ' lá', + '磬' => ' qìng', + '磮' => ' lun', + '磔' => ' zhé', + '磻' => ' pán', + '礅' => ' dūn', + '礄' => ' qiáo', + '礃' => ' zhǎng', + '礂' => ' xi', + '礁' => ' jiāo', + '礀' => ' jian', + '磿' => ' lì', + '磾' => ' dī', + '磽' => ' qiāo', + '磼' => ' zá', + '磺' => ' huáng', + '磯' => ' jī', + '磹' => ' tán', + '磸' => ' dìng', + '磷' => ' lín', + '磶' => ' xì', + '磵' => ' jiàn', + '磴' => ' dèng', + '磳' => ' zēng', + '磲' => ' qú', + '磱' => ' láo', + '磰' => ' shàn', + '磕' => ' kē', + '磓' => ' duī', + '礇' => ' yù', + '碬' => ' xiá', + '碶' => ' qì', + '碵' => ' tian', + '碴' => ' chá', + '碳' => ' tàn', + '碲' => ' dì', + '碱' => ' jiǎn', + '碰' => ' pèng', + '碯' => ' nǎo', + '碮' => ' tí', + '碭' => ' dàng', + '碫' => ' duàn', + '碸' => ' fēng', + '碪' => ' zhēn', + '碩' => ' shuò', + '碨' => ' wèi', + '碧' => ' bì', + '碦' => ' kè', + '碥' => ' biǎn', + '碤' => ' yīng', + '碣' => ' jié', + '碢' => ' tuó', + '缠' => ' chán', + '碷' => ' dun', + '碹' => ' xuàn', + '磒' => ' yǔn', + '磇' => ' pī', + '磑' => ' wéi', + '磐' => ' pán', + '磏' => ' lián', + '磎' => ' xī', + '磍' => ' xiá', + '磌' => ' tián', + '磋' => ' cuō', + '磊' => ' lěi', + '磉' => ' sǎng', + '磈' => ' wěi', + '磆' => ' huá', + '確' => ' què', + '磄' => ' táng', + '磃' => ' sī', + '磂' => ' liú', + '磁' => ' cí', + '磀' => ' é', + '碿' => ' sù', + '碾' => ' niǎn', + '碽' => ' gōng', + '碼' => ' mǎ', + '碻' => ' què', + '礆' => ' jiǎn', + '礈' => ' zhuì', + '祯' => ' zhēn', + '祊' => ' bēng', + '祔' => ' fù', + '祓' => ' fú', + '祒' => ' tiáo', + '祑' => ' zhì', + '祐' => ' yòu', + '祏' => ' shí', + '祎' => ' yī', + '祍' => ' rèn', + '祌' => ' zhòng', + '祋' => ' duì', + '祉' => ' zhǐ', + '祖' => ' zǔ', + '祈' => ' qí', + '祇' => ' qí', + '祆' => ' xiān', + '祅' => ' yāo', + '祄' => ' xiè', + '祃' => ' mà', + '祂' => ' tā', + '祁' => ' qí', + '祀' => ' sì', + '礿' => ' yuè', + '祕' => ' mì', + '祗' => ' zhī', + '礽' => ' réng', + '祤' => ' yǔ', + '祮' => ' gào', + '祭' => ' jì', + '祬' => ' zhī', + '祫' => ' xiá', + '祪' => ' guǐ', + '祩' => ' zhù', + '票' => ' piào', + '祧' => ' tiāo', + '祦' => ' wú', + '祥' => ' xiáng', + '祣' => ' lǚ', + '祘' => ' suàn', + '祢' => ' mí', + '祡' => ' chái', + '祠' => ' cí', + '祟' => ' suì', + '神' => ' shén', + '祝' => ' zhù', + '祜' => ' hù', + '祛' => ' qū', + '祚' => ' zuò', + '祙' => ' mèi', + '社' => ' shè', + '礼' => ' lǐ', + '礉' => ' hé', + '礕' => ' pī', + '礟' => ' pào', + '礞' => ' méng', + '礝' => ' ruǎn', + '礜' => ' yù', + '礛' => ' jiān', + '礚' => ' kē', + '礙' => ' ài', + '礘' => ' è', + '礗' => ' pīn', + '礖' => ' yù', + '礔' => ' pī', + '礡' => ' bó', + '礓' => ' jiāng', + '礒' => ' yǐ', + '礑' => ' dàng', + '礐' => ' què', + '礏' => ' yè', + '礎' => ' chǔ', + '礍' => ' kě', + '礌' => ' léi', + '礋' => ' zé', + '礊' => ' kè', + '礠' => ' cí', + '礢' => ' yang', + '礻' => ' shì', + '礰' => ' lì', + '示' => ' shì', + '礹' => ' yán', + '礸' => ' zǎn', + '礷' => ' lán', + '礶' => ' guàn', + '礵' => ' shuāng', + '礴' => ' bó', + '礳' => ' mò', + '礲' => ' lóng', + '礱' => ' lóng', + '礯' => ' yīng', + '礣' => ' mà', + '礮' => ' pào', + '礭' => ' què', + '礬' => ' fán', + '礫' => ' lì', + '礪' => ' lì', + '礩' => ' zhì', + '礨' => ' lěi', + '礧' => ' léi', + '礦' => ' kuàng', + '礥' => ' xián', + '礤' => ' cǎ', + '稿' => ' gǎo', + '穁' => ' róng', + '篠' => ' xiǎo', + '筓' => ' jī', + '筝' => ' zhēng', + '筜' => ' dāng', + '筛' => ' shāi', + '筚' => ' bì', + '筙' => ' lái', + '筘' => ' kòu', + '筗' => ' zhòng', + '策' => ' cè', + '筕' => ' háng', + '答' => ' dá', + '筒' => ' tǒng', + '筟' => ' fū', + '筑' => ' zhù', + '筐' => ' kuāng', + '筏' => ' fá', + '筎' => ' rú', + '筍' => ' sǔn', + '筌' => ' quán', + '筋' => ' jīn', + '筊' => ' xiáo', + '等' => ' děng', + '筈' => ' kuò', + '筞' => ' cè', + '筠' => ' yún', + '筆' => ' bǐ', + '筭' => ' suàn', + '筷' => ' kuài', + '筶' => ' gào', + '筵' => ' yán', + '筴' => ' cè', + '筳' => ' tíng', + '筲' => ' shāo', + '筱' => ' xiǎo', + '筰' => ' zuó', + '筯' => ' zhù', + '筮' => ' shì', + '筬' => ' chéng', + '筡' => ' tú', + '筫' => ' zhì', + '筪' => ' xiá', + '筩' => ' tóng', + '筨' => ' hán', + '筧' => ' jiǎn', + '筦' => ' guǎn', + '筥' => ' jǔ', + '筤' => ' láng', + '筣' => ' lí', + '筢' => ' pá', + '筇' => ' qióng', + '筅' => ' xiǎn', + '筹' => ' chóu', + '笟' => ' gū', + '笩' => ' pèi', + '笨' => ' bèn', + '笧' => ' cè', + '符' => ' fú', + '笥' => ' sì', + '笤' => ' tiáo', + '笣' => ' bāo', + '笢' => ' mǐn', + '笡' => ' qiè', + '笠' => ' lì', + '笞' => ' chī', + '笫' => ' zǐ', + '笝' => ' nà', + '笜' => ' zhú', + '笛' => ' dí', + '笚' => ' dā', + '笙' => ' shēng', + '笘' => ' shān', + '笗' => ' dōng', + '笖' => ' yǐ', + '笕' => ' jiǎn', + '笔' => ' bǐ', + '笪' => ' dá', + '第' => ' dì', + '筄' => ' yào', + '笹' => ' ti', + '筃' => ' yīn', + '筂' => ' chí', + '筁' => ' qū', + '筀' => ' guì', + '笿' => ' luò', + '笾' => ' biān', + '笽' => ' min', + '笼' => ' lóng', + '笻' => ' qióng', + '笺' => ' jiān', + '笸' => ' pǒ', + '笭' => ' líng', + '笷' => ' mǎo', + '笶' => ' shǐ', + '笵' => ' fàn', + '笴' => ' gǎn', + '笳' => ' jiā', + '笲' => ' fán', + '笱' => ' gǒu', + '笰' => ' fú', + '笯' => ' nú', + '笮' => ' zé', + '筸' => ' gān', + '筺' => ' kuang', + '笒' => ' cén', + '箻' => ' lǜ', + '篅' => ' chuán', + '範' => ' fàn', + '篃' => ' mèi', + '篂' => ' xīng', + '篁' => ' huáng', + '節' => ' jié', + '箿' => ' jí', + '箾' => ' shuò', + '箽' => ' dǒng', + '箼' => ' wū', + '箺' => ' chūn', + '篇' => ' piān', + '箹' => ' yuē', + '箸' => ' zhù', + '箷' => ' yí', + '箶' => ' hú', + '箵' => ' xīng', + '箴' => ' zhēn', + '箳' => ' píng', + '箲' => ' xiǎn', + '箱' => ' xiāng', + '箰' => ' sǔn', + '篆' => ' zhuàn', + '篈' => ' fēng', + '箮' => ' xuān', + '篕' => ' hé', + '篟' => ' qiàn', + '篞' => ' niè', + '篝' => ' gōu', + '篜' => ' zhēng', + '篛' => ' ruò', + '篚' => ' fěi', + '篙' => ' gāo', + '篘' => ' chōu', + '篗' => ' yuè', + '篖' => ' táng', + '篔' => ' yún', + '築' => ' zhú', + '篓' => ' lǒu', + '篒' => ' shi', + '篑' => ' kuì', + '篐' => ' gu', + '篏' => ' qiàn', + '篎' => ' miǎo', + '篍' => ' qiū', + '篌' => ' hóu', + '篋' => ' qiè', + '篊' => ' huáng', + '箯' => ' biān', + '箭' => ' jiàn', + '筻' => ' gàng', + '箇' => ' gè', + '箑' => ' shà', + '箐' => ' qìng', + '箏' => ' zhēng', + '箎' => ' chí', + '箍' => ' gū', + '箌' => ' dào', + '箋' => ' jiān', + '箊' => ' yū', + '箉' => ' guǎi', + '箈' => ' tái', + '箆' => ' bì', + '箓' => ' lù', + '箅' => ' bì', + '箄' => ' bǐ', + '箃' => ' zōu', + '箂' => ' lái', + '箁' => ' póu', + '简' => ' jiǎn', + '筿' => ' xiǎo', + '签' => ' qiān', + '筽' => ' o', + '筼' => ' yún', + '箒' => ' zhǒu', + '箔' => ' bó', + '箬' => ' ruò', + '管' => ' guǎn', + '箫' => ' xiāo', + '箪' => ' dān', + '箩' => ' luó', + '箨' => ' tuò', + '箧' => ' qiè', + '箦' => ' zé', + '箥' => ' bǒ', + '箤' => ' zú', + '箣' => ' cè', + '箢' => ' yuān', + '箠' => ' chuí', + '箕' => ' jī', + '箟' => ' jùn', + '箞' => ' qiān', + '箝' => ' qián', + '箜' => ' kōng', + '箛' => ' gū', + '箚' => ' zhá', + '箙' => ' fú', + '箘' => ' jùn', + '算' => ' suàn', + '箖' => ' lín', + '笓' => ' bì', + '笑' => ' xiào', + '穂' => ' suì', + '窂' => ' láo', + '窌' => ' jiào', + '窋' => ' zhú', + '窊' => ' wā', + '窉' => ' bǐng', + '窈' => ' yǎo', + '窇' => ' báo', + '窆' => ' biǎn', + '窅' => ' yǎo', + '窄' => ' zhǎi', + '窃' => ' qiè', + '突' => ' tū', + '窎' => ' diào', + '窀' => ' zhūn', + '穿' => ' chuān', + '穾' => ' yào', + '穽' => ' jǐng', + '穼' => ' shēn', + '穻' => ' yū', + '空' => ' kōng', + '穹' => ' qióng', + '穸' => ' xī', + '穷' => ' qióng', + '窍' => ' qiào', + '窏' => ' wū', + '穵' => ' wā', + '窜' => ' cuàn', + '窦' => ' dòu', + '窥' => ' kuī', + '窤' => ' guān', + '窣' => ' sū', + '窢' => ' xū', + '窡' => ' zhuó', + '窠' => ' kē', + '窟' => ' kū', + '窞' => ' dàn', + '窝' => ' wō', + '窛' => ' kòu', + '窐' => ' guī', + '窚' => ' chéng', + '窙' => ' xiāo', + '窘' => ' jiǒng', + '窗' => ' chuāng', + '窖' => ' jiào', + '窕' => ' tiǎo', + '窔' => ' yào', + '窓' => ' chuāng', + '窒' => ' zhì', + '窑' => ' yáo', + '究' => ' jiū', + '穴' => ' xué', + '窨' => ' xūn', + '穎' => ' yǐng', + '穘' => ' xiāo', + '穗' => ' suì', + '穖' => ' jǐ', + '穕' => ' qiè', + '穔' => ' huáng', + '穓' => ' yì', + '穒' => ' hè', + '穑' => ' sè', + '穐' => ' qiū', + '穏' => ' wěn', + '積' => ' jī', + '穚' => ' jiāo', + '穌' => ' sū', + '穋' => ' lù', + '穊' => ' jì', + '穉' => ' zhì', + '穈' => ' méi', + '穇' => ' cǎn', + '穆' => ' mù', + '穅' => ' kāng', + '穄' => ' jì', + '穃' => ' rong', + '穙' => ' pú', + '穛' => ' zhuō', + '穳' => ' cuán', + '穨' => ' tuí', + '穲' => ' lí', + '穱' => ' zhuō', + '穰' => ' ráng', + '穯' => ' se', + '穮' => ' biāo', + '穭' => ' lǚ', + '穬' => ' kuàng', + '穫' => ' huò', + '穪' => ' chēng', + '穩' => ' wěn', + '穧' => ' jì', + '穜' => ' zhǒng', + '穦' => ' pīn', + '穥' => ' yù', + '穤' => ' nuò', + '穣' => ' ráng', + '穢' => ' huì', + '穡' => ' sè', + '穠' => ' nóng', + '穟' => ' suì', + '穞' => ' lǚ', + '穝' => ' zui', + '窧' => ' zhuo', + '窩' => ' wō', + '笐' => ' háng', + '竫' => ' jìng', + '竵' => ' wāi', + '竴' => ' cūn', + '竳' => ' dēng', + '竲' => ' céng', + '竱' => ' zhuǎn', + '竰' => ' sháo', + '端' => ' duān', + '竮' => ' pīng', + '竭' => ' jié', + '竬' => ' qǔ', + '竪' => ' shù', + '竷' => ' kǎn', + '竩' => ' yì', + '竨' => ' diào', + '竧' => ' jìng', + '竦' => ' sǒng', + '童' => ' tóng', + '竤' => ' hóng', + '竣' => ' jùn', + '竢' => ' sì', + '竡' => ' bai', + '章' => ' zhāng', + '競' => ' jìng', + '竸' => ' jìng', + '竞' => ' jìng', + '笅' => ' xiáo', + '笏' => ' hù', + '笎' => ' yuán', + '笍' => ' zhuì', + '笌' => ' yá', + '笋' => ' sǔn', + '笊' => ' zhào', + '笉' => ' qǐn', + '笈' => ' jí', + '笇' => ' suàn', + '笆' => ' bā', + '笄' => ' jī', + '竹' => ' zhú', + '笃' => ' dǔ', + '笂' => ' wan', + '笁' => ' zhú', + '笀' => ' máng', + '竿' => ' gān', + '竾' => ' chí', + '竽' => ' yú', + '竼' => ' péng', + '竻' => ' lè', + '竺' => ' zhú', + '竟' => ' jìng', + '竝' => ' bìng', + '窪' => ' wā', + '窶' => ' jù', + '竀' => ' chēng', + '窿' => ' lóng', + '窾' => ' kuǎn', + '窽' => ' kuan', + '窼' => ' zhāo', + '窻' => ' chuāng', + '窺' => ' kuī', + '窹' => ' wù', + '窸' => ' xī', + '窷' => ' liào', + '窵' => ' diào', + '竂' => ' piáo', + '窴' => ' tián', + '窳' => ' yǔ', + '窲' => ' cháo', + '窱' => ' tiǎo', + '窰' => ' yáo', + '窯' => ' yáo', + '窮' => ' qióng', + '窭' => ' jù', + '窬' => ' yú', + '窫' => ' yà', + '竁' => ' cuì', + '竃' => ' zào', + '竜' => ' lóng', + '竑' => ' hóng', + '竛' => ' líng', + '竚' => ' zhù', + '站' => ' zhàn', + '竘' => ' qǔ', + '竗' => ' miào', + '竖' => ' shù', + '竕' => ' fen', + '竔' => ' sheng', + '竓' => ' hao', + '竒' => ' qí', + '竐' => ' chù', + '竄' => ' cuàn', + '竏' => ' qian', + '竎' => ' fù', + '竍' => ' shi', + '竌' => ' chù', + '立' => ' lì', + '竊' => ' qiè', + '竉' => ' lǒng', + '竈' => ' zào', + '竇' => ' dòu', + '竆' => ' qióng', + '竅' => ' qiào', + '缟' => ' gǎo', + '蕚' => ' è', + '缡' => ' lí', + '荳' => ' dòu', + '荽' => ' suī', + '荼' => ' tú', + '荻' => ' dí', + '荺' => ' yǔn', + '荹' => ' bù', + '荸' => ' bí', + '荷' => ' hé', + '荶' => ' yín', + '荵' => ' rěn', + '荴' => ' fū', + '荲' => ' lí', + '荿' => ' chéng', + '荱' => ' wěi', + '荰' => ' dù', + '药' => ' yào', + '荮' => ' zhòu', + '荭' => ' hóng', + '荬' => ' mǎi', + '荫' => ' yīn', + '荪' => ' sūn', + '荩' => ' jìn', + '荨' => ' xún', + '荾' => ' suī', + '莀' => ' chén', + '荦' => ' luò', + '莍' => ' qiú', + '莗' => ' chē', + '莖' => ' jīng', + '莕' => ' xìng', + '莔' => ' méng', + '莓' => ' méi', + '莒' => ' jǔ', + '莑' => ' péng', + '莐' => ' chén', + '莏' => ' suō', + '莎' => ' shā', + '莌' => ' tuō', + '莁' => ' wú', + '莋' => ' zuó', + '莊' => ' zhuāng', + '莉' => ' lì', + '莈' => ' mò', + '莇' => ' zhù', + '莆' => ' pú', + '莅' => ' lì', + '莄' => ' gěng', + '莃' => ' xī', + '莂' => ' bié', + '荧' => ' yíng', + '荥' => ' xíng', + '莙' => ' jūn', + '茿' => ' zhú', + '草' => ' cǎo', + '荈' => ' chuǎn', + '荇' => ' xìng', + '荆' => ' jīng', + '荅' => ' dā', + '荄' => ' gāi', + '荃' => ' quán', + '荂' => ' fū', + '荁' => ' huán', + '荀' => ' xún', + '茾' => ' qiān', + '荋' => ' ér', + '茽' => ' zhòng', + '茼' => ' tóng', + '茻' => ' mǎng', + '茺' => ' chōng', + '茹' => ' rú', + '茸' => ' rōng', + '茷' => ' fá', + '茶' => ' chá', + '茵' => ' yīn', + '茴' => ' huí', + '荊' => ' jīng', + '荌' => ' àn', + '荤' => ' hūn', + '荙' => ' dá', + '荣' => ' róng', + '荢' => ' yu', + '荡' => ' dàng', + '荠' => ' jì', + '荟' => ' huì', + '荞' => ' qiáo', + '荝' => ' zé', + '荜' => ' bì', + '荛' => ' ráo', + '荚' => ' jiá', + '荘' => ' zhuāng', + '荍' => ' qiáo', + '荗' => ' shù', + '荖' => ' lǎo', + '荕' => ' jīn', + '荔' => ' lì', + '荓' => ' píng', + '荒' => ' huāng', + '荑' => ' tí', + '荐' => ' jiàn', + '荏' => ' rěn', + '荎' => ' chí', + '莘' => ' shēn', + '莚' => ' yán', + '茲' => ' zī', + '菛' => ' mén', + '菥' => ' xī', + '菤' => ' juǎn', + '菣' => ' qìn', + '菢' => ' bào', + '菡' => ' hàn', + '菠' => ' bō', + '菟' => ' tú', + '菞' => ' lí', + '菝' => ' bá', + '菜' => ' cài', + '菚' => ' zhàn', + '菧' => ' dǐ', + '菙' => ' chuí', + '菘' => ' sōng', + '菗' => ' chóu', + '菖' => ' chāng', + '菕' => ' lún', + '菔' => ' fú', + '菓' => ' guǒ', + '菒' => ' gǎo', + '菑' => ' zāi', + '菐' => ' pú', + '菦' => ' qín', + '菨' => ' jiē', + '菎' => ' kūn', + '菵' => ' wǎng', + '菿' => ' dào', + '菾' => ' tián', + '菽' => ' shū', + '菼' => ' tǎn', + '菻' => ' lǐn', + '菺' => ' jiān', + '菹' => ' jū', + '菸' => ' yān', + '菷' => ' zhǒu', + '菶' => ' běng', + '菴' => ' ān', + '菩' => ' pú', + '菳' => ' qín', + '菲' => ' fēi', + '菱' => ' líng', + '菰' => ' gū', + '華' => ' huá', + '菮' => ' gēng', + '菭' => ' tái', + '菬' => ' qiáo', + '菫' => ' jǐn', + '菪' => ' dàng', + '菏' => ' hé', + '菍' => ' niè', + '莛' => ' tíng', + '莧' => ' xiàn', + '莱' => ' lái', + '莰' => ' kǎn', + '莯' => ' mù', + '莮' => ' nán', + '莭' => ' jié', + '莬' => ' wèn', + '莫' => ' mò', + '莪' => ' é', + '莩' => ' fú', + '莨' => ' làng', + '莦' => ' shāo', + '莳' => ' shí', + '莥' => ' niǔ', + '莤' => ' sù', + '莣' => ' wáng', + '莢' => ' jiá', + '莡' => ' cuò', + '莠' => ' yǒu', + '莟' => ' hàn', + '莞' => ' guǎn', + '莝' => ' cuò', + '莜' => ' yóu', + '莲' => ' lián', + '莴' => ' wō', + '菌' => ' jūn', + '菁' => ' jīng', + '菋' => ' wèi', + '菊' => ' jú', + '菉' => ' lù', + '菈' => ' lā', + '菇' => ' gū', + '菆' => ' zōu', + '菅' => ' jiān', + '菄' => ' dōng', + '菃' => ' qú', + '菂' => ' dì', + '菀' => ' wǎn', + '莵' => ' tu', + '莿' => ' cì', + '莾' => ' mǎng', + '莽' => ' mǎng', + '莼' => ' chún', + '莻' => ' gòng', + '莺' => ' yīng', + '莹' => ' yíng', + '莸' => ' yóu', + '获' => ' huò', + '莶' => ' xiān', + '茳' => ' jiāng', + '茱' => ' zhū', + '萁' => ' qí', + '芣' => ' fú', + '芭' => ' bā', + '芬' => ' fēn', + '芫' => ' yán', + '芪' => ' qí', + '芩' => ' qín', + '芨' => ' jī', + '芧' => ' xù', + '芦' => ' lú', + '芥' => ' jiè', + '芤' => ' kōu', + '芢' => ' rén', + '芯' => ' xīn', + '芡' => ' qiàn', + '芠' => ' wén', + '芟' => ' shān', + '芞' => ' qì', + '芝' => ' zhī', + '芜' => ' wú', + '芛' => ' wěi', + '芚' => ' tún', + '芙' => ' fú', + '芘' => ' pí', + '芮' => ' ruì', + '芰' => ' jì', + '芖' => ' zhì', + '芽' => ' yá', + '苇' => ' wěi', + '苆' => ' qie', + '苅' => ' yì', + '苄' => ' biàn', + '苃' => ' yǒu', + '苂' => ' yín', + '苁' => ' cōng', + '苀' => ' háng', + '芿' => ' rèng', + '芾' => ' fèi', + '芼' => ' mào', + '花' => ' huā', + '芻' => ' chú', + '芺' => ' ǎo', + '芹' => ' qín', + '芸' => ' yún', + '芷' => ' zhǐ', + '芶' => ' gǒu', + '芵' => ' jué', + '芴' => ' wù', + '芳' => ' fāng', + '芲' => ' huā', + '芗' => ' xiāng', + '芕' => ' suī', + '苉' => ' pǐ', + '良' => ' liáng', + '艹' => ' cao', + '艸' => ' cǎo', + '艷' => ' yàn', + '艶' => ' yàn', + '艵' => ' pīng', + '艴' => ' fú', + '艳' => ' yàn', + '色' => ' sè', + '艱' => ' jiān', + '艰' => ' jiān', + '艮' => ' gěn', + '艻' => ' lè', + '艭' => ' shuāng', + '艬' => ' chán', + '艫' => ' lú', + '艪' => ' lǔ', + '艩' => ' qí', + '艨' => ' méng', + '艧' => ' huò', + '艦' => ' jiàn', + '艥' => ' jí', + '艤' => ' yǐ', + '艺' => ' yì', + '艼' => ' tīng', + '芔' => ' huì', + '芉' => ' gān', + '芓' => ' zì', + '芒' => ' máng', + '芑' => ' qǐ', + '芐' => ' hù', + '芏' => ' dù', + '芎' => ' qiōng', + '芍' => ' sháo', + '芌' => ' yù', + '芋' => ' yù', + '芊' => ' qiān', + '芈' => ' mǐ', + '艽' => ' jiāo', + '芇' => ' mián', + '芆' => ' chāi', + '芅' => ' yì', + '芄' => ' wán', + '芃' => ' péng', + '节' => ' jié', + '芁' => ' jiāo', + '芀' => ' tiáo', + '艿' => ' nǎi', + '艾' => ' ài', + '苈' => ' lì', + '苊' => ' è', + '茰' => ' yú', + '茋' => ' dǐ', + '茕' => ' qióng', + '茔' => ' yíng', + '茓' => ' xué', + '茒' => ' yuán', + '茑' => ' niǎo', + '茐' => ' cong', + '茏' => ' lóng', + '茎' => ' jīng', + '茍' => ' jì', + '茌' => ' chí', + '茊' => ' zī', + '茗' => ' míng', + '茉' => ' mò', + '茈' => ' cí', + '茇' => ' bá', + '茆' => ' máo', + '茅' => ' máo', + '茄' => ' jiā', + '范' => ' fàn', + '茂' => ' mào', + '茁' => ' zhuó', + '茀' => ' fú', + '茖' => ' gé', + '茘' => ' lì', + '苾' => ' bì', + '茥' => ' guī', + '茯' => ' fú', + '茮' => ' jiāo', + '茭' => ' jiāo', + '茬' => ' chá', + '茫' => ' máng', + '茪' => ' guāng', + '茩' => ' gòu', + '茨' => ' cí', + '茧' => ' jiǎn', + '茦' => ' cì', + '茤' => ' jì', + '茙' => ' róng', + '茣' => ' wú', + '茢' => ' liè', + '茡' => ' zì', + '茠' => ' hāo', + '茟' => ' yù', + '茞' => ' chén', + '茝' => ' chǎi', + '茜' => ' qiàn', + '茛' => ' gèn', + '茚' => ' yìn', + '苿' => ' wèi', + '苽' => ' gū', + '苋' => ' xiàn', + '苗' => ' miáo', + '苡' => ' yǐ', + '苠' => ' mín', + '苟' => ' gǒu', + '苞' => ' bāo', + '苝' => ' bèi', + '苜' => ' mù', + '苛' => ' kē', + '苚' => ' yòng', + '苙' => ' lì', + '苘' => ' qǐng', + '苖' => ' dí', + '苣' => ' jù', + '苕' => ' sháo', + '苔' => ' tái', + '苓' => ' líng', + '苒' => ' rǎn', + '苑' => ' yuàn', + '苐' => ' tí', + '苏' => ' sū', + '苎' => ' zhù', + '苍' => ' cāng', + '苌' => ' cháng', + '苢' => ' yǐ', + '苤' => ' piě', + '苼' => ' shēng', + '英' => ' yīng', + '苻' => ' fú', + '苺' => ' méi', + '苹' => ' píng', + '苸' => ' hū', + '苷' => ' gān', + '苶' => ' nié', + '苵' => ' dié', + '苴' => ' jū', + '苳' => ' dōng', + '苲' => ' zhǎ', + '苰' => ' hóng', + '若' => ' ruò', + '苯' => ' běn', + '苮' => ' xiān', + '苭' => ' yǎo', + '苬' => ' xiú', + '苫' => ' shān', + '苪' => ' bǐng', + '苩' => ' bó', + '苨' => ' nǐ', + '苧' => ' níng', + '苦' => ' kǔ', + '萀' => ' hǔ', + '萂' => ' hé', + '艢' => ' qiáng', + '蔓' => ' màn', + '蔝' => ' mǐ', + '蔜' => ' áo', + '蔛' => ' hú', + '蔚' => ' wèi', + '蔙' => ' xuàn', + '蔘' => ' shēn', + '蔗' => ' zhè', + '蔖' => ' cuó', + '蔕' => ' dì', + '蔔' => ' bó', + '蔒' => ' xūn', + '蔟' => ' cù', + '蔑' => ' miè', + '蔐' => ' dí', + '蔏' => ' shāng', + '蔎' => ' shè', + '蔍' => ' lù', + '蔌' => ' sù', + '蔋' => ' dí', + '蔊' => ' hǎn', + '蔉' => ' gǔn', + '蔈' => ' biāo', + '蔞' => ' lóu', + '蔠' => ' zhōng', + '蔆' => ' líng', + '蔭' => ' yīn', + '蔷' => ' qiáng', + '蔶' => ' ze', + '蔵' => ' zāng', + '蔴' => ' má', + '蔳' => ' qiàn', + '蔲' => ' kòu', + '蔱' => ' shā', + '蔰' => ' hù', + '蔯' => ' chén', + '蔮' => ' guó', + '蔬' => ' shū', + '蔡' => ' cài', + '蔫' => ' niān', + '蔪' => ' jiàn', + '蔩' => ' yín', + '蔨' => ' juàn', + '蔧' => ' huì', + '蔦' => ' niǎo', + '蔥' => ' cōng', + '蔤' => ' mì', + '蔣' => ' jiǎng', + '蔢' => ' pó', + '蔇' => ' jì', + '蔅' => ' yán', + '蔹' => ' liǎn', + '蓟' => ' jì', + '蓩' => ' mǎo', + '蓨' => ' tiáo', + '蓧' => ' diào', + '蓦' => ' mò', + '蓥' => ' yíng', + '蓤' => ' ling', + '蓣' => ' yù', + '蓢' => ' lǎng', + '蓡' => ' shēn', + '蓠' => ' lí', + '蓞' => ' dàn', + '蓫' => ' chù', + '蓝' => ' lán', + '蓜' => ' pei', + '蓛' => ' cè', + '蓚' => ' tiáo', + '蓙' => ' zuo', + '蓘' => ' gǔn', + '蓗' => ' zǒng', + '蓖' => ' bì', + '蓕' => ' guì', + '蓔' => ' yǎo', + '蓪' => ' tōng', + '蓬' => ' péng', + '蔄' => ' màn', + '蓹' => ' yù', + '蔃' => ' qiáng', + '蔂' => ' léi', + '蔁' => ' zhāng', + '蔀' => ' bù', + '蓿' => ' xu', + '蓾' => ' lǔ', + '蓽' => ' bì', + '蓼' => ' liǎo', + '蓻' => ' zí', + '蓺' => ' yì', + '蓸' => ' cáo', + '蓭' => ' ān', + '蓷' => ' tuī', + '蓶' => ' wéi', + '蓵' => ' jié', + '蓴' => ' chún', + '蓳' => ' jǐn', + '蓲' => ' qiū', + '蓱' => ' píng', + '蓰' => ' xǐ', + '蓯' => ' cōng', + '蓮' => ' lián', + '蔸' => ' dōu', + '蔺' => ' lìn', + '蓒' => ' xuān', + '蕻' => ' hóng', + '薅' => ' hāo', + '薄' => ' báo', + '薃' => ' hào', + '薂' => ' xí', + '薁' => ' yù', + '薀' => ' yùn', + '蕿' => ' xuān', + '蕾' => ' lěi', + '蕽' => ' nóng', + '蕼' => ' sì', + '蕺' => ' jí', + '薇' => ' wēi', + '蕹' => ' wèng', + '蕸' => ' xiá', + '蕷' => ' yù', + '蕶' => ' líng', + '蕵' => ' sūn', + '蕴' => ' yùn', + '蕳' => ' jiān', + '蕲' => ' qí', + '蕱' => ' shao', + '蕰' => ' wēn', + '薆' => ' ài', + '薈' => ' huì', + '蕮' => ' xì', + '薕' => ' lián', + '薟' => ' xiān', + '薞' => ' sūn', + '薝' => ' zhān', + '薜' => ' bì', + '薛' => ' xuē', + '薚' => ' tāng', + '薙' => ' tì', + '薘' => ' dá', + '薗' => ' yuán', + '薖' => ' kē', + '薔' => ' qiáng', + '薉' => ' huì', + '薓' => ' shēn', + '薒' => ' càn', + '薑' => ' jiāng', + '薐' => ' léng', + '薏' => ' yì', + '薎' => ' miè', + '薍' => ' wàn', + '薌' => ' xiāng', + '薋' => ' cí', + '薊' => ' jì', + '蕯' => ' lóng', + '蕭' => ' xiāo', + '蔻' => ' kòu', + '蕇' => ' diǎn', + '蕑' => ' jiān', + '蕐' => ' huá', + '蕏' => ' chú', + '蕎' => ' qiáo', + '蕍' => ' yú', + '蕌' => ' lěi', + '蕋' => ' ruǐ', + '蕊' => ' ruǐ', + '蕉' => ' jiāo', + '蕈' => ' xùn', + '蕆' => ' chǎn', + '蕓' => ' yún', + '蕅' => ' ǒu', + '蕄' => ' méng', + '蕃' => ' fān', + '蕂' => ' shèng', + '蕁' => ' qián', + '蕀' => ' jí', + '蔿' => ' wěi', + '蔾' => ' lí', + '蔽' => ' bì', + '蔼' => ' ǎi', + '蕒' => ' mǎi', + '蕔' => ' bāo', + '蕬' => ' sī', + '蕡' => ' fén', + '蕫' => ' dǒng', + '蕪' => ' wú', + '蕩' => ' dàng', + '蕨' => ' jué', + '蕧' => ' fù', + '蕦' => ' xū', + '蕥' => ' yǎ', + '蕤' => ' ruí', + '蕣' => ' shùn', + '蕢' => ' kuì', + '蕠' => ' rú', + '蕕' => ' yóu', + '蕟' => ' fà', + '蕞' => ' zuì', + '蕝' => ' jué', + '蕜' => ' fěi', + '蕛' => ' tí', + '碠' => ' dìng', + '蕙' => ' huì', + '蕘' => ' ráo', + '蕗' => ' lù', + '蕖' => ' qú', + '蓓' => ' bèi', + '蓑' => ' suō', + '萃' => ' cuì', + '葃' => ' zuò', + '葍' => ' fú', + '葌' => ' jiān', + '葋' => ' qú', + '葊' => ' ān', + '葉' => ' yè', + '葈' => ' xǐ', + '葇' => ' róu', + '葆' => ' bǎo', + '葅' => ' zū', + '葄' => ' zuò', + '葂' => ' miǎn', + '葏' => ' jīng', + '葁' => ' jiāng', + '葀' => ' kuò', + '萿' => ' kuò', + '萾' => ' yíng', + '落' => ' luò', + '萼' => ' è', + '萻' => ' ān', + '萺' => ' mào', + '萹' => ' biǎn', + '萸' => ' yú', + '葎' => ' lǜ', + '葐' => ' pén', + '萶' => ' chǔn', + '葝' => ' qíng', + '葧' => ' bó', + '葦' => ' wěi', + '葥' => ' jiàn', + '葤' => ' zhòu', + '董' => ' dǒng', + '葢' => ' gài', + '葡' => ' pú', + '葠' => ' shēn', + '葟' => ' huáng', + '葞' => ' mǐ', + '葜' => ' qiā', + '葑' => ' fēng', + '葛' => ' gé', + '葚' => ' rèn', + '葙' => ' xiāng', + '葘' => ' zī', + '著' => ' zhe', + '葖' => ' tū', + '葕' => ' yàn', + '葔' => ' hóu', + '葓' => ' hóng', + '葒' => ' hóng', + '萷' => ' xiāo', + '萵' => ' wō', + '葩' => ' pā', + '萏' => ' dàn', + '萙' => ' zhen', + '萘' => ' nài', + '萗' => ' cè', + '萖' => ' wǎn', + '萕' => ' qí', + '萔' => ' tiáo', + '萓' => ' yí', + '萒' => ' yǎn', + '萑' => ' huán', + '萐' => ' shà', + '萎' => ' wēi', + '萛' => ' jiū', + '萍' => ' píng', + '萌' => ' méng', + '萋' => ' qī', + '萊' => ' lái', + '萉' => ' fèi', + '萈' => ' huán', + '萇' => ' cháng', + '萆' => ' bì', + '萅' => ' chūn', + '萄' => ' táo', + '萚' => ' tuò', + '萜' => ' tiē', + '萴' => ' cè', + '萩' => ' qiū', + '萳' => ' nǎn', + '萲' => ' xuān', + '萱' => ' xuān', + '萰' => ' liàn', + '萯' => ' fù', + '萮' => ' yú', + '萭' => ' yǔ', + '萬' => ' wàn', + '萫' => ' xiàng', + '萪' => ' kē', + '萨' => ' sà', + '萝' => ' luó', + '萧' => ' xiāo', + '萦' => ' yíng', + '营' => ' yíng', + '萤' => ' yíng', + '萣' => ' dìng', + '萢' => ' pao', + '萡' => ' bo', + '萠' => ' méng', + '萟' => ' yi', + '萞' => ' bi', + '葨' => ' wēi', + '葪' => ' jì', + '蓐' => ' rù', + '蒫' => ' cuó', + '蒵' => ' xí', + '蒴' => ' shuò', + '蒳' => ' nà', + '蒲' => ' pú', + '蒱' => ' pú', + '蒰' => ' pán', + '蒯' => ' kuǎi', + '蒮' => ' yù', + '蒭' => ' chú', + '蒬' => ' yuān', + '蒪' => ' pò', + '蒷' => ' yún', + '蒩' => ' zū', + '蒨' => ' qiàn', + '蒧' => ' diǎn', + '蒦' => ' huò', + '蒥' => ' liú', + '蒤' => ' tú', + '蒣' => ' xú', + '蒢' => ' chú', + '蒡' => ' bàng', + '蒠' => ' xī', + '蒶' => ' fén', + '蒸' => ' zhēng', + '蒞' => ' lì', + '蓅' => ' liú', + '蓏' => ' luǒ', + '蓎' => ' táng', + '蓍' => ' shī', + '蓌' => ' cuò', + '蓋' => ' gài', + '蓊' => ' wěng', + '蓉' => ' róng', + '蓈' => ' láng', + '蓇' => ' gǔ', + '蓆' => ' xí', + '蓄' => ' xù', + '蒹' => ' jiān', + '蓃' => ' sōu', + '蓂' => ' míng', + '蓁' => ' zhēn', + '蓀' => ' sūn', + '蒿' => ' hāo', + '蒾' => ' mí', + '蒽' => ' ēn', + '蒼' => ' cāng', + '蒻' => ' ruò', + '蒺' => ' jí', + '蒟' => ' jǔ', + '蒝' => ' yuán', + '葫' => ' hú', + '葷' => ' hūn', + '蒁' => ' shù', + '蒀' => ' yūn', + '葿' => ' méi', + '葾' => ' yuān', + '葽' => ' yāo', + '葼' => ' zōng', + '葻' => ' lán', + '葺' => ' qì', + '葹' => ' shī', + '葸' => ' xǐ', + '葶' => ' tíng', + '蒃' => ' zhuàn', + '葵' => ' kuí', + '葴' => ' zhēn', + '葳' => ' wēi', + '葲' => ' quán', + '葱' => ' cōng', + '葰' => ' suī', + '葯' => ' yào', + '葮' => ' duàn', + '葭' => ' jiā', + '葬' => ' zàng', + '蒂' => ' dì', + '蒄' => ' guān', + '蒜' => ' suàn', + '蒑' => ' yìn', + '蒛' => ' quē', + '蒚' => ' lì', + '蒙' => ' méng', + '蒘' => ' rú', + '蒗' => ' làng', + '蒖' => ' zhēn', + '蒕' => ' yūn', + '蒔' => ' shí', + '蒓' => ' chún', + '蒒' => ' shī', + '蒐' => ' sōu', + '蒅' => ' ran', + '蒏' => ' you', + '蒎' => ' pài', + '蒍' => ' wěi', + '蒌' => ' lóu', + '蒋' => ' jiǎng', + '蒊' => ' huā', + '蒉' => ' kuì', + '蒈' => ' kǎi', + '蒇' => ' chǎn', + '蒆' => ' xuē', + '艣' => ' lǔ', + '艡' => ' dāng', + '缢' => ' yì', + '耲' => ' huái', + '耼' => ' dān', + '耻' => ' chǐ', + '耺' => ' yún', + '耹' => ' qín', + '耸' => ' sǒng', + '耷' => ' dā', + '耶' => ' yé', + '耵' => ' dīng', + '耴' => ' yì', + '耳' => ' ěr', + '耱' => ' mò', + '耾' => ' hóng', + '耰' => ' yōu', + '耯' => ' huò', + '耮' => ' lào', + '耭' => ' jī', + '耬' => ' lóu', + '耫' => ' zhá', + '耪' => ' pǎng', + '耩' => ' jiǎng', + '耨' => ' nòu', + '耧' => ' lóu', + '耽' => ' dān', + '耿' => ' gěng', + '耥' => ' tāng', + '职' => ' zhí', + '聖' => ' shèng', + '聕' => ' hào', + '联' => ' lián', + '聓' => ' xu', + '聒' => ' guā', + '聑' => ' tiē', + '聐' => ' yà', + '聏' => ' ér', + '聎' => ' tiāo', + '聍' => ' níng', + '聋' => ' lóng', + '聀' => ' zhí', + '聊' => ' liáo', + '聉' => ' wà', + '聈' => ' yǒu', + '聇' => ' zhēng', + '聆' => ' líng', + '聅' => ' chè', + '聄' => ' zhěn', + '聃' => ' dān', + '聂' => ' niè', + '聁' => ' pàn', + '耦' => ' ǒu', + '耤' => ' jí', + '聘' => ' pìn', + '翾' => ' xuān', + '耈' => ' gǒu', + '耇' => ' gǒu', + '耆' => ' qí', + '者' => ' zhě', + '耄' => ' mào', + '考' => ' kǎo', + '耂' => ' lǎo', + '老' => ' lǎo', + '耀' => ' yào', + '翿' => ' dào', + '翽' => ' huì', + '耊' => ' diè', + '翼' => ' yì', + '翻' => ' fān', + '翺' => ' áo', + '翹' => ' qiào', + '翸' => ' pěn', + '翷' => ' lín', + '翶' => ' ao', + '翵' => ' hóu', + '翴' => ' lián', + '翳' => ' yì', + '耉' => ' gǒu', + '耋' => ' dié', + '耣' => ' lǔn', + '耘' => ' yún', + '耢' => ' lào', + '耡' => ' chú', + '耠' => ' huō', + '耟' => ' jù', + '耞' => ' jiā', + '耝' => ' qù', + '耜' => ' sì', + '耛' => ' yí', + '耚' => ' pī', + '耙' => ' bà', + '耗' => ' hào', + '而' => ' ér', + '耖' => ' chào', + '耕' => ' gēng', + '耔' => ' zǐ', + '耓' => ' tīng', + '耒' => ' lěi', + '耑' => ' duān', + '耐' => ' nài', + '耏' => ' nài', + '耎' => ' ruǎn', + '耍' => ' shuǎ', + '聗' => ' liè', + '聙' => ' jīng', + '翱' => ' áo', + '肚' => ' dù', + '肤' => ' fū', + '肣' => ' hán', + '肢' => ' zhī', + '股' => ' gǔ', + '肠' => ' cháng', + '肟' => ' wò', + '肞' => ' chā', + '肝' => ' gān', + '肜' => ' róng', + '肛' => ' gāng', + '肙' => ' yuàn', + '肦' => ' fén', + '肘' => ' zhǒu', + '肗' => ' rǔ', + '肖' => ' xiào', + '肕' => ' rèn', + '肔' => ' yǐ', + '肓' => ' huāng', + '肒' => ' huàn', + '肑' => ' bó', + '肐' => ' gē', + '肏' => ' cào', + '肥' => ' féi', + '肧' => ' pēi', + '肍' => ' qiú', + '肴' => ' yáo', + '肾' => ' shèn', + '肽' => ' tài', + '肼' => ' jǐng', + '肻' => ' kěn', + '肺' => ' fèi', + '肹' => ' xī', + '肸' => ' xī', + '肷' => ' qiǎn', + '肶' => ' pí', + '肵' => ' qí', + '肳' => ' wěn', + '肨' => ' pàng', + '育' => ' yù', + '肱' => ' gōng', + '肰' => ' rán', + '肯' => ' kěn', + '肮' => ' āng', + '肭' => ' nà', + '肬' => ' yóu', + '肫' => ' zhūn', + '肪' => ' fáng', + '肩' => ' jiān', + '肎' => ' kěn', + '肌' => ' jī', + '聚' => ' jù', + '聦' => ' cōng', + '聰' => ' cōng', + '聯' => ' lián', + '聮' => ' lián', + '聭' => ' kuì', + '聬' => ' wěng', + '聫' => ' lián', + '聪' => ' cōng', + '聩' => ' kuì', + '聨' => ' lian', + '聧' => ' kuī', + '聥' => ' jǔ', + '聲' => ' shēng', + '聤' => ' tíng', + '聣' => ' ní', + '聢' => ' ding', + '聡' => ' cōng', + '聠' => ' pīng', + '聟' => ' xù', + '聞' => ' wén', + '聝' => ' guó', + '聜' => ' dǐ', + '聛' => ' bǐ', + '聱' => ' áo', + '聳' => ' sǒng', + '肋' => ' lē', + '肀' => ' yù', + '肊' => ' yì', + '肉' => ' ròu', + '肈' => ' zhào', + '肇' => ' zhào', + '肆' => ' sì', + '肅' => ' sù', + '肄' => ' yì', + '肃' => ' sù', + '肂' => ' sì', + '肁' => ' zhào', + '聿' => ' yù', + '聴' => ' tīng', + '聾' => ' lóng', + '聽' => ' tīng', + '聼' => ' tīng', + '聻' => ' nǐ', + '聺' => ' qié', + '聹' => ' níng', + '聸' => ' dān', + '職' => ' zhí', + '聶' => ' niè', + '聵' => ' kuì', + '翲' => ' piāo', + '翰' => ' hàn', + '胀' => ' zhàng', + '罢' => ' bà', + '罬' => ' zhuó', + '罫' => ' guà', + '罪' => ' zuì', + '罩' => ' zhào', + '罨' => ' yǎn', + '罧' => ' shèn', + '罦' => ' fú', + '罥' => ' juàn', + '罤' => ' tí', + '罣' => ' guà', + '罡' => ' gāng', + '置' => ' zhì', + '罠' => ' mín', + '罟' => ' gǔ', + '罞' => ' máo', + '罝' => ' jū', + '罜' => ' zhǔ', + '罛' => ' gū', + '罚' => ' fá', + '罙' => ' mí', + '罘' => ' fú', + '罗' => ' luó', + '罭' => ' yù', + '罯' => ' ǎn', + '罕' => ' hǎn', + '罼' => ' bì', + '羆' => ' pí', + '羅' => ' luó', + '羄' => ' zhào', + '羃' => ' mì', + '羂' => ' juàn', + '羁' => ' jī', + '羀' => ' liǔ', + '罿' => ' chōng', + '罾' => ' zēng', + '罽' => ' jì', + '罻' => ' wèi', + '罰' => ' fá', + '罺' => ' cháo', + '罹' => ' lí', + '罸' => ' fá', + '罷' => ' bà', + '罶' => ' liǔ', + '罵' => ' mà', + '罴' => ' pí', + '罳' => ' sī', + '署' => ' shǔ', + '罱' => ' lǎn', + '罖' => ' luó', + '罔' => ' wǎng', + '羈' => ' jī', + '缮' => ' shàn', + '缸' => ' gāng', + '缷' => ' xiè', + '缶' => ' fǒu', + '缵' => ' zuǎn', + '缴' => ' jiǎo', + '缳' => ' huán', + '缲' => ' qiāo', + '缱' => ' qiǎn', + '缰' => ' jiāng', + '缯' => ' zēng', + '缭' => ' liáo', + '缺' => ' quē', + '缬' => ' xié', + '缫' => ' sāo', + '缪' => ' móu', + '缩' => ' suō', + '缨' => ' yīng', + '缧' => ' léi', + '缦' => ' màn', + '缥' => ' piāo', + '缤' => ' bīn', + '缣' => ' jiān', + '缹' => ' fǒu', + '缻' => ' fǒu', + '罓' => ' gāng', + '罈' => ' tán', + '罒' => ' wǎng', + '网' => ' wǎng', + '罐' => ' guàn', + '罏' => ' lú', + '罎' => ' tán', + '罍' => ' léi', + '罌' => ' yīng', + '罋' => ' wèng', + '罊' => ' qì', + '罉' => ' cang', + '罇' => ' zūn', + '缼' => ' qi', + '罆' => ' guàn', + '罅' => ' xià', + '罄' => ' qìng', + '罃' => ' yīng', + '罂' => ' yīng', + '罁' => ' gāng', + '罀' => ' zhao', + '缿' => ' xiàng', + '缾' => ' píng', + '缽' => ' bō', + '羇' => ' jī', + '羉' => ' luán', + '翯' => ' hè', + '翊' => ' yì', + '翔' => ' xiáng', + '翓' => ' xié', + '習' => ' xí', + '翑' => ' qú', + '翐' => ' zhì', + '翏' => ' liù', + '翎' => ' líng', + '翍' => ' pī', + '翌' => ' yì', + '翋' => ' lā', + '翉' => ' běn', + '翖' => ' xì', + '翈' => ' xiá', + '翇' => ' fú', + '翆' => ' cuì', + '翅' => ' chì', + '翄' => ' chì', + '翃' => ' hóng', + '翂' => ' fēn', + '翁' => ' wēng', + '翀' => ' chōng', + '羿' => ' yì', + '翕' => ' xī', + '翗' => ' ké', + '羽' => ' yǔ', + '翤' => ' chì', + '翮' => ' hé', + '翭' => ' hóu', + '翬' => ' huī', + '翫' => ' wán', + '翪' => ' zōng', + '翩' => ' piān', + '翨' => ' chì', + '翧' => ' xuān', + '翦' => ' jiǎn', + '翥' => ' zhù', + '翣' => ' shà', + '翘' => ' qiào', + '翢' => ' dào', + '翡' => ' fěi', + '翠' => ' cuì', + '翟' => ' dí', + '翞' => ' jiāng', + '翝' => ' hóng', + '翜' => ' shà', + '翛' => ' xiāo', + '翚' => ' huī', + '翙' => ' huì', + '羾' => ' gòng', + '羼' => ' chàn', + '羊' => ' yáng', + '羖' => ' gǔ', + '羠' => ' yí', + '羟' => ' qiǎng', + '羞' => ' xiū', + '羝' => ' dī', + '羜' => ' zhù', + '羛' => ' yì', + '羚' => ' líng', + '羙' => ' gāo', + '羘' => ' zāng', + '羗' => ' qiāng', + '羕' => ' yàng', + '羢' => ' róng', + '羔' => ' gāo', + '羓' => ' bā', + '羒' => ' fén', + '羑' => ' yǒu', + '羐' => ' yǒu', + '羏' => ' yáng', + '美' => ' měi', + '羍' => ' dá', + '羌' => ' qiāng', + '羋' => ' mǐ', + '羡' => ' xiàn', + '羣' => ' qún', + '羻' => ' qiàng', + '羰' => ' tāng', + '羺' => ' nóu', + '羹' => ' gēng', + '羸' => ' léi', + '羷' => ' liǎn', + '羶' => ' shān', + '羵' => ' fén', + '羴' => ' shān', + '羳' => ' fán', + '羲' => ' xī', + '羱' => ' yuán', + '羯' => ' jié', + '群' => ' qún', + '羮' => ' gēng', + '羭' => ' yú', + '羬' => ' qián', + '羫' => ' qiāng', + '羪' => ' yang', + '義' => ' yì', + '羨' => ' xiàn', + '羧' => ' suō', + '羦' => ' huán', + '羥' => ' qiǎng', + '肿' => ' zhǒng', + '胁' => ' xié', + '艠' => ' deng', + '臓' => ' zàng', + '臝' => ' luǒ', + '臜' => ' zā', + '臛' => ' huò', + '臚' => ' lú', + '臙' => ' yān', + '臘' => ' là', + '臗' => ' kuān', + '臖' => ' xìng', + '臕' => ' biāo', + '臔' => ' xiàn', + '臒' => ' wò', + '臟' => ' zàng', + '臑' => ' nào', + '臐' => ' xūn', + '臏' => ' bìn', + '臎' => ' cuì', + '臍' => ' qí', + '臌' => ' gǔ', + '臋' => ' tún', + '臊' => ' sāo', + '臉' => ' liǎn', + '臈' => ' là', + '臞' => ' qú', + '臠' => ' luán', + '臆' => ' yì', + '臭' => ' chòu', + '臷' => ' dié', + '臶' => ' jiàn', + '臵' => ' gé', + '致' => ' zhì', + '至' => ' zhì', + '臲' => ' niè', + '臱' => ' mián', + '臰' => ' chòu', + '臯' => ' gāo', + '臮' => ' jì', + '臬' => ' niè', + '臡' => ' ní', + '臫' => ' jiǎo', + '自' => ' zì', + '臩' => ' guǎng', + '臨' => ' lín', + '臧' => ' zāng', + '臦' => ' guàng', + '臥' => ' wò', + '臤' => ' qiān', + '臣' => ' chén', + '臢' => ' zā', + '臇' => ' juǎn', + '臅' => ' chù', + '臹' => ' xiū', + '膟' => ' lǜ', + '膩' => ' nì', + '膨' => ' péng', + '膧' => ' tóng', + '膦' => ' lìn', + '膥' => ' chūn', + '膤' => ' xue', + '膣' => ' zhì', + '膢' => ' lǘ', + '膡' => ' yìng', + '膠' => ' jiāo', + '膞' => ' zhuān', + '膫' => ' liáo', + '膝' => ' xī', + '膜' => ' mó', + '膛' => ' táng', + '膚' => ' fū', + '膙' => ' jiǎng', + '膘' => ' biāo', + '膗' => ' chuái', + '膖' => ' pāng', + '膕' => ' guó', + '膔' => ' lù', + '膪' => ' chuài', + '膬' => ' cuì', + '臄' => ' jué', + '膹' => ' fèn', + '臃' => ' yōng', + '臂' => ' bì', + '臁' => ' lián', + '臀' => ' tún', + '膿' => ' nóng', + '膾' => ' kuài', + '膽' => ' dǎn', + '膼' => ' zhuā', + '膻' => ' shān', + '膺' => ' yīng', + '膸' => ' suǐ', + '膭' => ' guī', + '膷' => ' xiāng', + '膶' => ' rùn', + '膵' => ' cuì', + '膴' => ' hū', + '膳' => ' shàn', + '膲' => ' jiāo', + '膱' => ' zhí', + '膰' => ' fán', + '膯' => ' tēng', + '膮' => ' xiāo', + '臸' => ' zhī', + '臺' => ' tái', + '膒' => ' óu', + '舻' => ' lú', + '艅' => ' yú', + '艄' => ' shāo', + '艃' => ' lí', + '艂' => ' féng', + '艁' => ' zào', + '艀' => ' fú', + '舿' => ' kua', + '舾' => ' xī', + '舽' => ' páng', + '舼' => ' qióng', + '舺' => ' xiá', + '艇' => ' tǐng', + '船' => ' chuán', + '舸' => ' gě', + '舷' => ' xián', + '舶' => ' bó', + '舵' => ' duò', + '舴' => ' zé', + '舳' => ' zhú', + '舲' => ' líng', + '舱' => ' cāng', + '舰' => ' jiàn', + '艆' => ' láng', + '艈' => ' yù', + '舮' => ' lu', + '艕' => ' bàng', + '艟' => ' chōng', + '艞' => ' yào', + '艝' => ' xue', + '艜' => ' dài', + '艛' => ' lóu', + '艚' => ' cáo', + '艙' => ' cāng', + '艘' => ' sōu', + '艗' => ' yì', + '艖' => ' chā', + '艔' => ' dou', + '艉' => ' wěi', + '艓' => ' dié', + '艒' => ' mù', + '艑' => ' biàn', + '艐' => ' kè', + '艏' => ' shǒu', + '艎' => ' huáng', + '艍' => ' jū', + '艌' => ' niàn', + '艋' => ' měng', + '艊' => ' bó', + '舯' => ' zhōng', + '舭' => ' bǐ', + '臻' => ' zhēn', + '與' => ' yǔ', + '舑' => ' tān', + '舐' => ' shì', + '舏' => ' jiǔ', + '舎' => ' she', + '舍' => ' shě', + '舌' => ' shé', + '舋' => ' xìn', + '舊' => ' jiù', + '舉' => ' jǔ', + '興' => ' xìng', + '舆' => ' yú', + '舓' => ' shì', + '舅' => ' jiù', + '舄' => ' xì', + '舃' => ' xì', + '舂' => ' chōng', + '舁' => ' yú', + '舀' => ' yǎo', + '臿' => ' chā', + '臾' => ' yú', + '臽' => ' xiàn', + '臼' => ' jiù', + '舒' => ' shū', + '舔' => ' tiǎn', + '般' => ' bān', + '舡' => ' chuán', + '舫' => ' fǎng', + '航' => ' háng', + '舩' => ' chuán', + '舨' => ' bǎn', + '舧' => ' fán', + '舦' => ' tài', + '舥' => ' pā', + '舤' => ' fan', + '舣' => ' yǐ', + '舢' => ' shān', + '舠' => ' dāo', + '舕' => ' tàn', + '舟' => ' zhōu', + '舞' => ' wǔ', + '舝' => ' xiá', + '舜' => ' shùn', + '舛' => ' chuǎn', + '舚' => ' tiàn', + '舙' => ' huà', + '舘' => ' guǎn', + '舗' => ' pù', + '舖' => ' pù', + '膓' => ' cháng', + '膑' => ' bìn', + '胂' => ' shèn', + '脂' => ' zhī', + '脌' => ' nin', + '脋' => ' xie', + '脊' => ' jí', + '脉' => ' mài', + '脈' => ' mài', + '脇' => ' xié', + '脆' => ' cuì', + '脅' => ' xié', + '脄' => ' méi', + '脃' => ' cuì', + '脁' => ' tiǎo', + '脎' => ' sà', + '脀' => ' chéng', + '胿' => ' guī', + '胾' => ' zì', + '能' => ' néng', + '胼' => ' pián', + '胻' => ' héng', + '胺' => ' àn', + '胹' => ' ér', + '胸' => ' xiōng', + '胷' => ' xiōng', + '脍' => ' kuài', + '脏' => ' zàng', + '胵' => ' chī', + '脜' => ' yǒu', + '脦' => ' de', + '脥' => ' qiǎn', + '脤' => ' shèn', + '脣' => ' chún', + '脢' => ' méi', + '脡' => ' tǐng', + '脠' => ' shān', + '脟' => ' liè', + '脞' => ' cuǒ', + '脝' => ' hēng', + '脛' => ' jìng', + '脐' => ' qí', + '脚' => ' jiǎo', + '脙' => ' xiū', + '脘' => ' wǎn', + '脗' => ' wěn', + '脖' => ' bó', + '脕' => ' wàn', + '脔' => ' luán', + '脓' => ' nóng', + '脒' => ' mǐ', + '脑' => ' nǎo', + '胶' => ' jiāo', + '胴' => ' dòng', + '脨' => ' cù', + '胎' => ' tāi', + '胘' => ' xián', + '胗' => ' zhēn', + '胖' => ' pàng', + '胕' => ' fǔ', + '胔' => ' zì', + '胓' => ' píng', + '胒' => ' nì', + '胑' => ' zhī', + '胐' => ' kū', + '胏' => ' zǐ', + '胍' => ' guā', + '胚' => ' pēi', + '背' => ' bèi', + '胋' => ' tián', + '胊' => ' qú', + '胉' => ' bó', + '胈' => ' bá', + '胇' => ' fèi', + '胆' => ' dǎn', + '胅' => ' dié', + '胄' => ' zhòu', + '胃' => ' wèi', + '胙' => ' zuò', + '胛' => ' jiǎ', + '胳' => ' gē', + '胨' => ' dòng', + '胲' => ' hǎi', + '胱' => ' guāng', + '胰' => ' yí', + '胯' => ' kuà', + '胮' => ' pāng', + '胭' => ' yān', + '胬' => ' nǔ', + '胫' => ' jìng', + '胪' => ' lú', + '胩' => ' kǎ', + '胧' => ' lóng', + '胜' => ' shèng', + '胦' => ' yāng', + '胥' => ' xū', + '胤' => ' yìn', + '胣' => ' chǐ', + '胢' => ' kē', + '胡' => ' hú', + '胠' => ' qū', + '胟' => ' mǔ', + '胞' => ' bāo', + '胝' => ' zhī', + '脧' => ' juān', + '脩' => ' xiū', + '膐' => ' lǚ', + '腫' => ' zhǒng', + '腵' => ' jiā', + '腴' => ' yú', + '腳' => ' jiǎo', + '腲' => ' wěi', + '腱' => ' jiàn', + '腰' => ' yāo', + '腯' => ' tú', + '腮' => ' sāi', + '腭' => ' è', + '腬' => ' ròu', + '腪' => ' yùn', + '腷' => ' bì', + '腩' => ' nǎn', + '腨' => ' shuàn', + '腧' => ' shù', + '腦' => ' nǎo', + '腥' => ' xīng', + '腤' => ' ān', + '腣' => ' dì', + '腢' => ' ǒu', + '腡' => ' luó', + '腠' => ' còu', + '腶' => ' duàn', + '腸' => ' cháng', + '腞' => ' zhuàn', + '膅' => ' táng', + '膏' => ' gāo', + '膎' => ' xié', + '膍' => ' pí', + '膌' => ' jí', + '膋' => ' liáo', + '膊' => ' bó', + '膉' => ' yì', + '膈' => ' gé', + '膇' => ' zhuì', + '膆' => ' sù', + '膄' => ' sòu', + '腹' => ' fù', + '膃' => ' wà', + '膂' => ' lǚ', + '膁' => ' qiǎn', + '膀' => ' bǎng', + '腿' => ' tuǐ', + '腾' => ' téng', + '腽' => ' wà', + '腼' => ' miǎn', + '腻' => ' nì', + '腺' => ' xiàn', + '腟' => ' chì', + '腝' => ' ní', + '脪' => ' xìn', + '脶' => ' luó', + '腀' => ' lún', + '脿' => ' biāo', + '脾' => ' pí', + '脽' => ' shuí', + '脼' => ' liǎng', + '脻' => ' jiē', + '脺' => ' cuì', + '脹' => ' zhàng', + '脸' => ' liǎn', + '脷' => ' lì', + '脵' => ' gǔ', + '腂' => ' lěi', + '脴' => ' pǐ', + '脳' => ' nao', + '脲' => ' niào', + '脱' => ' tuō', + '脰' => ' dòu', + '脯' => ' pú', + '脮' => ' něi', + '脭' => ' chéng', + '脬' => ' pāo', + '脫' => ' tuō', + '腁' => ' pián', + '腃' => ' kuì', + '腜' => ' měi', + '腑' => ' fǔ', + '腛' => ' wò', + '腚' => ' dìng', + '腙' => ' zōng', + '腘' => ' guó', + '腗' => ' pí', + '腖' => ' dòng', + '腕' => ' wàn', + '腔' => ' qiāng', + '腓' => ' féi', + '腒' => ' jū', + '腐' => ' fǔ', + '腄' => ' chuí', + '腏' => ' chuò', + '腎' => ' shèn', + '腍' => ' rèn', + '腌' => ' yān', + '腋' => ' yè', + '腊' => ' là', + '腉' => ' nái', + '腈' => ' jīng', + '腇' => ' něi', + '腆' => ' tiǎn', + '腅' => ' dàn', + '碡' => ' dú', + '牟' => ' móu', + '碟' => ' dié', + '濨' => ' cí', + '濲' => ' gǔ', + '濱' => ' bīn', + '濰' => ' wéi', + '濯' => ' zhuó', + '濮' => ' pú', + '濭' => ' ǎi', + '濬' => ' jùn', + '濫' => ' làn', + '濪' => ' jìng', + '濩' => ' huò', + '濧' => ' duì', + '濴' => ' yíng', + '濦' => ' yǐn', + '濥' => ' yǐn', + '濤' => ' tāo', + '濣' => ' wò', + '濢' => ' cuì', + '濡' => ' rú', + '濠' => ' háo', + '濟' => ' jì', + '濞' => ' bì', + '濝' => ' qí', + '濳' => ' qián', + '濵' => ' bin', + '濛' => ' méng', + '瀂' => ' lǔ', + '瀌' => ' biāo', + '瀋' => ' shěn', + '瀊' => ' pán', + '瀉' => ' xiè', + '瀈' => ' huī', + '瀇' => ' wǎng', + '瀆' => ' dú', + '瀅' => ' yíng', + '瀄' => ' zhì', + '瀃' => ' sì', + '瀁' => ' yàng', + '濶' => ' kuò', + '瀀' => ' yōu', + '濿' => ' lì', + '濾' => ' lǜ', + '濽' => ' zàn', + '濼' => ' luò', + '濻' => ' wěi', + '濺' => ' jiàn', + '濹' => ' me', + '濸' => ' cang', + '濷' => ' fèi', + '濜' => ' jìn', + '濚' => ' yíng', + '瀎' => ' mò', + '澴' => ' huán', + '澾' => ' tà', + '澽' => ' jù', + '澼' => ' pì', + '澻' => ' suì', + '澺' => ' yì', + '澹' => ' dàn', + '澸' => ' dǎn', + '澷' => ' màn', + '澶' => ' chán', + '澵' => ' zhēn', + '澳' => ' ào', + '激' => ' jī', + '澲' => ' yè', + '澱' => ' diàn', + '澰' => ' liàn', + '澯' => ' càn', + '澮' => ' huì', + '澭' => ' yōng', + '澬' => ' zī', + '澫' => ' wàn', + '澪' => ' líng', + '澩' => ' xué', + '澿' => ' qín', + '濁' => ' zhuó', + '濙' => ' yíng', + '濎' => ' dǐng', + '濘' => ' nìng', + '濗' => ' mì', + '濖' => ' shù', + '濕' => ' shī', + '濔' => ' mǐ', + '濓' => ' lián', + '濒' => ' bīn', + '濑' => ' lài', + '濐' => ' zhǔ', + '濏' => ' sè', + '濍' => ' sōng', + '濂' => ' lián', + '濌' => ' tà', + '濋' => ' chǔ', + '濊' => ' huì', + '濉' => ' suī', + '濈' => ' jí', + '濇' => ' sè', + '濆' => ' fén', + '濅' => ' jìn', + '濄' => ' guō', + '濃' => ' nóng', + '瀍' => ' chán', + '瀏' => ' liú', + '澧' => ' lǐ', + '灐' => ' ying', + '灚' => ' jiǎo', + '灙' => ' dǎng', + '灘' => ' tān', + '灗' => ' shàn', + '灖' => ' mǐ', + '灕' => ' lí', + '灔' => ' yàn', + '灓' => ' luán', + '灒' => ' zàn', + '灑' => ' sǎ', + '灏' => ' hào', + '灜' => ' ying', + '灎' => ' yàn', + '灍' => ' què', + '灌' => ' guàn', + '灋' => ' fǎ', + '灊' => ' qián', + '灉' => ' yōng', + '灈' => ' qú', + '灇' => ' cóng', + '灆' => ' lán', + '灅' => ' lěi', + '灛' => ' chǎn', + '灝' => ' hào', + '灃' => ' fēng', + '灪' => ' yù', + '灴' => ' hōng', + '灳' => ' hui', + '灲' => ' xiāo', + '灱' => ' xiāo', + '灰' => ' huī', + '灯' => ' dēng', + '灮' => ' guāng', + '灭' => ' miè', + '灬' => ' biāo', + '火' => ' huǒ', + '灩' => ' yàn', + '灞' => ' bà', + '灨' => ' gàn', + '灧' => ' yàn', + '灦' => ' xiǎn', + '灥' => ' xún', + '灤' => ' luán', + '灣' => ' wān', + '灢' => ' nǎng', + '灡' => ' lán', + '灠' => ' lǎn', + '灟' => ' zhú', + '灄' => ' shè', + '灂' => ' zhuó', + '瀐' => ' jiān', + '瀜' => ' róng', + '瀦' => ' zhū', + '瀥' => ' xuè', + '瀤' => ' huái', + '瀣' => ' xiè', + '瀢' => ' wěi', + '瀡' => ' suǐ', + '瀠' => ' yíng', + '瀟' => ' xiāo', + '瀞' => ' jìng', + '瀝' => ' lì', + '瀛' => ' yíng', + '瀨' => ' lài', + '瀚' => ' hàn', + '瀙' => ' qìn', + '瀘' => ' lú', + '瀗' => ' xiàn', + '瀖' => ' huò', + '瀕' => ' bīn', + '瀔' => ' gǔ', + '瀓' => ' chéng', + '瀒' => ' sè', + '瀑' => ' pù', + '瀧' => ' lóng', + '瀩' => ' duì', + '灁' => ' yuān', + '瀶' => ' lín', + '灀' => ' shuàng', + '瀿' => ' fán', + '瀾' => ' lán', + '瀽' => ' jiǎn', + '瀼' => ' ráng', + '瀻' => ' dài', + '瀺' => ' chán', + '瀹' => ' yuè', + '瀸' => ' jiān', + '瀷' => ' yì', + '瀵' => ' fèn', + '瀪' => ' fán', + '瀴' => ' yíng', + '瀳' => ' jiàn', + '瀲' => ' liàn', + '瀱' => ' jì', + '瀰' => ' mí', + '瀯' => ' yíng', + '瀮' => ' ling', + '瀭' => ' shu', + '瀬' => ' lài', + '瀫' => ' hú', + '澨' => ' shì', + '澦' => ' yù', + '灶' => ' zào', + '漘' => ' chún', + '漢' => ' hàn', + '漡' => ' shāng', + '漠' => ' mò', + '漟' => ' táng', + '漞' => ' mì', + '漝' => ' xí', + '漜' => ' yě', + '漛' => ' téng', + '漚' => ' ōu', + '漙' => ' tuán', + '漗' => ' cōng', + '漤' => ' lǎn', + '漖' => ' jiào', + '漕' => ' cáo', + '演' => ' yǎn', + '漓' => ' lí', + '漒' => ' qiáng', + '漑' => ' gài', + '漐' => ' zhí', + '漏' => ' lòu', + '漎' => ' cóng', + '漍' => ' guó', + '漣' => ' lián', + '漥' => ' wā', + '漋' => ' lóng', + '漲' => ' zhǎng', + '漼' => ' cuǐ', + '漻' => ' liáo', + '漺' => ' shuǎng', + '漹' => ' yān', + '漸' => ' jiàn', + '漷' => ' huǒ', + '漶' => ' huàn', + '漵' => ' xù', + '漴' => ' zhuàng', + '漳' => ' zhāng', + '漱' => ' shù', + '漦' => ' chí', + '漰' => ' pēng', + '漯' => ' luò', + '漮' => ' kāng', + '漭' => ' mǎng', + '漬' => ' zì', + '漫' => ' màn', + '漪' => ' yī', + '漩' => ' xuán', + '漨' => ' féng', + '漧' => ' gān', + '漌' => ' jǐn', + '漊' => ' lóu', + '漾' => ' yàng', + '滤' => ' lǜ', + '滮' => ' biāo', + '滭' => ' bì', + '滬' => ' hù', + '滫' => ' xiǔ', + '滪' => ' yù', + '滩' => ' tān', + '滨' => ' bīn', + '滧' => ' xiao', + '滦' => ' luán', + '滥' => ' làn', + '滣' => ' chún', + '滰' => ' jiàng', + '滢' => ' yíng', + '满' => ' mǎn', + '滠' => ' shè', + '滟' => ' yàn', + '滞' => ' zhì', + '滝' => ' lóng', + '滜' => ' gāo', + '滛' => ' yín', + '滚' => ' gǔn', + '滙' => ' huì', + '滯' => ' zhì', + '滱' => ' kòu', + '漉' => ' lù', + '滾' => ' gǔn', + '漈' => ' jì', + '漇' => ' xǐ', + '漆' => ' qī', + '漅' => ' cháo', + '漄' => ' yá', + '漃' => ' jì', + '漂' => ' piào', + '漁' => ' yú', + '漀' => ' qǐng', + '滿' => ' mǎn', + '滽' => ' yōng', + '滲' => ' shèn', + '滼' => ' fàn', + '滻' => ' chǎn', + '滺' => ' yōu', + '滹' => ' hū', + '滸' => ' hǔ', + '滷' => ' lǔ', + '滶' => ' áo', + '滵' => ' mì', + '滴' => ' dī', + '滳' => ' shāng', + '漽' => ' tí', + '漿' => ' jiāng', + '澥' => ' xiè', + '澀' => ' sè', + '澊' => ' cūn', + '澉' => ' gǎn', + '澈' => ' chè', + '澇' => ' lào', + '澆' => ' jiāo', + '澅' => ' huà', + '澄' => ' chéng', + '澃' => ' jiǒng', + '澂' => ' chéng', + '澁' => ' sè', + '潿' => ' wéi', + '澌' => ' sī', + '潾' => ' lín', + '潽' => ' pū', + '潼' => ' tóng', + '潻' => ' shǔ', + '潺' => ' chán', + '潹' => ' chán', + '潸' => ' shān', + '潷' => ' bì', + '潶' => ' hēi', + '潵' => ' sǎ', + '澋' => ' hòng', + '澍' => ' shù', + '潳' => ' tú', + '澚' => ' yu', + '澤' => ' zé', + '澣' => ' huàn', + '澢' => ' dāng', + '澡' => ' zǎo', + '澠' => ' miǎn', + '澟' => ' lǐn', + '澞' => ' yú', + '澝' => ' ning', + '澜' => ' lán', + '澛' => ' lǔ', + '澙' => ' xì', + '澎' => ' pēng', + '澘' => ' shān', + '澗' => ' jiàn', + '澖' => ' xián', + '澕' => ' hé', + '澔' => ' hào', + '澓' => ' fú', + '澒' => ' hòng', + '澑' => ' liù', + '澐' => ' yún', + '澏' => ' hán', + '潴' => ' zhū', + '潲' => ' shào', + '潀' => ' cóng', + '潌' => ' zhì', + '潖' => ' pá', + '潕' => ' wǔ', + '潔' => ' jié', + '潓' => ' huì', + '潒' => ' dàng', + '潑' => ' pō', + '潐' => ' jiào', + '潏' => ' yù', + '潎' => ' pì', + '潍' => ' wéi', + '潋' => ' liàn', + '潘' => ' pān', + '潊' => ' xù', + '潉' => ' kun', + '潈' => ' zong', + '潇' => ' xiāo', + '潆' => ' yíng', + '潅' => ' guàn', + '潄' => ' shù', + '潃' => ' xún', + '潂' => ' hóng', + '潁' => ' yǐng', + '潗' => ' jí', + '潙' => ' wéi', + '潱' => ' yē', + '潦' => ' lǎo', + '潰' => ' kuì', + '潯' => ' xún', + '潮' => ' cháo', + '潭' => ' tán', + '潬' => ' shàn', + '潫' => ' wān', + '潪' => ' zhè', + '潩' => ' yì', + '潨' => ' cóng', + '潧' => ' zhēn', + '潥' => ' sù', + '潚' => ' sù', + '潤' => ' rùn', + '潣' => ' mǐn', + '潢' => ' huáng', + '潡' => ' dùn', + '潠' => ' xùn', + '潟' => ' xì', + '潞' => ' lù', + '潝' => ' xī', + '潜' => ' qián', + '潛' => ' qián', + '灵' => ' líng', + '灷' => ' zhuàn', + '滗' => ' bì', + '熊' => ' xióng', + '熔' => ' róng', + '熓' => ' wǔ', + '熒' => ' yíng', + '熑' => ' lián', + '熐' => ' mì', + '熏' => ' xūn', + '熎' => ' yào', + '熍' => ' qiong', + '熌' => ' shǎn', + '熋' => ' nái', + '熉' => ' yún', + '熖' => ' yan', + '熈' => ' xī', + '熇' => ' hè', + '熆' => ' hé', + '熅' => ' yùn', + '熄' => ' xī', + '熃' => ' wù', + '熂' => ' xì', + '熁' => ' xié', + '熀' => ' huǎng', + '煿' => ' bó', + '熕' => ' gōng', + '熗' => ' qiàng', + '煽' => ' shān', + '熤' => ' yì', + '熮' => ' liǔ', + '熭' => ' wèi', + '熬' => ' áo', + '熫' => ' zhì', + '熪' => ' yí', + '熩' => ' hù', + '熨' => ' yùn', + '熧' => ' zōng', + '熦' => ' jué', + '熥' => ' tēng', + '熣' => ' suī', + '熘' => ' liū', + '熢' => ' péng', + '熡' => ' lóu', + '熠' => ' yì', + '熟' => ' shú', + '熞' => ' jiān', + '熝' => ' lù', + '熜' => ' cōng', + '熛' => ' biāo', + '熚' => ' bì', + '熙' => ' xī', + '煾' => ' ēn', + '煼' => ' chǎo', + '熰' => ' ōu', + '煖' => ' nuǎn', + '煠' => ' zhá', + '煟' => ' wèi', + '煞' => ' shā', + '煝' => ' mèi', + '煜' => ' yù', + '煛' => ' jiǒng', + '煚' => ' jiǒng', + '煙' => ' yān', + '煘' => ' chán', + '煗' => ' nuǎn', + '煕' => ' xī', + '煢' => ' qióng', + '煔' => ' shǎn', + '煓' => ' tuān', + '煒' => ' wěi', + '煑' => ' zhǔ', + '煐' => ' yīng', + '煏' => ' bì', + '煎' => ' jiān', + '煍' => ' jiǎo', + '煌' => ' huáng', + '煋' => ' xīng', + '煡' => ' xìn', + '煣' => ' róu', + '煻' => ' táng', + '煰' => ' gào', + '煺' => ' tuì', + '煹' => ' gòu', + '煸' => ' biān', + '煷' => ' liang', + '煶' => ' shi', + '煵' => ' xiā', + '煴' => ' yūn', + '煳' => ' hú', + '煲' => ' bāo', + '煱' => ' guā', + '煯' => ' jiē', + '煤' => ' méi', + '煮' => ' zhǔ', + '煭' => ' liè', + '煬' => ' yáng', + '煫' => ' suì', + '煪' => ' qiú', + '煩' => ' fán', + '煨' => ' wēi', + '照' => ' zhào', + '煦' => ' xù', + '煥' => ' huàn', + '熯' => ' hàn', + '熱' => ' rè', + '煉' => ' liàn', + '燲' => ' xié', + '燼' => ' jìn', + '燻' => ' xūn', + '燺' => ' kǎo', + '燹' => ' xiǎn', + '燸' => ' rú', + '燷' => ' lán', + '燶' => ' nóng', + '燵' => ' da', + '燴' => ' huì', + '燳' => ' zhào', + '燱' => ' yì', + '燾' => ' dào', + '燰' => ' wēi', + '燯' => ' líng', + '燮' => ' xiè', + '燭' => ' zhú', + '燬' => ' huǐ', + '燫' => ' lián', + '燪' => ' cōng', + '燩' => ' què', + '燨' => ' xī', + '燧' => ' suì', + '燽' => ' chóu', + '燿' => ' yào', + '燥' => ' zào', + '爌' => ' kuàng', + '爖' => ' lóng', + '爕' => ' xiè', + '爔' => ' xī', + '爓' => ' yàn', + '爒' => ' liǎo', + '爑' => ' jué', + '爐' => ' lú', + '爏' => ' lì', + '爎' => ' liao', + '爍' => ' shuò', + '爋' => ' xùn', + '爀' => ' hè', + '爊' => ' āo', + '爉' => ' là', + '爈' => ' lǜ', + '爇' => ' ruò', + '爆' => ' bào', + '爅' => ' mò', + '爄' => ' lì', + '爃' => ' róng', + '爂' => ' biāo', + '爁' => ' làn', + '燦' => ' càn', + '燤' => ' tài', + '熲' => ' jiǒng', + '熾' => ' chì', + '燈' => ' dēng', + '燇' => ' jùn', + '燆' => ' qiāo', + '燅' => ' xián', + '燄' => ' yàn', + '燃' => ' rán', + '燂' => ' tán', + '燁' => ' yè', + '燀' => ' chǎn', + '熿' => ' huáng', + '熽' => ' xiào', + '燊' => ' shēn', + '熼' => ' yì', + '熻' => ' xī', + '熺' => ' xī', + '熹' => ' xī', + '熸' => ' jiān', + '熷' => ' zēng', + '熶' => ' cuàn', + '熵' => ' shāng', + '熴' => ' kun', + '熳' => ' màn', + '燉' => ' dùn', + '燋' => ' jiāo', + '燣' => ' lán', + '燘' => ' měi', + '燢' => ' xué', + '燡' => ' yì', + '燠' => ' yù', + '營' => ' yíng', + '燞' => ' jiǎo', + '燝' => ' jing', + '燜' => ' mèn', + '燛' => ' jǐng', + '燚' => ' yì', + '燙' => ' tàng', + '燗' => ' làn', + '燌' => ' fén', + '燖' => ' xún', + '燕' => ' yàn', + '燔' => ' fán', + '燓' => ' fēn', + '燒' => ' shāo', + '燑' => ' tóng', + '燐' => ' lín', + '燏' => ' yù', + '燎' => ' liáo', + '燍' => ' sī', + '煊' => ' xuān', + '煈' => ' fèng', + '灸' => ' jiǔ', + '炸' => ' zhà', + '烂' => ' làn', + '烁' => ' shuò', + '烀' => ' hū', + '炿' => ' zhou', + '炾' => ' huǎng', + '炽' => ' chì', + '炼' => ' liàn', + '炻' => ' shí', + '為' => ' wèi', + '点' => ' diǎn', + '炷' => ' zhù', + '烄' => ' jiǎo', + '炶' => ' hān', + '炵' => ' tōng', + '炴' => ' yǎng', + '炳' => ' bǐng', + '炲' => ' tái', + '炱' => ' tái', + '炰' => ' páo', + '炯' => ' jiǒng', + '炮' => ' pào', + '炭' => ' tàn', + '烃' => ' tīng', + '烅' => ' xù', + '炫' => ' xuàn', + '烒' => ' shì', + '烜' => ' xuǎn', + '烛' => ' zhú', + '烚' => ' xiá', + '烙' => ' lào', + '烘' => ' hōng', + '烗' => ' kài', + '烖' => ' zāi', + '烕' => ' miè', + '烔' => ' tóng', + '烓' => ' wēi', + '烑' => ' yáo', + '烆' => ' héng', + '烐' => ' zhōu', + '烏' => ' wū', + '烎' => ' yín', + '烍' => ' xiǎn', + '烌' => ' xiū', + '烋' => ' xiū', + '烊' => ' yáng', + '烉' => ' huàn', + '烈' => ' liè', + '烇' => ' quǎn', + '炬' => ' jù', + '炪' => ' zhuō', + '烞' => ' pò', + '炄' => ' niǔ', + '炎' => ' yán', + '炍' => ' pàn', + '炌' => ' kài', + '炋' => ' pī', + '炊' => ' chuī', + '炉' => ' lú', + '炈' => ' yì', + '炇' => ' pò', + '炆' => ' wén', + '炅' => ' jiǒng', + '炃' => ' fén', + '炐' => ' pàng', + '炂' => ' zhōng', + '炁' => ' qì', + '炀' => ' yáng', + '灿' => ' càn', + '灾' => ' zāi', + '災' => ' zāi', + '灼' => ' zhuó', + '灻' => ' chì', + '灺' => ' xiè', + '灹' => ' zhà', + '炏' => ' kài', + '炑' => ' mù', + '炩' => ' lìng', + '炞' => ' bian', + '炨' => ' duò', + '炧' => ' xiè', + '炦' => ' bá', + '炥' => ' fú', + '炤' => ' zhào', + '炣' => ' kě', + '炢' => ' zhú', + '炡' => ' zhēng', + '炠' => ' xiá', + '炟' => ' dá', + '炝' => ' qiàng', + '炒' => ' chǎo', + '炜' => ' wěi', + '炛' => ' guāng', + '炚' => ' guāng', + '炙' => ' zhì', + '炘' => ' xīn', + '炗' => ' guāng', + '炖' => ' dùn', + '炕' => ' kàng', + '炔' => ' guì', + '炓' => ' liào', + '烝' => ' zhēng', + '烟' => ' yān', + '煇' => ' huī', + '焢' => ' hōng', + '焬' => ' xī', + '焫' => ' ruò', + '焪' => ' qióng', + '焩' => ' píng', + '焨' => ' fèng', + '焧' => ' cōng', + '焦' => ' jiāo', + '焥' => ' wò', + '焤' => ' fǔ', + '焣' => ' chǎo', + '無' => ' wú', + '焮' => ' xìn', + '焠' => ' cuì', + '焟' => ' xī', + '焞' => ' tūn', + '焝' => ' hùn', + '焜' => ' kūn', + '焛' => ' lìn', + '焚' => ' fén', + '焙' => ' bèi', + '焘' => ' dào', + '焗' => ' jú', + '焭' => ' qióng', + '焯' => ' chāo', + '焕' => ' huàn', + '焼' => ' shāo', + '煆' => ' xiā', + '煅' => ' duàn', + '煄' => ' zhǒng', + '煃' => ' kuǐ', + '煂' => ' hè', + '煁' => ' chén', + '煀' => ' wei', + '焿' => ' gēng', + '焾' => ' niǎn', + '焽' => ' xiǒng', + '焻' => ' chàng', + '焰' => ' yàn', + '焺' => ' shēng', + '焹' => ' wang', + '焸' => ' xiòng', + '焷' => ' pí', + '然' => ' rán', + '焵' => ' gàng', + '焴' => ' yù', + '焳' => ' jué', + '焲' => ' yì', + '焱' => ' yàn', + '焖' => ' mèn', + '焔' => ' yàn', + '烠' => ' huí', + '热' => ' rè', + '烷' => ' wán', + '烶' => ' tǐng', + '烵' => ' zhuó', + '烴' => ' tīng', + '烳' => ' pǔ', + '烲' => ' xiè', + '烱' => ' jiǒng', + '烰' => ' fú', + '烯' => ' xī', + '烮' => ' lie', + '烬' => ' jìn', + '烹' => ' pēng', + '烫' => ' tàng', + '烩' => ' huì', + '烨' => ' yè', + '烧' => ' shāo', + '烦' => ' fán', + '烥' => ' chen', + '烤' => ' kǎo', + '烣' => ' huī', + '烢' => ' chè', + '烡' => ' guāng', + '烸' => ' hǎi', + '烺' => ' lǎng', + '焓' => ' hán', + '焈' => ' xī', + '焒' => ' lü', + '焑' => ' yān', + '焐' => ' wù', + '焏' => ' jí', + '焎' => ' xiè', + '焍' => ' dì', + '焌' => ' jùn', + '焋' => ' zhuàng', + '焊' => ' hàn', + '焉' => ' yān', + '焇' => ' xiāo', + '烻' => ' yàn', + '焆' => ' juān', + '焅' => ' kù', + '焄' => ' xūn', + '焃' => ' hè', + '焂' => ' shū', + '焁' => ' xī', + '焀' => ' hú', + '烿' => ' róng', + '烾' => ' chì', + '烽' => ' fēng', + '烼' => ' xù', + '滘' => ' jiào', + '滖' => ' suī', + '爘' => ' can', + '沧' => ' cāng', + '沱' => ' tuó', + '沰' => ' tuō', + '沯' => ' zǎn', + '沮' => ' jǔ', + '沭' => ' shù', + '沬' => ' mèi', + '沫' => ' mò', + '沪' => ' hù', + '沩' => ' wéi', + '沨' => ' fēng', + '沦' => ' lún', + '河' => ' hé', + '沥' => ' lì', + '沤' => ' ōu', + '沣' => ' fēng', + '沢' => ' zé', + '没' => ' méi', + '沠' => ' liú', + '沟' => ' gōu', + '沞' => ' zā', + '沝' => ' zhuǐ', + '沜' => ' pàn', + '沲' => ' tuó', + '沴' => ' lì', + '沚' => ' zhǐ', + '況' => ' kuàng', + '泋' => ' huì', + '泊' => ' pō', + '泉' => ' quán', + '泈' => ' zhōng', + '泇' => ' jiā', + '泆' => ' yì', + '泅' => ' qiú', + '泄' => ' xiè', + '泃' => ' jū', + '泂' => ' jiǒng', + '泀' => ' sī', + '沵' => ' mǐ', + '沿' => ' yán', + '沾' => ' zhān', + '沽' => ' gū', + '沼' => ' zhǎo', + '治' => ' zhì', + '沺' => ' tián', + '油' => ' yóu', + '沸' => ' fèi', + '沷' => ' fā', + '沶' => ' yí', + '沛' => ' pèi', + '沙' => ' shā', + '泍' => ' bēn', + '汳' => ' biàn', + '汽' => ' qì', + '汼' => ' niú', + '汻' => ' hǔ', + '決' => ' jué', + '汹' => ' xiōng', + '汸' => ' fāng', + '汷' => ' zhōng', + '汶' => ' wèn', + '汵' => ' gàn', + '汴' => ' biàn', + '汲' => ' jí', + '汿' => ' xù', + '汱' => ' quǎn', + '汰' => ' tài', + '汯' => ' hóng', + '汮' => ' jūn', + '汭' => ' ruì', + '汬' => ' jǐng', + '汫' => ' jǐng', + '汪' => ' wāng', + '汩' => ' gǔ', + '汨' => ' mì', + '汾' => ' fén', + '沀' => ' xù', + '沘' => ' bǐ', + '沍' => ' hù', + '沗' => ' pāng', + '沖' => ' chōng', + '沕' => ' mì', + '沔' => ' miǎn', + '沓' => ' dá', + '沒' => ' méi', + '沑' => ' nǜ', + '沐' => ' mù', + '沏' => ' qī', + '沎' => ' huò', + '沌' => ' dùn', + '沁' => ' qìn', + '沋' => ' yóu', + '沊' => ' dàn', + '沉' => ' chén', + '沈' => ' shěn', + '沇' => ' yǎn', + '沆' => ' hàng', + '沅' => ' yuán', + '沄' => ' yún', + '沃' => ' wò', + '沂' => ' yí', + '泌' => ' mì', + '泎' => ' zé', + '汦' => ' zhǐ', + '洏' => ' ér', + '洙' => ' zhū', + '洘' => ' kǎo', + '洗' => ' xǐ', + '洖' => ' wú', + '洕' => ' yìn', + '洔' => ' zhǐ', + '洓' => ' sè', + '洒' => ' sǎ', + '洑' => ' fú', + '洐' => ' xíng', + '洎' => ' jì', + '洛' => ' luò', + '洍' => ' sì', + '洌' => ' liè', + '洋' => ' yáng', + '洊' => ' jiàn', + '洉' => ' hòu', + '洈' => ' wéi', + '洇' => ' yīn', + '洆' => ' chéng', + '洅' => ' zài', + '洄' => ' huí', + '洚' => ' jiàng', + '洜' => ' luò', + '洂' => ' yì', + '洩' => ' xiè', + '洳' => ' rù', + '洲' => ' zhōu', + '洱' => ' ěr', + '洰' => ' jù', + '洯' => ' qiè', + '洮' => ' táo', + '洭' => ' kuāng', + '洬' => ' sù', + '洫' => ' xù', + '洪' => ' hóng', + '洨' => ' xiáo', + '洝' => ' àn', + '洧' => ' wěi', + '洦' => ' pò', + '津' => ' jīn', + '洤' => ' quán', + '洣' => ' mǐ', + '洢' => ' yī', + '洡' => ' lèi', + '洠' => ' móu', + '洟' => ' tì', + '洞' => ' dòng', + '洃' => ' huī', + '洁' => ' jié', + '泏' => ' zhú', + '泛' => ' fàn', + '泥' => ' ní', + '泤' => ' sì', + '泣' => ' qì', + '波' => ' bō', + '泡' => ' pào', + '泠' => ' líng', + '泟' => ' chēng', + '泞' => ' nìng', + '泝' => ' sù', + '泜' => ' zhī', + '泚' => ' cǐ', + '泧' => ' sà', + '泙' => ' píng', + '泘' => ' hū', + '泗' => ' sì', + '泖' => ' mǎo', + '法' => ' fǎ', + '泔' => ' gān', + '泓' => ' hóng', + '泒' => ' gū', + '泑' => ' yōu', + '泐' => ' lè', + '泦' => ' jú', + '注' => ' zhù', + '洀' => ' pán', + '泵' => ' bèng', + '泿' => ' yín', + '泾' => ' jīng', + '泽' => ' zé', + '泼' => ' pō', + '泻' => ' xiè', + '泺' => ' luò', + '泹' => ' dàn', + '泸' => ' lú', + '泷' => ' lóng', + '泶' => ' xué', + '泴' => ' guàn', + '泩' => ' shēng', + '泳' => ' yǒng', + '泲' => ' jǐ', + '泱' => ' yāng', + '泰' => ' tài', + '泯' => ' mǐn', + '泮' => ' pàn', + '泭' => ' fú', + '泬' => ' jué', + '泫' => ' xuàn', + '泪' => ' lèi', + '汧' => ' qiān', + '汥' => ' zhī', + '洵' => ' xún', + '毗' => ' pí', + '毡' => ' zhān', + '毠' => ' jiā', + '毟' => ' lie', + '毞' => ' pí', + '毝' => ' cǎi', + '毜' => ' háo', + '毛' => ' máo', + '毚' => ' chán', + '毙' => ' bì', + '毘' => ' pí', + '毖' => ' bì', + '毣' => ' mù', + '毕' => ' bì', + '比' => ' bǐ', + '毓' => ' yù', + '毒' => ' dú', + '毑' => ' jiě', + '毐' => ' ǎi', + '每' => ' měi', + '毎' => ' měi', + '母' => ' mǔ', + '毌' => ' guàn', + '毢' => ' sāi', + '毤' => ' tuò', + '毊' => ' xiāo', + '毱' => ' jú', + '毻' => ' tuò', + '毺' => ' yū', + '毹' => ' shū', + '毸' => ' sāi', + '毷' => ' mào', + '毶' => ' san', + '毵' => ' sān', + '毴' => ' bī', + '毳' => ' cuì', + '毲' => ' duō', + '毰' => ' péi', + '毥' => ' xún', + '毯' => ' tǎn', + '毮' => ' shā', + '毭' => ' dòu', + '毬' => ' qiú', + '毫' => ' háo', + '毪' => ' mú', + '毩' => ' jú', + '毨' => ' xiǎn', + '毧' => ' róng', + '毦' => ' ěr', + '毋' => ' wú', + '毉' => ' yī', + '毽' => ' jiàn', + '殣' => ' jìn', + '殭' => ' jiāng', + '殬' => ' dù', + '殫' => ' dān', + '殪' => ' yì', + '殩' => ' cuàn', + '殨' => ' huì', + '殧' => ' jiù', + '殦' => ' diāo', + '殥' => ' yín', + '殤' => ' shāng', + '殢' => ' tì', + '殯' => ' bìn', + '殡' => ' bìn', + '殠' => ' chòu', + '殟' => ' wēn', + '殞' => ' yǔn', + '殝' => ' zhēn', + '殜' => ' dié', + '殛' => ' jí', + '殚' => ' dān', + '殙' => ' hūn', + '殘' => ' cán', + '殮' => ' liàn', + '殰' => ' dú', + '毈' => ' duàn', + '殽' => ' xiáo', + '毇' => ' huǐ', + '毆' => ' ōu', + '毅' => ' yì', + '毄' => ' jī', + '毃' => ' qiāo', + '毂' => ' gǔ', + '毁' => ' huǐ', + '毀' => ' huǐ', + '殿' => ' diàn', + '殾' => ' xùn', + '殼' => ' ké', + '殱' => ' jian', + '殻' => ' qiào', + '殺' => ' shā', + '殹' => ' yì', + '殸' => ' qìng', + '殷' => ' yīn', + '殶' => ' zhù', + '段' => ' duàn', + '殴' => ' ōu', + '殳' => ' shū', + '殲' => ' jiān', + '毼' => ' hé', + '毾' => ' tà', + '汤' => ' tāng', + '氿' => ' guǐ', + '汉' => ' hàn', + '汈' => ' diāo', + '汇' => ' huì', + '汆' => ' cuān', + '汅' => ' miǎn', + '汄' => ' zè', + '汃' => ' bīn', + '求' => ' qiú', + '汁' => ' zhī', + '汀' => ' tīng', + '氾' => ' fàn', + '汋' => ' zhuó', + '氽' => ' tǔn', + '氼' => ' nì', + '氻' => ' lè', + '氺' => ' shui', + '氹' => ' dàng', + '永' => ' yǒng', + '氷' => ' bīng', + '氶' => ' zhěng', + '氵' => ' shui', + '水' => ' shuǐ', + '汊' => ' chà', + '汌' => ' chuàn', + '氲' => ' yūn', + '汙' => ' wū', + '汣' => ' jiu', + '汢' => ' tu', + '污' => ' wū', + '池' => ' chí', + '江' => ' jiāng', + '汞' => ' gǒng', + '汝' => ' rǔ', + '汜' => ' sì', + '汛' => ' xùn', + '汚' => ' wū', + '汘' => ' qiān', + '汍' => ' wán', + '汗' => ' hàn', + '汖' => ' pìn', + '汕' => ' shàn', + '汔' => ' qì', + '汓' => ' qiú', + '汒' => ' máng', + '汑' => ' tuō', + '汐' => ' xī', + '汏' => ' dà', + '汎' => ' fàn', + '氳' => ' yūn', + '氱' => ' yǎng', + '毿' => ' sān', + '氋' => ' méng', + '氕' => ' piē', + '气' => ' qì', + '氓' => ' máng', + '氒' => ' jué', + '民' => ' mín', + '氐' => ' dī', + '氏' => ' shì', + '氎' => ' dié', + '氍' => ' qú', + '氌' => ' lǔ', + '氊' => ' zhān', + '気' => ' qì', + '氉' => ' sào', + '氈' => ' zhān', + '氇' => ' lu', + '氆' => ' pǔ', + '氅' => ' chǎng', + '氄' => ' rǒng', + '氃' => ' tóng', + '氂' => ' máo', + '氁' => ' mú', + '氀' => ' lǘ', + '氖' => ' nǎi', + '氘' => ' dāo', + '氰' => ' qíng', + '氥' => ' xī', + '氯' => ' lǜ', + '氮' => ' dàn', + '氭' => ' dōng', + '氬' => ' yà', + '氫' => ' qīng', + '氪' => ' kè', + '氩' => ' yà', + '氨' => ' ān', + '氧' => ' yǎng', + '氦' => ' hài', + '氤' => ' yīn', + '氙' => ' xiān', + '氣' => ' qì', + '氢' => ' qīng', + '氡' => ' dōng', + '氠' => ' shēn', + '氟' => ' fú', + '氞' => ' bin', + '氝' => ' nèi', + '氜' => ' yáng', + '氛' => ' fēn', + '氚' => ' chuān', + '洴' => ' píng', + '洶' => ' xiōng', + '滕' => ' téng', + '湈' => ' méi', + '湒' => ' jí', + '湑' => ' xū', + '湐' => ' mò', + '湏' => ' huì', + '湎' => ' miǎn', + '湍' => ' tuān', + '湌' => ' cān', + '湋' => ' wéi', + '湊' => ' còu', + '湉' => ' tián', + '湇' => ' qì', + '湔' => ' jiān', + '湆' => ' qì', + '湅' => ' liàn', + '湄' => ' méi', + '湃' => ' pài', + '湂' => ' è', + '湁' => ' chì', + '湀' => ' guǐ', + '渿' => ' nài', + '渾' => ' hún', + '渽' => ' zāi', + '湓' => ' pén', + '湕' => ' jiǎn', + '渻' => ' shěng', + '湢' => ' bì', + '湬' => ' jiǎo', + '湫' => ' jiǎo', + '湪' => ' tuàn', + '湩' => ' dòng', + '湨' => ' jú', + '湧' => ' yǒng', + '湦' => ' shēng', + '湥' => ' tū', + '湤' => ' shī', + '湣' => ' mǐn', + '湡' => ' yú', + '湖' => ' hú', + '湠' => ' tàn', + '湟' => ' huáng', + '湞' => ' chēng', + '湝' => ' jiē', + '湜' => ' shí', + '湛' => ' zhàn', + '湚' => ' yìn', + '湙' => ' yì', + '湘' => ' xiāng', + '湗' => ' fèng', + '渼' => ' měi', + '渺' => ' miǎo', + '湮' => ' yān', + '渔' => ' yú', + '渞' => ' qiú', + '渝' => ' yú', + '渜' => ' nuǎn', + '減' => ' jiǎn', + '渚' => ' zhǔ', + '渙' => ' huàn', + '渘' => ' róu', + '渗' => ' shèn', + '渖' => ' shěn', + '渕' => ' yuān', + '渓' => ' xi', + '渠' => ' qú', + '渒' => ' pì', + '渑' => ' miǎn', + '渐' => ' jiàn', + '渏' => ' qi', + '渎' => ' dú', + '渍' => ' zì', + '渌' => ' lù', + '渋' => ' se', + '渊' => ' yuān', + '渉' => ' shè', + '渟' => ' tíng', + '渡' => ' dù', + '渹' => ' hōng', + '渮' => ' hé', + '游' => ' yóu', + '渷' => ' yǎn', + '渶' => ' yīng', + '渵' => ' máo', + '渴' => ' kě', + '渳' => ' mǐ', + '渲' => ' xuàn', + '渱' => ' hóng', + '渰' => ' yǎn', + '港' => ' gǎng', + '渭' => ' wèi', + '渢' => ' fán', + '測' => ' cè', + '渫' => ' xiè', + '渪' => ' rú', + '温' => ' wēn', + '渨' => ' wēi', + '渧' => ' dì', + '渦' => ' wō', + '渥' => ' wò', + '渤' => ' bó', + '渣' => ' zhā', + '湭' => ' qiú', + '湯' => ' tāng', + '渇' => ' kě', + '溰' => ' ái', + '溺' => ' nì', + '溹' => ' suò', + '溸' => ' sù', + '溷' => ' hùn', + '溶' => ' róng', + '溵' => ' yīn', + '溴' => ' xiù', + '溳' => ' yún', + '溲' => ' sōu', + '溱' => ' qín', + '溯' => ' sù', + '溼' => ' shī', + '溮' => ' shī', + '溭' => ' zé', + '溬' => ' qiāng', + '溫' => ' wēn', + '溪' => ' xī', + '溩' => ' wù', + '溨' => ' cái', + '溧' => ' lì', + '溦' => ' wēi', + '溥' => ' pǔ', + '溻' => ' tā', + '溽' => ' rù', + '溣' => ' lùn', + '滊' => ' xì', + '滔' => ' tāo', + '滓' => ' zǐ', + '滒' => ' gē', + '滑' => ' huá', + '滐' => ' jié', + '滏' => ' fǔ', + '滎' => ' xíng', + '滍' => ' zhì', + '滌' => ' dí', + '滋' => ' zī', + '滉' => ' huàng', + '溾' => ' āi', + '滈' => ' hào', + '滇' => ' diān', + '滆' => ' gé', + '滅' => ' miè', + '滄' => ' cāng', + '滃' => ' wēng', + '滂' => ' pāng', + '滁' => ' chú', + '滀' => ' chù', + '溿' => ' pàn', + '溤' => ' mǎ', + '溢' => ' yì', + '湰' => ' lóng', + '湼' => ' niè', + '溆' => ' xù', + '溅' => ' jiàn', + '溄' => ' feng', + '溃' => ' kuì', + '溂' => ' la', + '溁' => ' yíng', + '満' => ' mǎn', + '湿' => ' shī', + '湾' => ' wān', + '湽' => ' zī', + '湻' => ' chún', + '溈' => ' wéi', + '湺' => ' yán', + '湹' => ' chán', + '湸' => ' liàng', + '湷' => ' zhuāng', + '湶' => ' quán', + '湵' => ' yǒu', + '湴' => ' bàn', + '湳' => ' nǎn', + '湲' => ' yuán', + '湱' => ' huò', + '溇' => ' lóu', + '溉' => ' gài', + '溡' => ' shí', + '準' => ' zhǔn', + '溠' => ' zhà', + '溟' => ' míng', + '溞' => ' sāo', + '溝' => ' gōu', + '溜' => ' liū', + '溛' => ' wā', + '溚' => ' tǎ', + '溙' => ' tài', + '溘' => ' kè', + '溗' => ' chéng', + '溕' => ' mèng', + '溊' => ' xia', + '溔' => ' yǎo', + '溓' => ' lián', + '溒' => ' yuán', + '溑' => ' suǒ', + '源' => ' yuán', + '溏' => ' táng', + '溎' => ' yàn', + '溍' => ' jìn', + '溌' => ' pō', + '溋' => ' yíng', + '済' => ' jì', + '渆' => ' yuān', + '洷' => ' zhì', + '海' => ' hǎi', + '涁' => ' shèn', + '涀' => ' xiàn', + '浿' => ' pèi', + '浾' => ' chēng', + '浽' => ' suī', + '浼' => ' měi', + '浻' => ' jiǒng', + '浺' => ' chōng', + '浹' => ' jiā', + '浸' => ' jìn', + '浶' => ' láo', + '涃' => ' kùn', + '浵' => ' tóng', + '浴' => ' yù', + '浳' => ' yì', + '浲' => ' féng', + '浱' => ' chún', + '浰' => ' liàn', + '浯' => ' wú', + '浮' => ' fú', + '浭' => ' gēng', + '浬' => ' lǐ', + '涂' => ' tú', + '涄' => ' pīng', + '浪' => ' làng', + '涑' => ' sù', + '涛' => ' tāo', + '涚' => ' shuì', + '涙' => ' lèi', + '涘' => ' sì', + '涗' => ' shuì', + '涖' => ' lì', + '涕' => ' tì', + '涔' => ' cén', + '涓' => ' juān', + '涒' => ' tūn', + '涐' => ' é', + '涅' => ' niè', + '涏' => ' tǐng', + '涎' => ' xián', + '涍' => ' xiào', + '涌' => ' yǒng', + '涋' => ' tū', + '涊' => ' niǎn', + '涉' => ' shè', + '消' => ' xiāo', + '涇' => ' jīng', + '涆' => ' hàn', + '浫' => ' hǎn', + '浩' => ' hào', + '涝' => ' lào', + '浃' => ' jiā', + '浍' => ' huì', + '浌' => ' fá', + '测' => ' cè', + '浊' => ' zhuó', + '浉' => ' shī', + '浈' => ' zhēn', + '浇' => ' jiāo', + '浆' => ' jiāng', + '浅' => ' qiǎn', + '浄' => ' jìng', + '浂' => ' yì', + '浏' => ' liú', + '流' => ' liú', + '浀' => ' qū', + '洿' => ' wū', + '派' => ' pài', + '洽' => ' qià', + '洼' => ' wā', + '活' => ' huó', + '洺' => ' míng', + '洹' => ' huán', + '洸' => ' guāng', + '济' => ' jì', + '浐' => ' chǎn', + '浨' => ' lǎn', + '浝' => ' máng', + '浧' => ' yǐng', + '浦' => ' pǔ', + '浥' => ' yì', + '浤' => ' hóng', + '浣' => ' huàn', + '浢' => ' dòu', + '浡' => ' bó', + '浠' => ' xī', + '浟' => ' yóu', + '浞' => ' zhuó', + '浜' => ' bāng', + '浑' => ' hún', + '浛' => ' hán', + '浚' => ' jùn', + '浙' => ' zhè', + '浘' => ' wěi', + '浗' => ' qiú', + '浖' => ' liè', + '浕' => ' jìn', + '浔' => ' xún', + '浓' => ' nóng', + '浒' => ' hǔ', + '涜' => ' dú', + '涞' => ' lái', + '清' => ' qīng', + '淠' => ' pì', + '淪' => ' lún', + '淩' => ' líng', + '淨' => ' jìng', + '淧' => ' mì', + '淦' => ' gàn', + '淥' => ' lù', + '淤' => ' yū', + '淣' => ' ní', + '淢' => ' yù', + '淡' => ' dàn', + '淟' => ' tiǎn', + '淬' => ' cuì', + '淞' => ' sōng', + '淝' => ' féi', + '淜' => ' píng', + '淛' => ' zhè', + '淚' => ' lèi', + '淙' => ' cóng', + '淘' => ' táo', + '淗' => ' jú', + '淖' => ' nào', + '淕' => ' lù', + '淫' => ' yín', + '淭' => ' qú', + '淓' => ' fāng', + '淺' => ' qiǎn', + '渄' => ' fēi', + '渃' => ' ruò', + '渂' => ' wèn', + '渁' => ' yuān', + '渀' => ' bèn', + '淿' => ' mì', + '淾' => ' yǐn', + '淽' => ' zhǐ', + '淼' => ' miǎo', + '添' => ' tiān', + '淹' => ' yān', + '淮' => ' huái', + '淸' => ' qīng', + '混' => ' hùn', + '淶' => ' lái', + '淵' => ' yuān', + '淴' => ' hū', + '淳' => ' chún', + '淲' => ' biāo', + '深' => ' shēn', + '淰' => ' niǎn', + '淯' => ' yù', + '淔' => ' zhí', + '淒' => ' qī', + '涟' => ' lián', + '涫' => ' guàn', + '涵' => ' hán', + '涴' => ' wò', + '涳' => ' kōng', + '液' => ' yè', + '涱' => ' zhàng', + '涰' => ' chuò', + '涯' => ' yá', + '涮' => ' shuàn', + '涭' => ' shòu', + '涬' => ' xìng', + '涪' => ' fú', + '涷' => ' dōng', + '涩' => ' sè', + '涨' => ' zhǎng', + '涧' => ' jiàn', + '润' => ' rùn', + '涥' => ' heng', + '涤' => ' dí', + '涣' => ' huàn', + '涢' => ' yún', + '涡' => ' wō', + '涠' => ' wéi', + '涶' => ' tuō', + '涸' => ' hé', + '淑' => ' shū', + '淆' => ' xiáo', + '淐' => ' chāng', + '淏' => ' hào', + '淎' => ' pěng', + '淍' => ' zhōu', + '淌' => ' tǎng', + '淋' => ' lín', + '淊' => ' yān', + '淉' => ' guǒ', + '淈' => ' gǔ', + '淇' => ' qí', + '淅' => ' xī', + '涹' => ' wō', + '淄' => ' zī', + '淃' => ' juàn', + '淂' => ' dé', + '淁' => ' qiè', + '淀' => ' diàn', + '涿' => ' zhuō', + '涾' => ' tà', + '涽' => ' hūn', + '涼' => ' liáng', + '涻' => ' shè', + '涺' => ' jū', + '爗' => ' yè', + '爙' => ' rǎng', + '碞' => ' yán', + '癰' => ' yōng', + '発' => ' fā', + '癹' => ' bá', + '癸' => ' guǐ', + '癷' => ' bō', + '癶' => ' bō', + '癵' => ' luán', + '癴' => ' luán', + '癳' => ' luǒ', + '癲' => ' diān', + '癱' => ' tān', + '癯' => ' qú', + '發' => ' fā', + '癮' => ' yǐn', + '癭' => ' yǐng', + '癬' => ' xuǎn', + '癫' => ' diān', + '癪' => ' jī', + '癩' => ' lài', + '癨' => ' huò', + '癧' => ' lì', + '癦' => ' me', + '癥' => ' zhēng', + '登' => ' dēng', + '白' => ' bái', + '癣' => ' xuǎn', + '皊' => ' líng', + '皔' => ' hàn', + '皓' => ' hào', + '皒' => ' é', + '皑' => ' ái', + '皐' => ' gāo', + '皏' => ' pěng', + '皎' => ' jiǎo', + '皍' => ' jí', + '皌' => ' mò', + '皋' => ' gāo', + '皉' => ' cǐ', + '百' => ' bǎi', + '皈' => ' guī', + '皇' => ' huáng', + '皆' => ' jiē', + '皅' => ' pā', + '的' => ' de', + '皃' => ' mào', + '皂' => ' zào', + '皁' => ' zào', + '皀' => ' jí', + '癿' => ' qié', + '癤' => ' jiē', + '癢' => ' yǎng', + '皖' => ' wǎn', + '瘼' => ' mò', + '癆' => ' láo', + '癅' => ' liú', + '癄' => ' qiáo', + '癃' => ' lóng', + '療' => ' liáo', + '癁' => ' fú', + '癀' => ' huáng', + '瘿' => ' yǐng', + '瘾' => ' yǐn', + '瘽' => ' qín', + '瘻' => ' lòu', + '癈' => ' fèi', + '瘺' => ' lòu', + '瘹' => ' diào', + '瘸' => ' qué', + '瘷' => ' sè', + '瘶' => ' sòu', + '瘵' => ' zhài', + '瘴' => ' zhàng', + '瘳' => ' chōu', + '瘲' => ' zòng', + '瘱' => ' yì', + '癇' => ' xián', + '癉' => ' dān', + '癡' => ' chī', + '癖' => ' pǐ', + '癠' => ' jì', + '癟' => ' biě', + '癞' => ' lài', + '癝' => ' lǐn', + '癜' => ' diàn', + '癛' => ' lǐn', + '癚' => ' dàn', + '癙' => ' shǔ', + '癘' => ' lì', + '癗' => ' lěi', + '癕' => ' yōng', + '癊' => ' yìn', + '癔' => ' yì', + '癓' => ' wéi', + '癒' => ' yù', + '癑' => ' nòng', + '癐' => ' guì', + '癏' => ' guān', + '癎' => ' xián', + '癍' => ' bān', + '癌' => ' ái', + '癋' => ' hè', + '皕' => ' bì', + '皗' => ' chóu', + '瘯' => ' cù', + '盘' => ' pán', + '盢' => ' xù', + '盡' => ' jǐn', + '盠' => ' lí', + '盟' => ' méng', + '盞' => ' zhǎn', + '盝' => ' lù', + '盜' => ' dào', + '盛' => ' shèng', + '盚' => ' qiú', + '盙' => ' fǔ', + '盗' => ' dào', + '盤' => ' pán', + '盖' => ' gài', + '盕' => ' fàn', + '盔' => ' kuī', + '盓' => ' yū', + '盒' => ' hé', + '监' => ' jiān', + '盐' => ' yán', + '盏' => ' zhǎn', + '盎' => ' àng', + '盍' => ' hé', + '監' => ' jiān', + '盥' => ' guàn', + '盋' => ' bō', + '盲' => ' máng', + '盼' => ' pàn', + '盻' => ' xì', + '盺' => ' xīn', + '盹' => ' dǔn', + '相' => ' xiāng', + '盷' => ' tián', + '盶' => ' yuǎn', + '盵' => ' qì', + '直' => ' zhí', + '盳' => ' wàng', + '盱' => ' xū', + '盦' => ' ān', + '盰' => ' gàn', + '盯' => ' dīng', + '目' => ' mù', + '盭' => ' lì', + '盬' => ' gǔ', + '盫' => ' ān', + '盪' => ' dàng', + '盩' => ' zhōu', + '盨' => ' xǔ', + '盧' => ' lú', + '盌' => ' wǎn', + '益' => ' yì', + '皘' => ' qiàn', + '皤' => ' pó', + '皮' => ' pí', + '皭' => ' jiào', + '皬' => ' hé', + '皫' => ' piǎo', + '皪' => ' lì', + '皩' => ' huàng', + '皨' => ' xīng', + '皧' => ' ài', + '皦' => ' jiǎo', + '皥' => ' hào', + '皣' => ' yè', + '皰' => ' pào', + '皢' => ' xiǎo', + '皡' => ' hào', + '皠' => ' cuǐ', + '皟' => ' zé', + '皞' => ' hào', + '皝' => ' huàng', + '皜' => ' hào', + '皛' => ' xiǎo', + '䴰' => ' shú', + '皙' => ' xī', + '皯' => ' gǎn', + '皱' => ' zhòu', + '盉' => ' hé', + '皾' => ' dú', + '盈' => ' yíng', + '盇' => ' hé', + '盆' => ' pén', + '盅' => ' zhōng', + '盄' => ' zhāo', + '盃' => ' bēi', + '盂' => ' yú', + '盁' => ' yíng', + '盀' => ' qǐ', + '皿' => ' mǐn', + '皽' => ' zhāo', + '皲' => ' jūn', + '皼' => ' gǔ', + '皻' => ' zhā', + '皺' => ' zhòu', + '皹' => ' jūn', + '皸' => ' jūn', + '皷' => ' gǔ', + '皶' => ' zhā', + '皵' => ' què', + '皴' => ' cūn', + '皳' => ' qiú', + '瘰' => ' luǒ', + '瘮' => ' shèn', + '盾' => ' dùn', + '疠' => ' lì', + '疪' => ' bì', + '疩' => ' cuì', + '疨' => ' xiā', + '疧' => ' qí', + '疦' => ' jué', + '疥' => ' jiè', + '疤' => ' bā', + '疣' => ' yóu', + '疢' => ' chèn', + '疡' => ' yáng', + '疟' => ' nüè', + '疬' => ' lì', + '疞' => ' xū', + '疝' => ' shàn', + '疜' => ' xià', + '疛' => ' zhǒu', + '疚' => ' jiù', + '疙' => ' gē', + '疘' => ' gāng', + '疗' => ' liáo', + '疖' => ' jiē', + '疕' => ' bǐ', + '疫' => ' yì', + '疭' => ' zòng', + '疓' => ' nǎi', + '疺' => ' fá', + '痄' => ' zhà', + '痃' => ' xuán', + '痂' => ' jiā', + '痁' => ' shān', + '痀' => ' jū', + '疿' => ' fèi', + '疾' => ' jí', + '疽' => ' jū', + '疼' => ' téng', + '疻' => ' zhǐ', + '疹' => ' zhěn', + '疮' => ' chuāng', + '疸' => ' dǎn', + '疷' => ' zhī', + '疶' => ' xuē', + '疵' => ' cī', + '疴' => ' kē', + '疳' => ' gān', + '疲' => ' pí', + '疱' => ' pào', + '疰' => ' zhù', + '疯' => ' fēng', + '疔' => ' dīng', + '疒' => ' nè', + '痆' => ' niè', + '畬' => ' shē', + '當' => ' dāng', + '畵' => ' huà', + '畴' => ' chóu', + '畳' => ' dié', + '畲' => ' shē', + '畱' => ' liú', + '異' => ' yì', + '畯' => ' jùn', + '畮' => ' mǔ', + '畭' => ' yú', + '畫' => ' huà', + '畸' => ' jī', + '番' => ' fān', + '畩' => ' yi', + '畨' => ' pān', + '畧' => ' lüè', + '畦' => ' qí', + '略' => ' lüè', + '畤' => ' zhì', + '畣' => ' dá', + '畢' => ' bì', + '畡' => ' gāi', + '畷' => ' zhuì', + '畹' => ' wǎn', + '疑' => ' yí', + '疆' => ' jiāng', + '疐' => ' zhì', + '疏' => ' shū', + '疎' => ' shū', + '疍' => ' dàn', + '疌' => ' jié', + '疋' => ' pǐ', + '疊' => ' dié', + '疉' => ' dié', + '疈' => ' pì', + '疇' => ' chóu', + '疅' => ' jiāng', + '畺' => ' jiāng', + '疄' => ' lìn', + '疃' => ' tuǎn', + '疂' => ' die', + '疁' => ' liú', + '疀' => ' chā', + '畿' => ' jī', + '畾' => ' léi', + '畽' => ' tǔn', + '畼' => ' chàng', + '畻' => ' chéng', + '病' => ' bìng', + '症' => ' zhèng', + '瘭' => ' biāo', + '瘈' => ' chì', + '瘒' => ' wén', + '瘑' => ' guō', + '瘐' => ' yǔ', + '瘏' => ' tú', + '瘎' => ' chén', + '瘍' => ' yáng', + '瘌' => ' là', + '瘋' => ' fēng', + '瘊' => ' hóu', + '瘉' => ' yù', + '瘇' => ' zhǒng', + '瘔' => ' kù', + '瘆' => ' shèn', + '瘅' => ' dān', + '瘄' => ' cù', + '瘃' => ' zhú', + '瘂' => ' yǎ', + '瘁' => ' cuì', + '瘀' => ' yū', + '痿' => ' wěi', + '痾' => ' ē', + '痽' => ' duī', + '瘓' => ' huàn', + '瘕' => ' jiǎ', + '痻' => ' mín', + '瘢' => ' bān', + '瘬' => ' zhàng', + '瘫' => ' tān', + '瘪' => ' biě', + '瘩' => ' da', + '瘨' => ' diān', + '瘧' => ' nüè', + '瘦' => ' shòu', + '瘥' => ' chài', + '瘤' => ' liú', + '瘣' => ' huì', + '瘡' => ' chuāng', + '瘖' => ' yīn', + '瘠' => ' jí', + '瘟' => ' wēn', + '瘞' => ' yì', + '瘝' => ' guān', + '瘜' => ' xī', + '瘛' => ' chì', + '瘚' => ' jué', + '瘙' => ' sào', + '瘘' => ' lòu', + '瘗' => ' yì', + '痼' => ' gù', + '痺' => ' bì', + '痈' => ' yōng', + '痔' => ' zhì', + '痞' => ' pǐ', + '痝' => ' máng', + '痜' => ' tū', + '痛' => ' tòng', + '痚' => ' xiāo', + '痙' => ' jìng', + '痘' => ' dòu', + '痗' => ' mèi', + '痖' => ' yǎ', + '痕' => ' hén', + '痓' => ' chì', + '痠' => ' suān', + '痒' => ' yǎng', + '痑' => ' tān', + '痐' => ' huí', + '痏' => ' wěi', + '痎' => ' jiē', + '痍' => ' yí', + '痌' => ' tōng', + '痋' => ' téng', + '痊' => ' quán', + '痉' => ' jìng', + '痟' => ' xiāo', + '痡' => ' fū', + '痹' => ' bì', + '痮' => ' zhàng', + '痸' => ' chì', + '痷' => ' ān', + '痶' => ' tiǎn', + '痵' => ' jì', + '痴' => ' chī', + '痳' => ' lín', + '痲' => ' má', + '痱' => ' fèi', + '痰' => ' tán', + '痯' => ' guǎn', + '痭' => ' bēng', + '痢' => ' lì', + '痬' => ' yì', + '痫' => ' xián', + '痪' => ' huàn', + '痩' => ' shòu', + '痨' => ' láo', + '痧' => ' shā', + '痦' => ' wù', + '痥' => ' duó', + '痤' => ' cuó', + '痣' => ' zhì', + '盽' => ' fēng', + '盿' => ' mín', + '畟' => ' cè', + '砑' => ' yà', + '砛' => ' jin', + '砚' => ' yàn', + '砙' => ' pān', + '砘' => ' dùn', + '砗' => ' chē', + '砖' => ' zhuān', + '砕' => ' suì', + '研' => ' yán', + '砓' => ' zhé', + '砒' => ' pī', + '砐' => ' è', + '砝' => ' fá', + '砏' => ' bīn', + '砎' => ' jiè', + '砍' => ' kǎn', + '砌' => ' qì', + '砋' => ' zhǐ', + '砊' => ' kāng', + '砉' => ' huò', + '砈' => ' ě', + '砇' => ' mín', + '砆' => ' fū', + '砜' => ' fēng', + '砞' => ' mò', + '砄' => ' jué', + '砫' => ' zhù', + '砵' => ' bō', + '破' => ' pò', + '砳' => ' lè', + '砲' => ' pào', + '砱' => ' líng', + '砰' => ' pēng', + '砯' => ' pīng', + '砮' => ' nǔ', + '砭' => ' biān', + '砬' => ' lá', + '砪' => ' mǔ', + '砟' => ' zhǎ', + '砩' => ' fú', + '砨' => ' è', + '砧' => ' zhēn', + '砦' => ' zhài', + '砥' => ' dǐ', + '砤' => ' tuó', + '砣' => ' tuó', + '砢' => ' kē', + '砡' => ' yù', + '砠' => ' jū', + '砅' => ' lì', + '砃' => ' dān', + '砷' => ' shēn', + '矝' => ' lín', + '矧' => ' shěn', + '矦' => ' hóu', + '知' => ' zhī', + '矤' => ' shěn', + '矣' => ' yǐ', + '矢' => ' shǐ', + '矡' => ' jué', + '矠' => ' zé', + '矟' => ' shuò', + '矞' => ' yù', + '矜' => ' jīn', + '矩' => ' jǔ', + '矛' => ' máo', + '矚' => ' zhǔ', + '矙' => ' kàn', + '矘' => ' tǎng', + '矗' => ' chù', + '矖' => ' xǐ', + '矕' => ' mǎn', + '矔' => ' guàn', + '矓' => ' lóng', + '矒' => ' méng', + '矨' => ' yǐng', + '矪' => ' zhōu', + '砂' => ' shā', + '矷' => ' zǐ', + '码' => ' mǎ', + '砀' => ' dàng', + '矿' => ' kuàng', + '矾' => ' fán', + '矽' => ' xì', + '矼' => ' gāng', + '矻' => ' kū', + '矺' => ' zhé', + '矹' => ' wù', + '矸' => ' gān', + '矶' => ' jī', + '矫' => ' jiǎo', + '矵' => ' qì', + '矴' => ' dìng', + '石' => ' shí', + '矲' => ' bà', + '矱' => ' yuē', + '矰' => ' zēng', + '矯' => ' jiǎo', + '矮' => ' ǎi', + '短' => ' duǎn', + '矬' => ' cuó', + '砶' => ' pò', + '砸' => ' zá', + '矐' => ' huò', + '硹' => ' sōng', + '碃' => ' qìng', + '碂' => ' zòng', + '碁' => ' qí', + '碀' => ' chéng', + '硿' => ' kōng', + '硾' => ' zhuì', + '硽' => ' yān', + '硼' => ' péng', + '硻' => ' kēng', + '硺' => ' zhuó', + '硸' => ' nüè', + '碅' => ' jūn', + '硷' => ' jiǎn', + '硶' => ' chěn', + '硵' => ' lǔ', + '硴' => ' hua', + '硳' => ' chì', + '硲' => ' yù', + '硱' => ' kǔn', + '硰' => ' shā', + '硯' => ' yàn', + '确' => ' què', + '碄' => ' lín', + '碆' => ' bō', + '硬' => ' yìng', + '碓' => ' duì', + '碝' => ' ruǎn', + '碜' => ' chěn', + '碛' => ' qì', + '碚' => ' bèi', + '碙' => ' náo', + '碘' => ' diǎn', + '碗' => ' wǎn', + '碖' => ' lǔn', + '碕' => ' qí', + '碔' => ' wǔ', + '碒' => ' yín', + '碇' => ' dìng', + '碑' => ' bēi', + '碐' => ' léng', + '碏' => ' què', + '碎' => ' suì', + '碍' => ' ài', + '碌' => ' lù', + '碋' => ' hè', + '碊' => ' jiān', + '碉' => ' diāo', + '碈' => ' mín', + '硭' => ' máng', + '硫' => ' liú', + '砹' => ' ài', + '硅' => ' guī', + '硏' => ' yán', + '硎' => ' xíng', + '硍' => ' xiàn', + '硌' => ' gè', + '硋' => ' ài', + '硊' => ' wěi', + '硉' => ' lù', + '硈' => ' qià', + '硇' => ' náo', + '硆' => ' è', + '硄' => ' kuāng', + '硑' => ' pēng', + '硃' => ' zhū', + '硂' => ' quán', + '硁' => ' kēng', + '础' => ' chǔ', + '砿' => ' kuang', + '砾' => ' lì', + '砽' => ' yong', + '砼' => ' tóng', + '砻' => ' lóng', + '砺' => ' lì', + '硐' => ' dòng', + '硒' => ' xī', + '硪' => ' wò', + '硟' => ' chàn', + '硩' => ' chè', + '硨' => ' chē', + '硧' => ' yǒng', + '硦' => ' luò', + '硥' => ' mǎng', + '硤' => ' xiá', + '硣' => ' xiāo', + '硢' => ' yú', + '硡' => ' hōng', + '硠' => ' láng', + '硞' => ' què', + '硓' => ' lao', + '硝' => ' xiāo', + '硜' => ' kēng', + '硛' => ' yì', + '硚' => ' qiáo', + '硙' => ' wéi', + '硘' => ' qing', + '硗' => ' qiāo', + '硖' => ' xiá', + '硕' => ' shuò', + '硔' => ' hóng', + '矑' => ' lú', + '矏' => ' mián', + '眀' => ' míng', + '着' => ' zhe', + '睊' => ' juàn', + '睉' => ' cuó', + '睈' => ' chěng', + '睇' => ' dì', + '睆' => ' huàn', + '睅' => ' hàn', + '睄' => ' shào', + '睃' => ' suō', + '睂' => ' méi', + '睁' => ' zhēng', + '眿' => ' mò', + '睌' => ' mǎn', + '眾' => ' zhòng', + '眽' => ' mò', + '眼' => ' yǎn', + '眻' => ' yáng', + '眺' => ' tiào', + '眹' => ' zhèn', + '眸' => ' móu', + '眷' => ' juàn', + '眶' => ' kuàng', + '眵' => ' chī', + '睋' => ' é', + '睍' => ' xiàn', + '眳' => ' míng', + '睚' => ' yá', + '睤' => ' bì', + '督' => ' dū', + '睢' => ' suī', + '睡' => ' shuì', + '睠' => ' juàn', + '睟' => ' suì', + '睞' => ' lài', + '睝' => ' lí', + '睜' => ' zhēng', + '睛' => ' jīng', + '睙' => ' liè', + '睎' => ' xī', + '睘' => ' qióng', + '睗' => ' shì', + '睖' => ' lèng', + '睕' => ' wǎn', + '睔' => ' gùn', + '睓' => ' tiǎn', + '睒' => ' shǎn', + '睑' => ' jiǎn', + '睐' => ' lài', + '睏' => ' kùn', + '眴' => ' xuàn', + '眲' => ' nè', + '睦' => ' mù', + '県' => ' xiàn', + '眖' => ' kuàng', + '眕' => ' zhěn', + '眔' => ' dà', + '眓' => ' huò', + '眒' => ' shēn', + '眑' => ' yǎo', + '眐' => ' zhēng', + '眏' => ' yāng', + '眎' => ' shì', + '眍' => ' kōu', + '看' => ' kàn', + '眘' => ' shèn', + '眊' => ' mào', + '眉' => ' méi', + '眈' => ' dān', + '眇' => ' miǎo', + '眆' => ' fǎng', + '眅' => ' pān', + '眄' => ' miǎn', + '眃' => ' yún', + '眂' => ' shì', + '省' => ' shěng', + '眗' => ' jū', + '眙' => ' yí', + '眱' => ' dì', + '眦' => ' zì', + '眰' => ' diè', + '眯' => ' mī', + '眮' => ' tóng', + '眭' => ' suī', + '眬' => ' lóng', + '眫' => ' mǐ', + '眪' => ' bǐng', + '眩' => ' xuàn', + '眨' => ' zhǎ', + '眧' => ' chǎo', + '眥' => ' zì', + '眚' => ' shěng', + '眤' => ' nì', + '眣' => ' dié', + '眢' => ' yuān', + '眡' => ' shì', + '眠' => ' mián', + '真' => ' zhēn', + '眞' => ' zhēn', + '眝' => ' zhù', + '眜' => ' mò', + '眛' => ' mèi', + '睥' => ' pì', + '睧' => ' hūn', + '矎' => ' xuān', + '瞩' => ' zhǔ', + '瞳' => ' tóng', + '瞲' => ' xù', + '瞱' => ' yè', + '瞰' => ' kàn', + '瞯' => ' xián', + '瞮' => ' chè', + '瞭' => ' liǎo', + '瞬' => ' shùn', + '瞫' => ' shěn', + '瞪' => ' dèng', + '瞨' => ' pú', + '瞵' => ' lín', + '瞧' => ' qiáo', + '瞦' => ' xī', + '瞥' => ' piē', + '瞤' => ' rún', + '瞣' => ' huan', + '瞢' => ' méng', + '瞡' => ' guǐ', + '瞠' => ' chēng', + '瞟' => ' piǎo', + '瞞' => ' mán', + '瞴' => ' móu', + '瞶' => ' guì', + '瞜' => ' lōu', + '矃' => ' nǐng', + '矍' => ' jué', + '矌' => ' kuàng', + '矋' => ' lì', + '矊' => ' mián', + '矉' => ' pín', + '矈' => ' mián', + '矇' => ' méng', + '矆' => ' huò', + '矅' => ' yào', + '矄' => ' xūn', + '矂' => ' sào', + '瞷' => ' jiàn', + '矁' => ' chǒu', + '矀' => ' méi', + '瞿' => ' qú', + '瞾' => ' zhào', + '瞽' => ' gǔ', + '瞼' => ' jiǎn', + '瞻' => ' zhān', + '瞺' => ' huì', + '瞹' => ' ài', + '瞸' => ' yè', + '瞝' => ' chī', + '瞛' => ' cōng', + '睨' => ' nì', + '睴' => ' gùn', + '睾' => ' gāo', + '睽' => ' kuí', + '睼' => ' tiàn', + '睻' => ' xuān', + '睺' => ' hóu', + '睹' => ' dǔ', + '睸' => ' mèi', + '睷' => ' jiān', + '睶' => ' chǔn', + '睵' => ' zāi', + '睳' => ' huī', + '瞀' => ' mào', + '睲' => ' xǐng', + '睱' => ' xià', + '睰' => ' mà', + '睯' => ' hūn', + '睮' => ' yú', + '睭' => ' zhǒu', + '睬' => ' cǎi', + '睫' => ' jié', + '睪' => ' yì', + '睩' => ' lù', + '睿' => ' ruì', + '瞁' => ' xù', + '瞚' => ' shùn', + '瞏' => ' qióng', + '瞙' => ' mò', + '瞘' => ' kōu', + '瞗' => ' diāo', + '瞖' => ' yì', + '瞕' => ' zhàng', + '瞔' => ' zé', + '瞓' => ' shuì', + '瞒' => ' mán', + '瞑' => ' míng', + '瞐' => ' mò', + '瞎' => ' xiā', + '瞂' => ' fá', + '瞍' => ' sǒu', + '瞌' => ' kē', + '瞋' => ' chēn', + '瞊' => ' dàng', + '瞉' => ' kòu', + '瞈' => ' wěng', + '瞇' => ' mī', + '瞆' => ' kuì', + '瞅' => ' chǒu', + '瞄' => ' miáo', + '瞃' => ' wò', + '畠' => ' tián', + '畞' => ' mǔ', + '爚' => ' yuè', + '猪' => ' zhū', + '猴' => ' hóu', + '猳' => ' jiā', + '猲' => ' xiē', + '猱' => ' náo', + '猰' => ' yà', + '猯' => ' tuān', + '献' => ' xiàn', + '猭' => ' chuān', + '猬' => ' wèi', + '猫' => ' māo', + '猩' => ' xīng', + '猶' => ' yóu', + '猨' => ' yuán', + '猧' => ' wō', + '猦' => ' fēng', + '猥' => ' wěi', + '猤' => ' guì', + '猣' => ' zōng', + '猢' => ' hú', + '猡' => ' luó', + '猠' => ' diǎn', + '猟' => ' lie', + '猵' => ' biān', + '猷' => ' yóu', + '猝' => ' cù', + '獄' => ' yù', + '獎' => ' jiǎng', + '獍' => ' jìng', + '獌' => ' màn', + '獋' => ' háo', + '獊' => ' cāng', + '獉' => ' zhēn', + '獈' => ' yì', + '獇' => ' qiang', + '獆' => ' háo', + '獅' => ' shī', + '獃' => ' dāi', + '猸' => ' méi', + '獂' => ' yuán', + '獁' => ' mà', + '獀' => ' sōu', + '猿' => ' yuán', + '猾' => ' huá', + '猽' => ' míng', + '猼' => ' bó', + '猻' => ' sūn', + '猺' => ' yáo', + '猹' => ' chá', + '猞' => ' shē', + '猜' => ' cāi', + '獐' => ' zhāng', + '狶' => ' xī', + '猀' => ' shā', + '狿' => ' yán', + '狾' => ' zhì', + '狽' => ' bèi', + '狼' => ' láng', + '狻' => ' suān', + '狺' => ' yín', + '狹' => ' xiá', + '狸' => ' lí', + '狷' => ' juàn', + '狵' => ' máng', + '猂' => ' hàn', + '狴' => ' bì', + '狳' => ' yú', + '狲' => ' sūn', + '狱' => ' yù', + '狰' => ' zhēng', + '狯' => ' kuài', + '狮' => ' shī', + '狭' => ' xiá', + '独' => ' dú', + '狫' => ' lǎo', + '猁' => ' lì', + '猃' => ' xiǎn', + '猛' => ' měng', + '猐' => ' qiāng', + '猚' => ' yá', + '猙' => ' zhēng', + '猘' => ' zhì', + '猗' => ' yī', + '猖' => ' chāng', + '猕' => ' mí', + '猔' => ' zòng', + '猓' => ' guǒ', + '猒' => ' yān', + '猑' => ' kūn', + '猏' => ' jiān', + '猄' => ' jīng', + '猎' => ' liè', + '猍' => ' lái', + '猌' => ' yìn', + '猋' => ' biāo', + '猊' => ' ní', + '猉' => ' qí', + '猈' => ' bài', + '猇' => ' xiāo', + '猆' => ' fēi', + '猅' => ' pái', + '獏' => ' mò', + '獑' => ' chán', + '狩' => ' shòu', + '玒' => ' hóng', + '玜' => ' gōng', + '玛' => ' mǎ', + '玚' => ' chàng', + '玙' => ' yú', + '玘' => ' qǐ', + '玗' => ' yú', + '玖' => ' jiǔ', + '玕' => ' gān', + '玔' => ' chuàn', + '玓' => ' dì', + '玑' => ' jī', + '玞' => ' fū', + '玐' => ' bā', + '玏' => ' lè', + '玎' => ' dīng', + '玍' => ' gǎ', + '玌' => ' qiú', + '王' => ' wáng', + '玊' => ' sù', + '玉' => ' yù', + '玈' => ' lú', + '率' => ' lǜ', + '玝' => ' wǔ', + '玟' => ' wén', + '玅' => ' miào', + '玬' => ' dǎn', + '玶' => ' píng', + '玵' => ' án', + '玴' => ' yì', + '玳' => ' dài', + '玲' => ' líng', + '玱' => ' qiāng', + '现' => ' xiàn', + '环' => ' huán', + '玮' => ' wěi', + '玭' => ' pín', + '玫' => ' méi', + '玠' => ' jiè', + '玪' => ' jiān', + '玩' => ' wán', + '玨' => ' jué', + '玧' => ' mén', + '玦' => ' jué', + '玥' => ' yuè', + '玤' => ' bàng', + '玣' => ' biàn', + '玢' => ' bīn', + '玡' => ' yá', + '玆' => ' zī', + '玄' => ' xuán', + '獒' => ' áo', + '獞' => ' tóng', + '獨' => ' dú', + '獧' => ' juàn', + '獦' => ' gé', + '獥' => ' jiào', + '獤' => ' dūn', + '獣' => ' shou', + '獢' => ' xiāo', + '獡' => ' shuò', + '獠' => ' liáo', + '獟' => ' yào', + '獝' => ' xù', + '獪' => ' kuài', + '獜' => ' lín', + '獛' => ' pú', + '獚' => ' huáng', + '獙' => ' bì', + '獘' => ' bì', + '獗' => ' jué', + '獖' => ' bèn', + '獕' => ' cuī', + '獔' => ' háo', + '獓' => ' áo', + '獩' => ' huì', + '獫' => ' xiǎn', + '玃' => ' jué', + '獸' => ' shòu', + '玂' => ' qí', + '玁' => ' xiǎn', + '玀' => ' luó', + '獿' => ' nǎo', + '獾' => ' huān', + '獽' => ' ráng', + '獼' => ' mí', + '獻' => ' xiàn', + '獺' => ' tǎ', + '獹' => ' lú', + '獷' => ' guǎng', + '獬' => ' xiè', + '獶' => ' nǎo', + '獵' => ' liè', + '獴' => ' měng', + '獳' => ' nòu', + '獲' => ' huò', + '獱' => ' pín', + '獰' => ' níng', + '獯' => ' xūn', + '獮' => ' xiǎn', + '獭' => ' tǎ', + '狪' => ' tóng', + '狨' => ' róng', + '玸' => ' fú', + '牚' => ' chēng', + '牤' => ' māng', + '牣' => ' rèn', + '牢' => ' láo', + '牡' => ' mǔ', + '牠' => ' tā', + '薡' => ' dǐng', + '牞' => ' jiū', + '牝' => ' pìn', + '牜' => ' niu', + '牛' => ' niú', + '牙' => ' yá', + '牦' => ' máo', + '牘' => ' dú', + '牗' => ' you', + '牖' => ' yǒu', + '牕' => ' chuāng', + '牔' => ' bó', + '牓' => ' bǎng', + '牒' => ' dié', + '牑' => ' biān', + '牐' => ' zhá', + '牏' => ' yú', + '牥' => ' fāng', + '牧' => ' mù', + '牍' => ' dú', + '牴' => ' dǐ', + '牾' => ' wǔ', + '牽' => ' qiān', + '牼' => ' kēng', + '牻' => ' máng', + '牺' => ' xī', + '特' => ' tè', + '牸' => ' zì', + '牷' => ' quán', + '牶' => ' quàn', + '牵' => ' qiān', + '牳' => ' mǔ', + '牨' => ' gāng', + '牲' => ' shēng', + '牱' => ' gē', + '牰' => ' yòu', + '牯' => ' gǔ', + '牮' => ' jiàn', + '牭' => ' sì', + '牬' => ' bèi', + '牫' => ' gē', + '牪' => ' yàn', + '物' => ' wù', + '牎' => ' chuāng', + '牌' => ' pái', + '犀' => ' xī', + '爦' => ' lǎn', + '爰' => ' yuán', + '爯' => ' chēng', + '爮' => ' páo', + '爭' => ' zhēng', + '爬' => ' pá', + '爫' => ' zhao', + '爪' => ' zhǎo', + '爩' => ' yù', + '爨' => ' cuàn', + '爧' => ' líng', + '爥' => ' zhú', + '爲' => ' wèi', + '爤' => ' làn', + '爣' => ' tǎng', + '爢' => ' mí', + '爡' => ' chè', + '爠' => ' ju', + '爟' => ' guàn', + '爞' => ' chóng', + '爝' => ' jué', + '爜' => ' cóng', + '爛' => ' làn', + '爱' => ' ài', + '爳' => ' han', + '牋' => ' jiān', + '牀' => ' chuáng', + '牊' => ' cháo', + '牉' => ' pàn', + '版' => ' bǎn', + '片' => ' piàn', + '牆' => ' qiáng', + '牅' => ' yóng', + '牄' => ' qiāng', + '牃' => ' dié', + '牂' => ' zāng', + '牁' => ' kē', + '爿' => ' pán', + '爴' => ' jué', + '爾' => ' ěr', + '爽' => ' shuǎng', + '爼' => ' zǔ', + '爻' => ' yáo', + '爺' => ' yé', + '爹' => ' diē', + '爸' => ' bà', + '爷' => ' yé', + '父' => ' fù', + '爵' => ' jué', + '牿' => ' gù', + '犁' => ' lí', + '狧' => ' tà', + '狂' => ' kuáng', + '狌' => ' shēng', + '狋' => ' yí', + '狊' => ' jú', + '狉' => ' pī', + '狈' => ' bèi', + '狇' => ' mù', + '狆' => ' zhòng', + '狅' => ' qīng', + '狄' => ' dí', + '狃' => ' niǔ', + '狁' => ' yǔn', + '狎' => ' xiá', + '狀' => ' zhuàng', + '犿' => ' huān', + '犾' => ' yín', + '犽' => ' yà', + '犼' => ' hǒu', + '犻' => ' bó', + '犺' => ' kàng', + '犹' => ' yóu', + '犸' => ' mà', + '犷' => ' guǎng', + '狍' => ' páo', + '狏' => ' tuó', + '犵' => ' gē', + '狜' => ' kǔ', + '狦' => ' shān', + '狥' => ' xùn', + '狤' => ' jí', + '狣' => ' zhào', + '狢' => ' hé', + '狡' => ' jiǎo', + '狠' => ' hěn', + '狟' => ' huán', + '狞' => ' níng', + '狝' => ' xiǎn', + '狛' => ' bó', + '狐' => ' hú', + '狚' => ' dàn', + '狙' => ' jū', + '狘' => ' xuè', + '狗' => ' gǒu', + '狖' => ' yòu', + '狕' => ' yǎo', + '狔' => ' nǐ', + '狓' => ' pí', + '狒' => ' fèi', + '狑' => ' líng', + '状' => ' zhuàng', + '犴' => ' àn', + '犂' => ' lí', + '犎' => ' fēng', + '犘' => ' má', + '犗' => ' jiè', + '犖' => ' luò', + '犕' => ' bèi', + '犔' => ' xì', + '犓' => ' chú', + '犒' => ' kào', + '犑' => ' jú', + '犐' => ' kē', + '犏' => ' piān', + '犍' => ' jiān', + '犚' => ' wèi', + '犌' => ' jiā', + '犋' => ' jù', + '犊' => ' dú', + '犉' => ' chún', + '犈' => ' quán', + '犇' => ' bēn', + '犆' => ' zhí', + '犅' => ' gāng', + '犄' => ' jī', + '犃' => ' pǒu', + '犙' => ' sān', + '犛' => ' máo', + '犳' => ' zhuó', + '犨' => ' chōu', + '犲' => ' cái', + '犱' => ' jǐ', + '犰' => ' qiú', + '犯' => ' fàn', + '犮' => ' bá', + '犭' => ' quǎn', + '犬' => ' quǎn', + '犫' => ' chōu', + '犪' => ' kuí', + '犩' => ' wéi', + '犧' => ' xī', + '犜' => ' dūn', + '犦' => ' bó', + '犥' => ' piāo', + '犤' => ' pái', + '犣' => ' liè', + '犢' => ' dú', + '犡' => ' lì', + '犠' => ' xi', + '犟' => ' jiàng', + '犞' => ' qiao', + '犝' => ' tóng', + '玷' => ' diàn', + '玹' => ' xuán', + '畝' => ' mǔ', + '瓋' => ' tì', + '瓕' => ' mí', + '瓔' => ' yīng', + '瓓' => ' làn', + '瓒' => ' zàn', + '瓑' => ' lì', + '瓐' => ' lú', + '瓏' => ' lóng', + '瓎' => ' là', + '瓍' => ' suí', + '瓌' => ' guī', + '瓊' => ' qióng', + '瓗' => ' qióng', + '瓉' => ' zàn', + '瓈' => ' lí', + '瓇' => ' róu', + '瓆' => ' zhì', + '瓅' => ' lì', + '瓄' => ' dú', + '瓃' => ' léi', + '瓂' => ' gài', + '瓁' => ' wò', + '瓀' => ' ruǎn', + '瓖' => ' xiāng', + '瓘' => ' guàn', + '璾' => ' zī', + '瓥' => ' lì', + '瓲' => ' wa', + '瓯' => ' ōu', + '瓮' => ' wèng', + '瓭' => ' dǎn', + '瓬' => ' fǎng', + '瓫' => ' pén', + '瓪' => ' bǎn', + '瓩' => ' qiān', + '瓨' => ' xiáng', + '瓦' => ' wǎ', + '瓤' => ' ráng', + '瓙' => ' dào', + '瓣' => ' bàn', + '瓢' => ' piáo', + '瓡' => ' zhí', + '瓠' => ' hù', + '瓟' => ' bó', + '瓞' => ' dié', + '瓝' => ' bó', + '瓜' => ' guā', + '瓛' => ' huán', + '瓚' => ' zàn', + '璿' => ' xuán', + '璽' => ' xǐ', + '瓴' => ' líng', + '璗' => ' dàng', + '璡' => ' jìn', + '璠' => ' fán', + '璟' => ' jǐng', + '璞' => ' pú', + '璝' => ' guī', + '璜' => ' huáng', + '璛' => ' sù', + '璚' => ' qióng', + '璙' => ' liáo', + '璘' => ' lín', + '璖' => ' qú', + '璣' => ' jī', + '璕' => ' xún', + '璔' => ' zēng', + '璓' => ' xiù', + '璒' => ' dēng', + '璑' => ' wú', + '璐' => ' lù', + '璏' => ' wèi', + '璎' => ' yīng', + '璍' => ' hua', + '璌' => ' yín', + '璢' => ' liú', + '璤' => ' hui', + '璼' => ' lán', + '璱' => ' sè', + '璻' => ' zuǐ', + '璺' => ' wèn', + '璹' => ' shú', + '璸' => ' bīn', + '璷' => ' lú', + '璶' => ' jìn', + '璵' => ' yú', + '璴' => ' chu', + '璳' => ' tián', + '璲' => ' suì', + '環' => ' huán', + '璥' => ' jǐng', + '璯' => ' huì', + '璮' => ' tǎn', + '璭' => ' gùn', + '璬' => ' jiǎo', + '璫' => ' dāng', + '璪' => ' zǎo', + '璩' => ' qú', + '璨' => ' càn', + '璧' => ' bì', + '璦' => ' ài', + '瓳' => ' hú', + '瓵' => ' yí', + '璊' => ' mén', + '甸' => ' diān', + '畂' => ' liù', + '畁' => ' qí', + '畀' => ' bì', + '甿' => ' méng', + '甾' => ' zāi', + '甽' => ' zhèn', + '甼' => ' tǐng', + '画' => ' huà', + '町' => ' tīng', + '甹' => ' pīng', + '男' => ' nán', + '畄' => ' liú', + '甶' => ' fú', + '电' => ' diàn', + '甴' => ' zhá', + '申' => ' shēn', + '甲' => ' jiǎ', + '由' => ' yóu', + '田' => ' tián', + '甯' => ' níng', + '甮' => ' fèng', + '甭' => ' béng', + '畃' => ' xún', + '畅' => ' chàng', + '甫' => ' fǔ', + '畒' => ' mǔ', + '畜' => ' chù', + '畛' => ' zhěn', + '畚' => ' běn', + '留' => ' liú', + '畘' => ' nán', + '畗' => ' dá', + '畖' => ' wā', + '畕' => ' jiāng', + '畔' => ' pàn', + '畓' => ' duō', + '畑' => ' tián', + '畆' => ' mǔ', + '畐' => ' fú', + '畏' => ' wèi', + '畎' => ' quǎn', + '畍' => ' jiè', + '界' => ' jiè', + '畋' => ' tián', + '畊' => ' gēng', + '畉' => ' fú', + '畈' => ' fàn', + '畇' => ' yún', + '甬' => ' yǒng', + '甪' => ' lù', + '瓶' => ' píng', + '甃' => ' zhòu', + '甎' => ' zhuān', + '甍' => ' méng', + '甌' => ' ōu', + '甋' => ' dì', + '甊' => ' lǒu', + '甉' => ' xián', + '甈' => ' qì', + '甇' => ' yīng', + '甆' => ' cí', + '甄' => ' zhēn', + '甂' => ' biān', + '甐' => ' lìn', + '甁' => ' píng', + '甀' => ' zhuì', + '瓿' => ' bù', + '瓾' => ' měng', + '瓽' => ' dàng', + '瓻' => ' chī', + '瓺' => ' cháng', + '瓹' => ' juān', + '瓸' => ' bǎi', + '瓷' => ' cí', + '甏' => ' bèng', + '甑' => ' zèng', + '甩' => ' shuǎi', + '甞' => ' cháng', + '用' => ' yòng', + '甧' => ' shēn', + '甦' => ' sū', + '甥' => ' shēng', + '甤' => ' ruí', + '産' => ' chǎn', + '產' => ' chǎn', + '甡' => ' shēn', + '甠' => ' qíng', + '生' => ' shēng', + '甝' => ' hán', + '甒' => ' wǔ', + '甜' => ' tián', + '甛' => ' tián', + '甚' => ' shén', + '甙' => ' dài', + '甘' => ' gān', + '甗' => ' yǎn', + '甖' => ' yīng', + '甕' => ' wèng', + '甔' => ' dān', + '甓' => ' pì', + '璋' => ' zhāng', + '璉' => ' liǎn', + '玺' => ' xǐ', + '珺' => ' jùn', + '琄' => ' xuàn', + '球' => ' qiú', + '琂' => ' yán', + '琁' => ' xuán', + '琀' => ' hán', + '珿' => ' chù', + '現' => ' xiàn', + '珽' => ' tǐng', + '珼' => ' bèi', + '珻' => ' méi', + '珹' => ' chéng', + '理' => ' lǐ', + '珸' => ' wú', + '珷' => ' wǔ', + '珶' => ' dì', + '珵' => ' chéng', + '珴' => ' é', + '珳' => ' wén', + '珲' => ' huī', + '珱' => ' ying', + '珰' => ' dāng', + '珯' => ' lao', + '琅' => ' láng', + '琇' => ' xiù', + '班' => ' bān', + '琔' => ' diàn', + '琞' => ' shèng', + '琝' => ' wén', + '琜' => ' lái', + '琛' => ' chēn', + '琚' => ' jū', + '琙' => ' yù', + '琘' => ' mín', + '琗' => ' sè', + '琖' => ' zhǎn', + '琕' => ' pín', + '琓' => ' wán', + '琈' => ' fú', + '琒' => ' feng', + '琑' => ' suo', + '琐' => ' suǒ', + '琏' => ' liǎn', + '琎' => ' jìn', + '琍' => ' lí', + '琌' => ' líng', + '琋' => ' xī', + '琊' => ' yá', + '琉' => ' liú', + '珮' => ' pèi', + '珬' => ' xù', + '琠' => ' tiǎn', + '珆' => ' yí', + '珐' => ' fà', + '珏' => ' jué', + '珎' => ' zhēn', + '珍' => ' zhēn', + '珌' => ' bì', + '珋' => ' liǔ', + '珊' => ' shān', + '珉' => ' mín', + '珈' => ' jiā', + '珇' => ' zǔ', + '珅' => ' shēn', + '珒' => ' jīn', + '珄' => ' shēng', + '珃' => ' rǎn', + '珂' => ' kē', + '珁' => ' cí', + '珀' => ' pò', + '玿' => ' sháo', + '玾' => ' jiǎ', + '玽' => ' gǒu', + '玼' => ' cǐ', + '玻' => ' bō', + '珑' => ' lóng', + '珓' => ' jiào', + '珫' => ' chōng', + '珠' => ' zhū', + '珪' => ' guī', + '珩' => ' háng', + '珨' => ' xiá', + '珧' => ' yáo', + '珦' => ' xiàng', + '珥' => ' ěr', + '珤' => ' bǎo', + '珣' => ' xún', + '珢' => ' yín', + '珡' => ' qín', + '珟' => ' sù', + '珔' => ' jiàn', + '珞' => ' luò', + '珝' => ' xǔ', + '珜' => ' yáng', + '珛' => ' xiù', + '珚' => ' yān', + '珙' => ' gǒng', + '珘' => ' zhōu', + '珗' => ' xiān', + '珖' => ' guàng', + '珕' => ' lì', + '琟' => ' wéi', + '琡' => ' chù', + '璈' => ' áo', + '瑣' => ' suǒ', + '瑭' => ' táng', + '瑬' => ' liú', + '瑫' => ' tāo', + '瑪' => ' mǎ', + '瑩' => ' yíng', + '瑨' => ' jìn', + '瑧' => ' zhēn', + '瑦' => ' wǔ', + '瑥' => ' wēn', + '瑤' => ' yáo', + '瑢' => ' róng', + '瑯' => ' láng', + '瑡' => ' shī', + '瑠' => ' liú', + '瑟' => ' sè', + '瑞' => ' ruì', + '瑝' => ' huáng', + '瑜' => ' yú', + '瑛' => ' yīng', + '瑚' => ' hú', + '瑙' => ' nǎo', + '瑘' => ' yá', + '瑮' => ' lì', + '瑰' => ' guī', + '瑖' => ' duàn', + '瑽' => ' cōng', + '璇' => ' xuán', + '璆' => ' qiú', + '璅' => ' suǒ', + '璄' => ' yǐng', + '璃' => ' lí', + '璂' => ' qí', + '璁' => ' cōng', + '璀' => ' cuǐ', + '瑿' => ' yī', + '瑾' => ' jǐn', + '瑼' => ' zhuān', + '瑱' => ' zhèn', + '瑻' => ' kūn', + '瑺' => ' cháng', + '瑹' => ' shū', + '瑸' => ' bīn', + '瑷' => ' ài', + '瑶' => ' yáo', + '瑵' => ' zhǎo', + '瑴' => ' jué', + '瑳' => ' cuō', + '瑲' => ' qiāng', + '瑗' => ' yuàn', + '瑕' => ' xiá', + '琢' => ' zuó', + '琮' => ' cóng', + '琸' => ' zhuó', + '琷' => ' què', + '琶' => ' pá', + '琵' => ' pí', + '琴' => ' qín', + '琳' => ' lín', + '琲' => ' bèi', + '琱' => ' diāo', + '琰' => ' yǎn', + '琯' => ' guǎn', + '琭' => ' lù', + '琺' => ' fà', + '琬' => ' wǎn', + '琫' => ' běng', + '琪' => ' qí', + '琩' => ' chāng', + '琨' => ' kūn', + '琧' => ' è', + '琦' => ' qí', + '琥' => ' hǔ', + '琤' => ' chēng', + '琣' => ' běng', + '琹' => ' qín', + '琻' => ' jin', + '瑔' => ' quán', + '瑉' => ' mín', + '瑓' => ' liàn', + '瑒' => ' chàng', + '瑑' => ' zhuàn', + '瑐' => ' jiǎn', + '瑏' => ' chuān', + '瑎' => ' xié', + '瑍' => ' huàn', + '瑌' => ' ruǎn', + '瑋' => ' wěi', + '瑊' => ' jiān', + '瑈' => ' róu', + '琼' => ' qióng', + '瑇' => ' dài', + '瑆' => ' xīng', + '瑅' => ' tí', + '瑄' => ' xuān', + '瑃' => ' chūn', + '瑂' => ' méi', + '瑁' => ' mào', + '瑀' => ' yǔ', + '琿' => ' hún', + '琾' => ' jiè', + '琽' => ' dǔ', + '薠' => ' fán', + '诛' => ' zhū', + '薢' => ' xiè', + '雸' => ' án', + '霂' => ' mù', + '霁' => ' jì', + '需' => ' xū', + '雿' => ' diào', + '雾' => ' wù', + '雽' => ' hù', + '雼' => ' dàng', + '電' => ' diàn', + '雺' => ' wù', + '雹' => ' báo', + '雷' => ' léi', + '霄' => ' xiāo', + '零' => ' líng', + '雵' => ' yāng', + '雴' => ' chì', + '雳' => ' lì', + '雲' => ' yún', + '雱' => ' pāng', + '雰' => ' fēn', + '雯' => ' wén', + '雮' => ' mù', + '雭' => ' sè', + '霃' => ' chén', + '霅' => ' zhà', + '雫' => ' nǎ', + '霒' => ' yīn', + '霜' => ' shuāng', + '霛' => ' líng', + '霚' => ' wù', + '霙' => ' yīng', + '霘' => ' dòng', + '霗' => ' ling', + '霖' => ' lín', + '霕' => ' tún', + '霔' => ' zhù', + '霓' => ' ní', + '霑' => ' zhān', + '霆' => ' tíng', + '霐' => ' hóng', + '霏' => ' fēi', + '霎' => ' shà', + '霍' => ' huò', + '霌' => ' zhōu', + '霋' => ' qī', + '霊' => ' líng', + '霉' => ' méi', + '霈' => ' pèi', + '震' => ' zhèn', + '雬' => ' fǒu', + '雪' => ' xuě', + '霞' => ' xiá', + '雄' => ' xióng', + '雎' => ' jū', + '雍' => ' yōng', + '雌' => ' cí', + '雋' => ' juàn', + '雊' => ' gòu', + '雉' => ' zhì', + '雈' => ' huán', + '雇' => ' gù', + '集' => ' jí', + '雅' => ' yǎ', + '雃' => ' qiān', + '雐' => ' hū', + '雂' => ' qín', + '雁' => ' yàn', + '雀' => ' què', + '隿' => ' yì', + '难' => ' nán', + '隽' => ' juàn', + '隼' => ' sǔn', + '隻' => ' zhī', + '隺' => ' hú', + '隹' => ' zhuī', + '雏' => ' chú', + '雑' => ' zá', + '雩' => ' yú', + '雞' => ' jī', + '雨' => ' yǔ', + '雧' => ' jí', + '雦' => ' jí', + '雥' => ' zá', + '雤' => ' xué', + '難' => ' nán', + '離' => ' lí', + '雡' => ' liù', + '雠' => ' chóu', + '雟' => ' xī', + '雝' => ' yōng', + '雒' => ' luò', + '雜' => ' zá', + '雛' => ' chú', + '雚' => ' guàn', + '雙' => ' shuāng', + '雘' => ' wò', + '雗' => ' hàn', + '雖' => ' suī', + '雕' => ' diāo', + '雔' => ' chóu', + '雓' => ' yú', + '霝' => ' líng', + '霟' => ' hóng', + '隷' => ' lì', + '靠' => ' kào', + '靪' => ' dīng', + '革' => ' gé', + '靨' => ' yè', + '靧' => ' huì', + '靦' => ' tiǎn', + '靥' => ' yè', + '靤' => ' bào', + '靣' => ' miàn', + '面' => ' miàn', + '靡' => ' mí', + '靟' => ' fēi', + '靬' => ' qián', + '非' => ' fēi', + '靝' => ' tiān', + '靜' => ' jìng', + '靛' => ' diàn', + '靚' => ' jìng', + '静' => ' jìng', + '靘' => ' qìng', + '靗' => ' chēng', + '靖' => ' jìng', + '靕' => ' zhēn', + '靫' => ' chá', + '靭' => ' rèn', + '靓' => ' jìng', + '靺' => ' mò', + '鞄' => ' páo', + '鞃' => ' hóng', + '鞂' => ' jiá', + '鞁' => ' bèi', + '鞀' => ' táo', + '靿' => ' yào', + '靾' => ' yì', + '靽' => ' bàn', + '靼' => ' dá', + '靻' => ' zǔ', + '靹' => ' nà', + '靮' => ' dí', + '靸' => ' sǎ', + '靷' => ' yǐn', + '靶' => ' bǎ', + '靵' => ' niǔ', + '靴' => ' xuē', + '靳' => ' jìn', + '靲' => ' qín', + '靱' => ' rèn', + '靰' => ' wù', + '靯' => ' dù', + '靔' => ' tiān', + '青' => ' qīng', + '霠' => ' yīn', + '霬' => ' yì', + '霶' => ' pāng', + '霵' => ' jí', + '霴' => ' dài', + '霳' => ' lóng', + '露' => ' lù', + '霱' => ' yù', + '霰' => ' sǎn', + '霯' => ' tèng', + '霮' => ' dàn', + '霭' => ' ǎi', + '霫' => ' xí', + '霸' => ' bà', + '霪' => ' yín', + '霩' => ' kuò', + '霨' => ' wèi', + '霧' => ' wù', + '霦' => ' bīn', + '霥' => ' mèng', + '霤' => ' liù', + '霣' => ' yǔn', + '霢' => ' mài', + '霡' => ' mài', + '霷' => ' yáng', + '霹' => ' pī', + '靑' => ' qīng', + '靆' => ' dài', + '靐' => ' bìng', + '靏' => ' hè', + '靎' => ' hè', + '靍' => ' he', + '靌' => ' bǎo', + '靋' => ' lì', + '靊' => ' fēng', + '靉' => ' ài', + '靈' => ' líng', + '靇' => ' lóng', + '靅' => ' fèi', + '霺' => ' wéi', + '靄' => ' ǎi', + '靃' => ' huò', + '靂' => ' lì', + '靁' => ' léi', + '靀' => ' méng', + '霿' => ' méng', + '霾' => ' mái', + '霽' => ' jì', + '霼' => ' xì', + '霻' => ' fēng', + '隸' => ' lì', + '隶' => ' lì', + '鞆' => ' bing', + '阨' => ' è', + '防' => ' fáng', + '阱' => ' jǐng', + '阰' => ' pí', + '阯' => ' zhǐ', + '阮' => ' ruǎn', + '阭' => ' yǔn', + '阬' => ' kēng', + '阫' => ' péi', + '阪' => ' bǎn', + '阩' => ' shēng', + '阧' => ' dǒu', + '阴' => ' yīn', + '阦' => ' yáng', + '阥' => ' yīn', + '阤' => ' zhì', + '阣' => ' gài', + '阢' => ' wù', + '阡' => ' qiān', + '阠' => ' xìn', + '队' => ' duì', + '阞' => ' lè', + '阝' => ' fù', + '阳' => ' yáng', + '阵' => ' zhèn', + '阛' => ' huán', + '陂' => ' bēi', + '陌' => ' mò', + '陋' => ' lòu', + '陊' => ' duò', + '陉' => ' xíng', + '陈' => ' chén', + '陇' => ' lǒng', + '陆' => ' lù', + '际' => ' jì', + '附' => ' fù', + '陃' => ' bǐng', + '陁' => ' tuó', + '阶' => ' jiē', + '陀' => ' tuó', + '阿' => ' ā', + '阾' => ' lǐng', + '阽' => ' diàn', + '阼' => ' zuò', + '阻' => ' zǔ', + '阺' => ' dǐ', + '阹' => ' qū', + '阸' => ' è', + '阷' => ' chēng', + '阜' => ' fù', + '阚' => ' hǎn', + '陎' => ' shū', + '间' => ' jiān', + '闾' => ' lǘ', + '闽' => ' mǐn', + '闼' => ' tà', + '闻' => ' wén', + '闺' => ' guī', + '闹' => ' nào', + '闸' => ' zhá', + '闷' => ' mèn', + '闶' => ' kāng', + '闵' => ' mǐn', + '闳' => ' hóng', + '阀' => ' fá', + '闲' => ' xián', + '闱' => ' wéi', + '闰' => ' rùn', + '闯' => ' chuǎng', + '问' => ' wèn', + '闭' => ' bì', + '闬' => ' hàn', + '闫' => ' yàn', + '闪' => ' shǎn', + '闩' => ' shuān', + '闿' => ' kǎi', + '阁' => ' gé', + '阙' => ' quē', + '阎' => ' yán', + '阘' => ' dá', + '阗' => ' tián', + '阖' => ' hé', + '阕' => ' què', + '阔' => ' kuò', + '阓' => ' huì', + '阒' => ' qù', + '阑' => ' lán', + '阐' => ' chǎn', + '阏' => ' è', + '阍' => ' hūn', + '阂' => ' hé', + '阌' => ' wén', + '阋' => ' xì', + '阊' => ' chāng', + '阉' => ' yān', + '阈' => ' yù', + '阇' => ' dū', + '阆' => ' láng', + '阅' => ' yuè', + '阄' => ' jiū', + '阃' => ' kǔn', + '降' => ' jiàng', + '陏' => ' duò', + '隵' => ' xī', + '隐' => ' yǐn', + '隚' => ' táng', + '隙' => ' xì', + '隘' => ' ài', + '隗' => ' kuí', + '隖' => ' wù', + '隕' => ' yǔn', + '隔' => ' gé', + '隓' => ' huī', + '隒' => ' yǎn', + '隑' => ' gài', + '随' => ' suí', + '障' => ' zhàng', + '階' => ' jiē', + '隍' => ' huáng', + '隌' => ' ǎn', + '隋' => ' suí', + '隊' => ' duì', + '隉' => ' niè', + '隈' => ' wēi', + '隇' => ' wēi', + '隆' => ' lóng', + '隅' => ' yú', + '際' => ' jì', + '隝' => ' dǎo', + '隃' => ' shù', + '險' => ' xiǎn', + '隴' => ' lǒng', + '隳' => ' huī', + '隲' => ' é', + '隱' => ' yǐn', + '隰' => ' xí', + '隯' => ' dǎo', + '隮' => ' jī', + '隭' => ' ér', + '隬' => ' nǐ', + '隫' => ' fén', + '隩' => ' ào', + '隞' => ' áo', + '隨' => ' suí', + '隧' => ' suì', + '隦' => ' jiǎo', + '隥' => ' dèng', + '隤' => ' tuí', + '隣' => ' lín', + '隢' => ' rǎo', + '隡' => ' sa', + '隠' => ' yǐn', + '隟' => ' xì', + '隄' => ' dī', + '隂' => ' yīn', + '限' => ' xiàn', + '陜' => ' xiá', + '陦' => ' dao', + '陥' => ' xiàn', + '除' => ' chú', + '陣' => ' zhèn', + '院' => ' yuàn', + '陡' => ' dǒu', + '陠' => ' pū', + '陟' => ' zhì', + '陞' => ' shēng', + '陝' => ' shǎn', + '陛' => ' bì', + '陨' => ' yǔn', + '陚' => ' fù', + '陙' => ' chún', + '陘' => ' xíng', + '陗' => ' qiào', + '陖' => ' jùn', + '陕' => ' shǎn', + '陔' => ' gāi', + '陓' => ' yū', + '陒' => ' guǐ', + '陑' => ' ér', + '陧' => ' niè', + '险' => ' xiǎn', + '隁' => ' yàn', + '陶' => ' táo', + '隀' => ' chóng', + '陿' => ' xiá', + '陾' => ' réng', + '陽' => ' yáng', + '陼' => ' zhǔ', + '陻' => ' yīn', + '険' => ' xiǎn', + '陹' => ' shēng', + '陸' => ' lù', + '陷' => ' xiàn', + '陵' => ' líng', + '陪' => ' péi', + '陴' => ' pí', + '陳' => ' chén', + '陲' => ' chuí', + '陱' => ' jū', + '陰' => ' yīn', + '陯' => ' lún', + '陮' => ' duì', + '陭' => ' yì', + '陬' => ' zōu', + '陫' => ' fèi', + '鞅' => ' yāng', + '鞇' => ' yīn', + '闧' => ' tā', + '题' => ' tí', + '颢' => ' hào', + '颡' => ' sǎng', + '颠' => ' diān', + '颟' => ' mān', + '颞' => ' niè', + '额' => ' é', + '颜' => ' yán', + '颛' => ' zhuān', + '颚' => ' è', + '颙' => ' yóng', + '颗' => ' kē', + '颤' => ' chàn', + '颖' => ' yǐng', + '颕' => ' yǐng', + '颔' => ' hàn', + '颓' => ' tuí', + '颒' => ' huì', + '频' => ' pín', + '颐' => ' yí', + '颏' => ' kē', + '颎' => ' jiǒng', + '颍' => ' yǐng', + '颣' => ' lèi', + '颥' => ' rú', + '颋' => ' tǐng', + '颲' => ' liè', + '颼' => ' sōu', + '颻' => ' yáo', + '颺' => ' yáng', + '颹' => ' wěi', + '颸' => ' sī', + '颷' => ' biāo', + '颶' => ' jù', + '颵' => ' shāo', + '颴' => ' xuàn', + '颳' => ' guā', + '颱' => ' tái', + '颦' => ' pín', + '颰' => ' bá', + '颯' => ' sà', + '颮' => ' biāo', + '颭' => ' zhǎn', + '颬' => ' xiā', + '颫' => ' fú', + '颪' => ' gua', + '颩' => ' biāo', + '風' => ' fēng', + '颧' => ' quán', + '颌' => ' hé', + '颊' => ' jiá', + '颾' => ' sōu', + '顤' => ' yáo', + '顮' => ' bīn', + '顭' => ' méng', + '顬' => ' rú', + '顫' => ' chàn', + '顪' => ' huì', + '顩' => ' yǎn', + '顨' => ' xùn', + '顧' => ' gù', + '顦' => ' qiáo', + '顥' => ' hào', + '顣' => ' cù', + '顰' => ' pín', + '顢' => ' mán', + '顡' => ' wài', + '顠' => ' piǎo', + '顟' => ' láo', + '類' => ' lèi', + '顝' => ' kuī', + '顜' => ' jiǎng', + '顛' => ' diān', + '顚' => ' diān', + '顙' => ' sǎng', + '顯' => ' xiǎn', + '顱' => ' lú', + '颉' => ' jié', + '顾' => ' gù', + '颈' => ' jǐng', + '颇' => ' pō', + '领' => ' lǐng', + '颅' => ' lú', + '预' => ' yù', + '颃' => ' háng', + '颂' => ' sòng', + '颁' => ' bān', + '颀' => ' qí', + '顿' => ' dùn', + '顽' => ' wán', + '顲' => ' lǎn', + '顼' => ' xū', + '须' => ' xū', + '顺' => ' shùn', + '项' => ' xiàng', + '顸' => ' hān', + '顷' => ' qǐng', + '顶' => ' dǐng', + '页' => ' yè', + '顴' => ' quán', + '顳' => ' niè', + '颽' => ' kǎi', + '颿' => ' fān', + '顗' => ' yǐ', + '餀' => ' hài', + '養' => ' yǎng', + '餉' => ' xiǎng', + '餈' => ' cí', + '餇' => ' tóng', + '餆' => ' yáo', + '餅' => ' bǐng', + '餄' => ' jiá', + '餃' => ' jiǎo', + '餂' => ' tiǎn', + '餁' => ' rèn', + '飿' => ' duò', + '餌' => ' ěr', + '飾' => ' shì', + '飽' => ' bǎo', + '飼' => ' sì', + '飻' => ' tiè', + '飺' => ' cí', + '飹' => ' liǔ', + '飸' => ' tāo', + '飷' => ' jiě', + '飶' => ' bì', + '飵' => ' zuò', + '餋' => ' juàn', + '餍' => ' yàn', + '飳' => ' tǒu', + '餚' => ' yáo', + '餤' => ' tán', + '餣' => ' yè', + '餢' => ' bù', + '餡' => ' xiàn', + '餠' => ' bǐng', + '餟' => ' zhuì', + '餞' => ' jiàn', + '餝' => ' shì', + '餜' => ' guǒ', + '餛' => ' hún', + '餙' => ' shì', + '餎' => ' le', + '餘' => ' yú', + '餗' => ' sù', + '餖' => ' dòu', + '餕' => ' jùn', + '餔' => ' bù', + '餓' => ' è', + '餒' => ' něi', + '餑' => ' bō', + '餐' => ' cān', + '餏' => ' xī', + '飴' => ' yí', + '飲' => ' yǐn', + '飀' => ' liú', + '飌' => ' fēng', + '飖' => ' yáo', + '飕' => ' sōu', + '飔' => ' sī', + '飓' => ' jù', + '飒' => ' sà', + '飑' => ' biāo', + '飐' => ' zhǎn', + '飏' => ' yáng', + '风' => ' fēng', + '飍' => ' xiū', + '飋' => ' sè', + '飘' => ' piāo', + '飊' => ' biao', + '飉' => ' liáo', + '飈' => ' biāo', + '飇' => ' biāo', + '飆' => ' biāo', + '飅' => ' liú', + '飄' => ' piāo', + '飃' => ' piāo', + '飂' => ' liù', + '飁' => ' xí', + '飗' => ' liú', + '飙' => ' biāo', + '飱' => ' sūn', + '飦' => ' zhān', + '飰' => ' fàn', + '飯' => ' fàn', + '飮' => ' yǐn', + '飭' => ' chì', + '飬' => ' juàn', + '飫' => ' yù', + '飪' => ' rèn', + '飩' => ' tún', + '飨' => ' xiǎng', + '飧' => ' sūn', + '飥' => ' tuō', + '飚' => ' biāo', + '飤' => ' sì', + '飣' => ' dìng', + '飢' => ' jī', + '飡' => ' cān', + '飠' => ' shí', + '食' => ' shí', + '飞' => ' fēi', + '飝' => ' fēi', + '飜' => ' fān', + '飛' => ' fēi', + '願' => ' yuàn', + '顖' => ' xìn', + '鞈' => ' gé', + '韈' => ' wà', + '韒' => ' qiào', + '韑' => ' wěi', + '韐' => ' gé', + '韏' => ' quàn', + '韎' => ' mèi', + '韍' => ' fú', + '韌' => ' rèn', + '韋' => ' wéi', + '韊' => ' lán', + '韉' => ' jiān', + '韇' => ' dú', + '韔' => ' chàng', + '韆' => ' qiān', + '韅' => ' xiǎn', + '韄' => ' hù', + '韃' => ' dá', + '韂' => ' chàn', + '韁' => ' jiāng', + '韀' => ' jiān', + '鞿' => ' jī', + '鞾' => ' xuē', + '鞽' => ' qiáo', + '韓' => ' hán', + '韕' => ' kuo', + '鞻' => ' lóu', + '韢' => ' suì', + '韬' => ' tāo', + '韫' => ' yùn', + '韪' => ' wěi', + '韩' => ' hán', + '韨' => ' fú', + '韧' => ' rèn', + '韦' => ' wéi', + '韥' => ' dú', + '韤' => ' wà', + '韣' => ' dú', + '韡' => ' wěi', + '韖' => ' rǒu', + '韠' => ' bì', + '韟' => ' gāo', + '韞' => ' yùn', + '韝' => ' gōu', + '韜' => ' tāo', + '韛' => ' bài', + '韚' => ' gé', + '韙' => ' wěi', + '韘' => ' shè', + '韗' => ' yùn', + '鞼' => ' guì', + '鞺' => ' tāng', + '韮' => ' jiǔ', + '鞔' => ' mán', + '鞞' => ' bǐng', + '鞝' => ' shàng', + '鞜' => ' tà', + '鞛' => ' běng', + '鞚' => ' kòng', + '鞙' => ' xuàn', + '鞘' => ' qiào', + '鞗' => ' tiáo', + '鞖' => ' suī', + '鞕' => ' yìng', + '鞓' => ' tīng', + '鞠' => ' jū', + '鞒' => ' qiáo', + '鞑' => ' dá', + '鞐' => ' qia', + '鞏' => ' gǒng', + '鞎' => ' hén', + '鞍' => ' ān', + '鞌' => ' ān', + '鞋' => ' xié', + '鞊' => ' jié', + '鞉' => ' táo', + '鞟' => ' kuò', + '鞡' => ' la', + '鞹' => ' kuò', + '鞮' => ' dī', + '鞸' => ' bì', + '鞷' => ' gé', + '鞶' => ' pán', + '鞵' => ' xié', + '鞴' => ' bèi', + '鞳' => ' tà', + '鞲' => ' gōu', + '鞱' => ' tāo', + '鞰' => ' wēn', + '鞯' => ' jiān', + '鞭' => ' biān', + '鞢' => ' xiè', + '鞬' => ' jiān', + '鞫' => ' jū', + '鞪' => ' mù', + '鞩' => ' xiào', + '鞨' => ' hé', + '鞧' => ' qiū', + '鞦' => ' qiū', + '鞥' => ' ēng', + '鞤' => ' bāng', + '鞣' => ' róu', + '韭' => ' jiǔ', + '韯' => ' xiān', + '顕' => ' xiǎn', + '頰' => ' jiá', + '頺' => ' tuí', + '頹' => ' tuí', + '頸' => ' jǐng', + '頷' => ' hàn', + '頶' => ' hú', + '頵' => ' yūn', + '頴' => ' yǐng', + '頳' => ' chēng', + '頲' => ' tǐng', + '頱' => ' luō', + '頯' => ' kuí', + '頼' => ' lài', + '頮' => ' huì', + '頭' => ' tóu', + '頬' => ' jiá', + '頫' => ' fǔ', + '頪' => ' lèi', + '頩' => ' pīng', + '頨' => ' yǔ', + '頧' => ' duǐ', + '頦' => ' hái', + '頥' => ' shěn', + '頻' => ' pín', + '頽' => ' tuí', + '頣' => ' shěn', + '顊' => ' yí', + '顔' => ' yán', + '顓' => ' zhuān', + '顒' => ' yóng', + '顑' => ' kǎn', + '顐' => ' wèn', + '顏' => ' yán', + '顎' => ' è', + '額' => ' é', + '題' => ' tí', + '顋' => ' sāi', + '顉' => ' qīn', + '頾' => ' zī', + '顈' => ' xuǎn', + '顇' => ' cuì', + '顆' => ' kē', + '顅' => ' qiān', + '顄' => ' hàn', + '顃' => ' tán', + '顂' => ' lài', + '顁' => ' dìng', + '顀' => ' chuí', + '頿' => ' zī', + '頤' => ' yí', + '頢' => ' kuò', + '韰' => ' xiè', + '韼' => ' péng', + '順' => ' shùn', + '項' => ' xiàng', + '頄' => ' kuí', + '頃' => ' qǐng', + '頂' => ' dǐng', + '頁' => ' yè', + '頀' => ' hù', + '響' => ' xiǎng', + '韾' => ' yīn', + '韽' => ' ān', + '韻' => ' yùn', + '須' => ' xū', + '韺' => ' yīng', + '韹' => ' huáng', + '韸' => ' péng', + '韷' => ' lè', + '韶' => ' sháo', + '韵' => ' yùn', + '韴' => ' zá', + '音' => ' yīn', + '韲' => ' jī', + '韱' => ' xiān', + '頇' => ' hān', + '頉' => ' yí', + '頡' => ' xié', + '頖' => ' pàn', + '頠' => ' wěi', + '頟' => ' é', + '頞' => ' è', + '頝' => ' qiāo', + '頜' => ' hé', + '頛' => ' lèi', + '頚' => ' jǐng', + '頙' => ' chè', + '領' => ' lǐng', + '頗' => ' pō', + '頕' => ' dān', + '頊' => ' xū', + '頔' => ' dí', + '頓' => ' dùn', + '頒' => ' bān', + '頑' => ' wán', + '預' => ' yù', + '頏' => ' háng', + '頎' => ' qí', + '頍' => ' kuǐ', + '頌' => ' sòng', + '頋' => ' ě', + '门' => ' mén', + '闦' => ' wén', + '餦' => ' zhāng', + '鎷' => ' mǎ', + '鏁' => ' suǒ', + '鏀' => ' lǔ', + '鎿' => ' ná', + '鎾' => ' wen', + '鎽' => ' feng', + '鎼' => ' xià', + '鎻' => ' suǒ', + '鎺' => ' zu', + '鎹' => ' song', + '鎸' => ' juān', + '鎶' => ' gē', + '鏃' => ' zú', + '鎵' => ' jiā', + '鎴' => ' xí', + '鎳' => ' niè', + '鎲' => ' tǎng', + '鎱' => ' yuán', + '鎰' => ' yì', + '鎯' => ' láng', + '鎮' => ' zhèn', + '鎭' => ' zhèn', + '鎬' => ' hào', + '鏂' => ' ōu', + '鏄' => ' tuán', + '鎪' => ' sōu', + '鏑' => ' dí', + '鏛' => ' cháng', + '鏚' => ' qī', + '鏙' => ' cuī', + '鏘' => ' qiāng', + '鏗' => ' kēng', + '鏖' => ' áo', + '鏕' => ' lù', + '鏔' => ' yí', + '鏓' => ' zǒng', + '鏒' => ' sǎn', + '鏐' => ' liú', + '鏅' => ' xiū', + '鏏' => ' wèi', + '鏎' => ' bì', + '鏍' => ' luó', + '鏌' => ' mò', + '鏋' => ' mǎn', + '鏊' => ' ào', + '鏉' => ' shòu', + '鏈' => ' liàn', + '鏇' => ' xuàn', + '鏆' => ' guàn', + '鎫' => ' wàn', + '鎩' => ' shā', + '鏝' => ' màn', + '鎃' => ' pài', + '鎍' => ' suǒ', + '鎌' => ' lián', + '鎋' => ' xiá', + '鎊' => ' bàng', + '鎉' => ' dá', + '鎈' => ' suǒ', + '鎇' => ' méi', + '鎆' => ' qian', + '鎅' => ' jiè', + '鎄' => ' āi', + '鎂' => ' měi', + '鎏' => ' liú', + '鎁' => ' yé', + '鎀' => ' xiū', + '鍿' => ' zī', + '鍾' => ' zhōng', + '鍽' => ' biān', + '鍼' => ' zhēn', + '鍻' => ' jié', + '鍺' => ' duǒ', + '鍹' => ' xuān', + '鍸' => ' hú', + '鎎' => ' kài', + '鎐' => ' yáo', + '鎨' => ' sǔn', + '鎝' => ' dā', + '鎧' => ' kǎi', + '鎦' => ' liú', + '鎥' => ' tiáo', + '鎤' => ' huàng', + '鎣' => ' yíng', + '鎢' => ' wū', + '鎡' => ' zī', + '鎠' => ' gāng', + '鎟' => ' sǎng', + '鎞' => ' bī', + '鎜' => ' pán', + '鎑' => ' yè', + '鎛' => ' bó', + '鎚' => ' chuí', + '鎙' => ' shuò', + '鎘' => ' lì', + '鎗' => ' qiāng', + '鎖' => ' suǒ', + '鎕' => ' táng', + '鎔' => ' róng', + '鎓' => ' wēng', + '鎒' => ' nòu', + '鏜' => ' tāng', + '鏞' => ' yōng', + '鍶' => ' sōng', + '鐟' => ' zān', + '鐩' => ' suì', + '鐨' => ' fèi', + '鐧' => ' jiān', + '鐦' => ' kāi', + '鐥' => ' shàn', + '鐤' => ' dǐng', + '鐣' => ' zhang', + '鐢' => ' fán', + '鐡' => ' tiě', + '鐠' => ' pǔ', + '鐞' => ' nòu', + '鐫' => ' juān', + '鐝' => ' jué', + '鐜' => ' duī', + '鐛' => ' yìng', + '鐚' => ' yā', + '鐙' => ' dèng', + '鐘' => ' zhōng', + '鐗' => ' jiǎn', + '鐖' => ' jī', + '鐕' => ' zān', + '鐔' => ' xín', + '鐪' => ' lǔ', + '鐬' => ' huì', + '鐒' => ' láo', + '鐹' => ' guǒ', + '鑃' => ' diào', + '鑂' => ' xùn', + '鑁' => ' zōng', + '鑀' => ' ài', + '鐿' => ' yì', + '鐾' => ' bèi', + '鐽' => ' dá', + '鐼' => ' fén', + '鐻' => ' jù', + '鐺' => ' dāng', + '鐸' => ' duó', + '鐭' => ' yù', + '鐷' => ' yè', + '鐶' => ' huán', + '鐵' => ' tiě', + '鐴' => ' bì', + '鐳' => ' léi', + '鐲' => ' zhuó', + '鐱' => ' jiàn', + '鐰' => ' qiāo', + '鐯' => ' zhuó', + '鐮' => ' lián', + '鐓' => ' duì', + '鐑' => ' qiè', + '鏟' => ' chǎn', + '鏫' => ' lí', + '鏵' => ' huá', + '鏴' => ' lù', + '鏳' => ' chēng', + '鏲' => ' qian', + '鏱' => ' zhang', + '鏰' => ' bèng', + '鏯' => ' shuang', + '鏮' => ' kāng', + '鏭' => ' xī', + '鏬' => ' xià', + '鏪' => ' cáo', + '鏷' => ' pú', + '鏩' => ' jiàn', + '鏨' => ' zàn', + '鏧' => ' lóng', + '鏦' => ' cōng', + '鏥' => ' xiù', + '鏤' => ' lòu', + '鏣' => ' shù', + '鏢' => ' biāo', + '鏡' => ' jìng', + '鏠' => ' fēng', + '鏶' => ' jí', + '鏸' => ' huì', + '鐐' => ' liáo', + '鐅' => ' piě', + '鐏' => ' zūn', + '鐎' => ' jiāo', + '鐍' => ' jué', + '鐌' => ' xiàng', + '鐋' => ' tāng', + '鐊' => ' yáng', + '鐉' => ' quān', + '鐈' => ' qiáo', + '鐇' => ' fán', + '鐆' => ' suì', + '鐄' => ' huáng', + '鏹' => ' qiǎng', + '鐃' => ' náo', + '鐂' => ' liù', + '鐁' => ' sī', + '鐀' => ' kuì', + '鏿' => ' chēng', + '鏾' => ' sǎn', + '鏽' => ' xiù', + '鏼' => ' sè', + '鏻' => ' lín', + '鏺' => ' pō', + '鍷' => ' kuí', + '鍵' => ' jiàn', + '鑅' => ' héng', + '鋧' => ' xiàn', + '鋱' => ' tè', + '鋰' => ' lǐ', + '鋯' => ' gào', + '鋮' => ' chéng', + '鋭' => ' ruì', + '鋬' => ' pàn', + '鋫' => ' lí', + '鋪' => ' pù', + '鋩' => ' máng', + '鋨' => ' tiě', + '鋦' => ' jū', + '鋳' => ' zhù', + '鋥' => ' zèng', + '鋤' => ' chú', + '鋣' => ' yé', + '鋢' => ' lüè', + '鋡' => ' hán', + '鋠' => ' shèn', + '鋟' => ' qǐn', + '鋞' => ' xíng', + '鋝' => ' lüè', + '鋜' => ' zhuó', + '鋲' => ' bing', + '鋴' => ' zhen', + '鋚' => ' tiáo', + '錁' => ' guǒ', + '錋' => ' péng', + '錊' => ' zuì', + '錉' => ' mín', + '錈' => ' juǎn', + '錇' => ' póu', + '錆' => ' qiāng', + '錅' => ' lí', + '錄' => ' lù', + '錃' => ' bēi', + '錂' => ' líng', + '錀' => ' lún', + '鋵' => ' tū', + '鋿' => ' cháng', + '鋾' => ' táo', + '鋽' => ' diào', + '鋼' => ' gāng', + '鋻' => ' jiàn', + '鋺' => ' yuǎn', + '鋹' => ' chǎng', + '鋸' => ' jù', + '鋷' => ' zuì', + '鋶' => ' liǔ', + '鋛' => ' gǒng', + '鋙' => ' yǔ', + '錍' => ' pī', + '銳' => ' ruì', + '銽' => ' guā', + '銼' => ' cuò', + '銻' => ' tí', + '銺' => ' zàng', + '銹' => ' xiù', + '銸' => ' zhé', + '銷' => ' xiāo', + '銶' => ' qiú', + '銵' => ' kēng', + '銴' => ' shì', + '銲' => ' hàn', + '銿' => ' zhōng', + '銱' => ' diào', + '銰' => ' āi', + '銯' => ' si', + '銮' => ' luán', + '銭' => ' qián', + '銬' => ' kào', + '銫' => ' sè', + '銪' => ' yǒu', + '銩' => ' diū', + '銨' => ' ǎn', + '銾' => ' hòng', + '鋀' => ' tōu', + '鋘' => ' huá', + '鋍' => ' bó', + '鋗' => ' xuān', + '鋖' => ' sī', + '鋕' => ' zhì', + '鋔' => ' wǎn', + '鋓' => ' chān', + '鋒' => ' fēng', + '鋑' => ' cuān', + '鋐' => ' hóng', + '鋏' => ' jiá', + '鋎' => ' hàn', + '鋌' => ' dìng', + '鋁' => ' lǚ', + '鋋' => ' chán', + '鋊' => ' yù', + '鋉' => ' sù', + '鋈' => ' wù', + '鋇' => ' bèi', + '鋆' => ' yún', + '鋅' => ' xīn', + '鋄' => ' wǎn', + '鋃' => ' láng', + '鋂' => ' méi', + '錌' => ' àn', + '錎' => ' xiàn', + '鍴' => ' duān', + '鍏' => ' wéi', + '鍙' => ' hù', + '鍘' => ' zhá', + '鍗' => ' tí', + '鍖' => ' chěn', + '鍕' => ' jūn', + '鍔' => ' è', + '鍓' => ' jí', + '鍒' => ' róu', + '鍑' => ' fù', + '鍐' => ' zōng', + '鍎' => ' tú', + '鍛' => ' duàn', + '鍍' => ' dù', + '鍌' => ' xiǎn', + '鍋' => ' guō', + '鍊' => ' liàn', + '鍉' => ' dī', + '鍈' => ' yīng', + '鍇' => ' kǎi', + '鍆' => ' mén', + '鍅' => ' fa', + '鍄' => ' liàng', + '鍚' => ' yáng', + '鍜' => ' xiá', + '鍂' => ' piān', + '鍩' => ' tiǎn', + '鍳' => ' jiàn', + '鍲' => ' mín', + '鍱' => ' yè', + '鍰' => ' huán', + '鍯' => ' cōng', + '鍮' => ' tōu', + '鍭' => ' hóu', + '鍬' => ' qiāo', + '鍫' => ' qiāo', + '鍪' => ' móu', + '鍨' => ' kuí', + '鍝' => ' yú', + '鍧' => ' hōng', + '鍦' => ' shī', + '鍥' => ' qiè', + '鍤' => ' chā', + '鍣' => ' zhāo', + '鍢' => ' fù', + '鍡' => ' wěi', + '鍠' => ' huáng', + '鍟' => ' xīng', + '鍞' => ' kēng', + '鍃' => ' huō', + '鍁' => ' xiān', + '錏' => ' yā', + '錛' => ' bēn', + '錥' => ' yù', + '錤' => ' jī', + '錣' => ' zhuì', + '錢' => ' qián', + '錡' => ' qí', + '錠' => ' dìng', + '錟' => ' tán', + '錞' => ' chún', + '錝' => ' zòng', + '錜' => ' niè', + '錚' => ' zhēng', + '錧' => ' guǎn', + '錙' => ' zī', + '錘' => ' chuí', + '錗' => ' nèi', + '錖' => ' dú', + '錕' => ' kūn', + '錔' => ' tà', + '錓' => ' kōng', + '錒' => ' kē', + '錑' => ' lèi', + '錐' => ' zhuī', + '錦' => ' jǐn', + '錨' => ' máo', + '鍀' => ' dé', + '錵' => ' huā', + '錿' => ' hu', + '錾' => ' zàn', + '錽' => ' wàn', + '錼' => ' nài', + '錻' => ' wu', + '錺' => ' fang', + '錹' => ' kěn', + '錸' => ' lái', + '錷' => ' gá', + '錶' => ' biǎo', + '錴' => ' lù', + '錩' => ' chāng', + '錳' => ' měng', + '録' => ' lù', + '錱' => ' zhēn', + '錰' => ' shù', + '錯' => ' cuò', + '錮' => ' gù', + '錭' => ' táo', + '錬' => ' liàn', + '錫' => ' xī', + '錪' => ' tiǎn', + '鑄' => ' zhù', + '鑆' => ' zhuì', + '闥' => ' tà', + '镘' => ' màn', + '镢' => ' jué', + '镡' => ' chán', + '镠' => ' liú', + '镟' => ' xuàn', + '镞' => ' zú', + '镝' => ' dī', + '镜' => ' jìng', + '镛' => ' yōng', + '镚' => ' bèng', + '镙' => ' luó', + '镗' => ' tāng', + '镤' => ' pú', + '镖' => ' biāo', + '镕' => ' róng', + '镔' => ' bīn', + '镓' => ' jiā', + '镒' => ' yì', + '镑' => ' bàng', + '镐' => ' gǎo', + '镏' => ' liú', + '镎' => ' ná', + '镍' => ' niè', + '镣' => ' liào', + '镥' => ' lǔ', + '镋' => ' tǎng', + '镲' => ' chǎ', + '镼' => ' qū', + '镻' => ' dié', + '镺' => ' ǎo', + '镹' => ' jiǔ', + '镸' => ' cháng', + '長' => ' zhǎng', + '镶' => ' xiāng', + '镵' => ' chán', + '镴' => ' là', + '镳' => ' biāo', + '镱' => ' yì', + '镦' => ' duì', + '镰' => ' lián', + '镯' => ' zhuó', + '镮' => ' huán', + '镭' => ' léi', + '镬' => ' huò', + '镫' => ' dèng', + '镪' => ' qiāng', + '镩' => ' cuān', + '镨' => ' pǔ', + '镧' => ' lán', + '镌' => ' juān', + '镊' => ' niè', + '镾' => ' mí', + '锤' => ' chuí', + '键' => ' jiàn', + '锭' => ' dìng', + '锬' => ' tán', + '锫' => ' péi', + '锪' => ' huō', + '锩' => ' juǎn', + '锨' => ' xiān', + '锧' => ' zhì', + '锦' => ' jǐn', + '锥' => ' zhuī', + '锣' => ' luó', + '锰' => ' měng', + '锢' => ' gù', + '锡' => ' xī', + '锠' => ' chāng', + '锟' => ' kūn', + '锞' => ' kè', + '锝' => ' dé', + '锜' => ' qí', + '锛' => ' bēn', + '锚' => ' máo', + '错' => ' cuò', + '锯' => ' jù', + '锱' => ' zī', + '镉' => ' gé', + '锾' => ' huán', + '镈' => ' bó', + '镇' => ' zhèn', + '镆' => ' mò', + '镅' => ' méi', + '镄' => ' fèi', + '镃' => ' zī', + '镂' => ' lòu', + '镁' => ' měi', + '镀' => ' dù', + '锿' => ' āi', + '锽' => ' huáng', + '锲' => ' qiè', + '锼' => ' sōu', + '锻' => ' duàn', + '锺' => ' zhōng', + '锹' => ' qiāo', + '锸' => ' chā', + '锷' => ' è', + '锶' => ' sī', + '锵' => ' qiāng', + '锴' => ' kǎi', + '锳' => ' yīng', + '镽' => ' liǎo', + '长' => ' zhǎng', + '锗' => ' zhě', + '闀' => ' xiàng', + '闊' => ' kuò', + '闉' => ' yīn', + '闈' => ' wéi', + '闇' => ' àn', + '闆' => ' bǎn', + '闅' => ' wén', + '闄' => ' yǎo', + '闃' => ' qù', + '闂' => ' hòng', + '闁' => ' bāo', + '閿' => ' wén', + '闌' => ' lán', + '閾' => ' yù', + '閽' => ' hūn', + '閼' => ' è', + '閻' => ' yán', + '閺' => ' wén', + '閹' => ' yān', + '閸' => ' kǔn', + '閷' => ' shài', + '閶' => ' chāng', + '閵' => ' lìn', + '闋' => ' què', + '闍' => ' dū', + '閳' => ' chǎn', + '闚' => ' kuī', + '闤' => ' huán', + '闣' => ' dàng', + '闢' => ' pì', + '闡' => ' chǎn', + '闠' => ' huì', + '闟' => ' xì', + '闞' => ' kàn', + '闝' => ' piáo', + '關' => ' guān', + '闛' => ' táng', + '闙' => ' qǐ', + '闎' => ' quan', + '闘' => ' dòu', + '闗' => ' guān', + '闖' => ' chuǎng', + '闕' => ' què', + '闔' => ' hé', + '闓' => ' kǎi', + '闒' => ' tà', + '闑' => ' niè', + '闐' => ' tián', + '闏' => ' fēng', + '閴' => ' qù', + '閲' => ' yuè', + '門' => ' mén', + '閌' => ' kàng', + '閖' => ' shui', + '閕' => ' xiā', + '閔' => ' mǐn', + '間' => ' jiān', + '閒' => ' xián', + '閑' => ' xián', + '閐' => ' sàn', + '閏' => ' rùn', + '閎' => ' hóng', + '閍' => ' bēng', + '開' => ' kāi', + '閘' => ' zhá', + '閊' => ' shan', + '閉' => ' bì', + '閈' => ' hàn', + '閇' => ' bì', + '閆' => ' yán', + '閅' => ' mén', + '閄' => ' huò', + '閃' => ' shǎn', + '閂' => ' shuān', + '閁' => ' mà', + '閗' => ' dòu', + '閙' => ' nào', + '閱' => ' yuè', + '閦' => ' chù', + '閰' => ' jú', + '閯' => ' shà', + '閮' => ' tíng', + '閭' => ' lǘ', + '閬' => ' làng', + '閫' => ' kǔn', + '閪' => ' sē', + '閩' => ' mǐn', + '閨' => ' guī', + '閧' => ' hòng', + '閥' => ' fá', + '閚' => ' zhān', + '閤' => ' gé', + '閣' => ' gé', + '関' => ' guān', + '閡' => ' ài', + '閠' => ' rùn', + '閟' => ' bì', + '閞' => ' biàn', + '閝' => ' líng', + '閜' => ' xiǎ', + '閛' => ' pēng', + '锘' => ' nuò', + '锖' => ' qiāng', + '鑇' => ' jī', + '钇' => ' yǐ', + '钑' => ' jí', + '钐' => ' shān', + '钏' => ' chuàn', + '钎' => ' qiān', + '钍' => ' tǔ', + '钌' => ' liǎo', + '钋' => ' pō', + '钊' => ' zhāo', + '钉' => ' dīng', + '针' => ' zhēn', + '钆' => ' gá', + '钓' => ' diào', + '钅' => ' jīn', + '钄' => ' lán', + '钃' => ' shǔ', + '钂' => ' tǎng', + '钁' => ' jué', + '钀' => ' niè', + '鑿' => ' záo', + '鑾' => ' luán', + '鑽' => ' zuān', + '鑼' => ' luó', + '钒' => ' fán', + '钔' => ' mén', + '鑺' => ' qú', + '钡' => ' bèi', + '钫' => ' fāng', + '钪' => ' kàng', + '钩' => ' gōu', + '钨' => ' wū', + '钧' => ' jūn', + '钦' => ' qīn', + '钥' => ' yào', + '钤' => ' qián', + '钣' => ' bǎn', + '钢' => ' gāng', + '钠' => ' nà', + '钕' => ' nǚ', + '钟' => ' zhōng', + '钞' => ' chāo', + '钝' => ' dùn', + '钜' => ' jù', + '钛' => ' tài', + '钚' => ' bù', + '钙' => ' gài', + '钘' => ' xíng', + '钗' => ' chāi', + '钖' => ' yáng', + '鑻' => ' pàn', + '鑹' => ' cuān', + '钭' => ' tǒu', + '鑓' => ' qian', + '鑝' => ' péng', + '鑜' => ' shǎng', + '鑛' => ' kuàng', + '鑚' => ' zuàn', + '鑙' => ' jī', + '鑘' => ' léi', + '鑗' => ' lí', + '鑖' => ' miè', + '鑕' => ' zhì', + '鑔' => ' chǎ', + '鑒' => ' jiàn', + '鑟' => ' dú', + '鑑' => ' jiàn', + '鑐' => ' xū', + '鑏' => ' níng', + '鑎' => ' kuì', + '鑍' => ' yīng', + '鑌' => ' bīn', + '鑋' => ' qīng', + '鑊' => ' huò', + '鑉' => ' hé', + '鑈' => ' niè', + '鑞' => ' là', + '鑠' => ' shuò', + '鑸' => ' lěi', + '鑭' => ' làn', + '鑷' => ' niè', + '鑶' => ' cáng', + '鑵' => ' guàn', + '鑴' => ' xī', + '鑳' => ' jiàn', + '鑲' => ' xiāng', + '鑱' => ' chán', + '鑰' => ' yào', + '鑯' => ' jiān', + '鑮' => ' bó', + '鑬' => ' jiàn', + '鑡' => ' chuò', + '鑫' => ' xīn', + '鑪' => ' lú', + '鑩' => ' è', + '鑨' => ' lóng', + '鑧' => ' kuan', + '鑦' => ' xian', + '鑥' => ' lǔ', + '鑤' => ' bào', + '鑣' => ' biāo', + '鑢' => ' lǜ', + '钬' => ' huǒ', + '钮' => ' niǔ', + '锕' => ' ā', + '铰' => ' jiǎo', + '铺' => ' pù', + '铹' => ' láo', + '铸' => ' zhù', + '铷' => ' rú', + '银' => ' yín', + '铵' => ' ǎn', + '铴' => ' tāng', + '铳' => ' chòng', + '铲' => ' chǎn', + '铱' => ' yī', + '铯' => ' sè', + '铼' => ' lái', + '铮' => ' zhēng', + '铭' => ' míng', + '铬' => ' gè', + '铫' => ' diào', + '铪' => ' hā', + '铩' => ' shā', + '铨' => ' quán', + '铧' => ' huá', + '铦' => ' xiān', + '铥' => ' diū', + '铻' => ' wú', + '铽' => ' tè', + '铣' => ' xǐ', + '锊' => ' lüè', + '锔' => ' jū', + '锓' => ' qǐn', + '锒' => ' láng', + '锑' => ' tī', + '锐' => ' ruì', + '锏' => ' jiǎn', + '锎' => ' kāi', + '锍' => ' liǔ', + '锌' => ' xīn', + '锋' => ' fēng', + '锉' => ' cuò', + '链' => ' liàn', + '锈' => ' xiù', + '锇' => ' é', + '锆' => ' gào', + '锅' => ' guō', + '锄' => ' chú', + '锃' => ' zèng', + '锂' => ' lǐ', + '锁' => ' suǒ', + '销' => ' xiāo', + '铿' => ' kēng', + '铤' => ' dìng', + '铢' => ' zhū', + '钯' => ' bǎ', + '钻' => ' zuān', + '铅' => ' qiān', + '铄' => ' shuò', + '铃' => ' líng', + '铂' => ' bó', + '铁' => ' tiě', + '铀' => ' yóu', + '钿' => ' diàn', + '钾' => ' jiǎ', + '钽' => ' tǎn', + '钼' => ' mù', + '钺' => ' yuè', + '铇' => ' bào', + '钹' => ' bó', + '钸' => ' bù', + '钷' => ' pǒ', + '钶' => ' ē', + '钵' => ' bō', + '钴' => ' gǔ', + '钳' => ' qián', + '钲' => ' zhēng', + '钱' => ' qián', + '钰' => ' yù', + '铆' => ' mǎo', + '铈' => ' shì', + '铡' => ' zhá', + '铖' => ' chéng', + '铠' => ' kǎi', + '铟' => ' yīn', + '铞' => ' diào', + '铝' => ' lǚ', + '铜' => ' tóng', + '铛' => ' dāng', + '铚' => ' zhì', + '铙' => ' náo', + '铘' => ' yé', + '铗' => ' jiá', + '铕' => ' yǒu', + '铉' => ' xuàn', + '铔' => ' yà', + '铓' => ' máng', + '铒' => ' ěr', + '铑' => ' lǎo', + '铐' => ' kào', + '铏' => ' xíng', + '铎' => ' duó', + '铍' => ' pī', + '铌' => ' ní', + '铋' => ' bì', + '铊' => ' tā', + '餥' => ' fēi', + '餧' => ' wèi', + '銦' => ' yīn', + '鵹' => ' lí', + '鶃' => ' yì', + '鶂' => ' yì', + '鶁' => ' jīng', + '鶀' => ' qí', + '鵿' => ' shēng', + '鵾' => ' kūn', + '鵽' => ' duò', + '鵼' => ' kōng', + '鵻' => ' zhuī', + '鵺' => ' yè', + '鵸' => ' qí', + '鶅' => ' zī', + '鵷' => ' yuān', + '鵶' => ' yā', + '鵵' => ' tù', + '鵴' => ' jú', + '鵳' => ' jiān', + '鵲' => ' què', + '鵱' => ' lù', + '鵰' => ' diāo', + '鵯' => ' bēi', + '鵮' => ' qiān', + '鶄' => ' jīng', + '鶆' => ' lái', + '鵬' => ' péng', + '鶓' => ' miáo', + '鶝' => ' fú', + '鶜' => ' máo', + '鶛' => ' jiē', + '鶚' => ' è', + '鶙' => ' tí', + '鶘' => ' hú', + '鶗' => ' tí', + '鶖' => ' qiū', + '鶕' => ' ān', + '鶔' => ' róu', + '鶒' => ' chì', + '鶇' => ' dōng', + '鶑' => ' yīng', + '鶐' => ' shù', + '鶏' => ' jī', + '鶎' => ' zun', + '鶍' => ' yi', + '鶌' => ' jué', + '鶋' => ' jū', + '鶊' => ' gēng', + '鶉' => ' chún', + '鶈' => ' qī', + '鵭' => ' qín', + '鵫' => ' zhuó', + '鶟' => ' tú', + '鵅' => ' luò', + '鵏' => ' bǔ', + '鵎' => ' tuǒ', + '鵍' => ' huān', + '鵌' => ' tú', + '鵋' => ' jì', + '鵊' => ' jiá', + '鵉' => ' luán', + '鵈' => ' e', + '鵇' => ' nian', + '鵆' => ' heng', + '鵄' => ' zhī', + '鵑' => ' juān', + '鵃' => ' zhōu', + '鵂' => ' xiū', + '鵁' => ' jiāo', + '鵀' => ' rén', + '鴿' => ' gē', + '鴾' => ' móu', + '鴽' => ' rú', + '鴼' => ' luò', + '鴻' => ' hóng', + '鴺' => ' tí', + '鵐' => ' wú', + '鵒' => ' yù', + '鵪' => ' ān', + '鵟' => ' kuáng', + '鵩' => ' fú', + '鵨' => ' shū', + '鵧' => ' pí', + '鵦' => ' lù', + '鵥' => ' pan', + '鵤' => ' jiao', + '鵣' => ' lài', + '鵢' => ' shēn', + '鵡' => ' wǔ', + '鵠' => ' hú', + '鵞' => ' é', + '鵓' => ' bó', + '鵝' => ' é', + '鵜' => ' tí', + '鵛' => ' jīng', + '鵚' => ' tū', + '鵙' => ' jú', + '鵘' => ' jùn', + '鵗' => ' xī', + '鵖' => ' bī', + '鵕' => ' xùn', + '鵔' => ' jùn', + '鶞' => ' chūn', + '鶠' => ' yǎn', + '鴸' => ' zhū', + '鷡' => ' wú', + '鷫' => ' sù', + '鷪' => ' yīng', + '鷩' => ' bì', + '鷨' => ' huá', + '鷧' => ' yì', + '鷦' => ' jiāo', + '鷥' => ' sī', + '鷤' => ' tí', + '鷣' => ' yín', + '鷢' => ' jué', + '鷠' => ' yú', + '鷭' => ' fán', + '鷟' => ' zhuó', + '鷞' => ' shuāng', + '鷝' => ' bì', + '鷜' => ' lǘ', + '鷛' => ' yōng', + '鷚' => ' liù', + '鷙' => ' zhì', + '鷘' => ' chì', + '鷗' => ' ōu', + '鷖' => ' yī', + '鷬' => ' huáng', + '鷮' => ' jiāo', + '鷔' => ' áo', + '鷻' => ' tuán', + '鸅' => ' zé', + '鸄' => ' jī', + '鸃' => ' yí', + '鸂' => ' xī', + '鸁' => ' luó', + '鸀' => ' chǔ', + '鷿' => ' pì', + '鷾' => ' yì', + '鷽' => ' xué', + '鷼' => ' xián', + '鷺' => ' lù', + '鷯' => ' liáo', + '鷹' => ' yīng', + '鷸' => ' yù', + '鷷' => ' zūn', + '鷶' => ' mǎi', + '鷵' => ' tú', + '鷴' => ' xián', + '鷳' => ' xián', + '鷲' => ' jiù', + '鷱' => ' gāo', + '鷰' => ' yàn', + '鷕' => ' yǎo', + '鷓' => ' zhè', + '鶡' => ' hé', + '鶭' => ' fǎng', + '鶷' => ' xiá', + '鶶' => ' táng', + '鶵' => ' chú', + '鶴' => ' hè', + '鶳' => ' shī', + '鶲' => ' wēng', + '鶱' => ' xiān', + '鶰' => ' yuán', + '鶯' => ' yīng', + '鶮' => ' hè', + '鶬' => ' cāng', + '鶹' => ' liú', + '鶫' => ' dong', + '鶪' => ' jú', + '鶩' => ' wù', + '鶨' => ' chuàn', + '鶧' => ' yīng', + '鶦' => ' hú', + '鶥' => ' méi', + '鶤' => ' kūn', + '鶣' => ' piān', + '鶢' => ' yuán', + '鶸' => ' ruò', + '鶺' => ' jí', + '鷒' => ' tuán', + '鷇' => ' kòu', + '鷑' => ' jí', + '鷐' => ' chén', + '鷏' => ' tián', + '鷎' => ' gāo', + '鷍' => ' jiāo', + '鷌' => ' mǎ', + '鷋' => ' tú', + '鷊' => ' yì', + '鷉' => ' tī', + '鷈' => ' tī', + '鷆' => ' tián', + '鶻' => ' gú', + '鷅' => ' lì', + '鷄' => ' jī', + '鷃' => ' yàn', + '鷂' => ' yào', + '鷁' => ' yì', + '鷀' => ' cí', + '鶿' => ' cí', + '鶾' => ' hàn', + '鶽' => ' sǔn', + '鶼' => ' jiān', + '鴹' => ' yáng', + '鴷' => ' liè', + '鸇' => ' zhān', + '鲩' => ' huàn', + '鲳' => ' chāng', + '鲲' => ' kūn', + '鲱' => ' fēi', + '鲰' => ' zōu', + '鲯' => ' qí', + '鲮' => ' líng', + '鲭' => ' qīng', + '鲬' => ' yǒng', + '鲫' => ' jì', + '鲪' => ' jūn', + '鲨' => ' shā', + '鲵' => ' ní', + '鲧' => ' gǔn', + '鲦' => ' tiáo', + '鲥' => ' shí', + '鲤' => ' lǐ', + '鲣' => ' jiān', + '鲢' => ' lián', + '鲡' => ' lí', + '鲠' => ' gěng', + '鲟' => ' xún', + '鲞' => ' xiǎng', + '鲴' => ' gù', + '鲶' => ' nián', + '鲜' => ' xiān', + '鳃' => ' sāi', + '鳍' => ' qí', + '鳌' => ' áo', + '鳋' => ' sāo', + '鳊' => ' biān', + '鳉' => ' jiāng', + '鳈' => ' quán', + '鳇' => ' huáng', + '鳆' => ' fù', + '鳅' => ' qiū', + '鳄' => ' è', + '鳂' => ' wēi', + '鲷' => ' diāo', + '鳁' => ' wēn', + '鳀' => ' tí', + '鲿' => ' cháng', + '鲾' => ' bī', + '鲽' => ' dié', + '鲼' => ' fèn', + '鲻' => ' zī', + '鲺' => ' shī', + '鲹' => ' shēn', + '鲸' => ' jīng', + '鲝' => ' zhǎ', + '鲛' => ' jiāo', + '鳏' => ' guān', + '鱵' => ' zhēn', + '鱿' => ' yóu', + '鱾' => ' jǐ', + '鱽' => ' dāo', + '鱼' => ' yú', + '鱻' => ' xiān', + '鱺' => ' lí', + '鱹' => ' guàn', + '鱸' => ' lú', + '鱷' => ' è', + '鱶' => ' xiǎng', + '鱴' => ' miè', + '鲁' => ' lǔ', +); \ No newline at end of file diff --git a/vendor/overtrue/pinyin/data/words_3 b/vendor/overtrue/pinyin/data/words_3 new file mode 100644 index 0000000..a9222ab --- /dev/null +++ b/vendor/overtrue/pinyin/data/words_3 @@ -0,0 +1,8003 @@ + ' lì', + '鱲' => ' liè', + '鱱' => ' lì', + '鱰' => ' shu', + '鱯' => ' hù', + '鱮' => ' xù', + '鱭' => ' jì', + '鱬' => ' rú', + '鱫' => ' ai', + '鱪' => ' shu', + '鲀' => ' tún', + '鲂' => ' fáng', + '鲚' => ' jì', + '鲏' => ' pí', + '鲙' => ' kuài', + '鲘' => ' hòu', + '鲗' => ' zéi', + '鲖' => ' tóng', + '鲕' => ' ér', + '鲔' => ' wěi', + '鲓' => ' kǎo', + '鲒' => ' jié', + '鲑' => ' guī', + '鲐' => ' tái', + '鲎' => ' hòu', + '鲃' => ' bā', + '鲍' => ' bào', + '鲌' => ' bà', + '鲋' => ' fù', + '鲊' => ' zhǎ', + '鲉' => ' yóu', + '鲈' => ' lú', + '鲇' => ' nián', + '鲆' => ' píng', + '鲅' => ' bà', + '鲄' => ' hé', + '鳎' => ' tǎ', + '鳐' => ' yáo', + '鴶' => ' jiá', + '鴑' => ' rú', + '鴛' => ' yuān', + '鴚' => ' gē', + '鴙' => ' zhì', + '鴘' => ' biǎn', + '鴗' => ' lì', + '鴖' => ' mín', + '鴕' => ' tuó', + '鴔' => ' fú', + '鴓' => ' miè', + '鴒' => ' líng', + '鴐' => ' gē', + '鴝' => ' qú', + '鴏' => ' dài', + '鴎' => ' ōu', + '鴍' => ' wén', + '鴌' => ' fèng', + '鴋' => ' fāng', + '鴊' => ' zhèng', + '鴉' => ' yā', + '鴈' => ' yàn', + '鴇' => ' bǎo', + '鴆' => ' zhèn', + '鴜' => ' cí', + '鴞' => ' xiāo', + '鴄' => ' pǐ', + '鴫' => ' tian', + '鴵' => ' xiāo', + '鴴' => ' héng', + '鴳' => ' yàn', + '鴲' => ' zhī', + '鴱' => ' ài', + '鴰' => ' guā', + '鴯' => ' ér', + '鴮' => ' wū', + '鴭' => ' duī', + '鴬' => ' yīng', + '鴪' => ' yù', + '鴟' => ' chī', + '鴩' => ' tiě', + '鴨' => ' yā', + '鴧' => ' ròng', + '鴦' => ' yāng', + '鴥' => ' yù', + '鴤' => ' dōng', + '鴣' => ' gū', + '鴢' => ' yǎo', + '鴡' => ' jū', + '鴠' => ' dàn', + '鴅' => ' huān', + '鴃' => ' jué', + '鳑' => ' páng', + '鳝' => ' shàn', + '鳧' => ' fú', + '鳦' => ' yǐ', + '鳥' => ' niǎo', + '鳤' => ' guǎn', + '鳣' => ' zhān', + '鳢' => ' lǐ', + '鳡' => ' gǎn', + '鳠' => ' hù', + '鳟' => ' zūn', + '鳞' => ' lín', + '鳜' => ' guì', + '鳩' => ' jiū', + '鳛' => ' xí', + '鳚' => ' wèi', + '鳙' => ' yōng', + '鳘' => ' mǐn', + '鳗' => ' mán', + '鳖' => ' biē', + '鳕' => ' xuě', + '鳔' => ' biào', + '鳓' => ' lè', + '鳒' => ' jiān', + '鳨' => ' lì', + '鳪' => ' bú', + '鴂' => ' jué', + '鳷' => ' zhī', + '鴁' => ' yāo', + '鴀' => ' fǒu', + '鳿' => ' yù', + '鳾' => ' shī', + '鳽' => ' jiān', + '鳼' => ' wén', + '鳻' => ' bān', + '鳺' => ' fū', + '鳹' => ' qín', + '鳸' => ' hù', + '鳶' => ' yuān', + '鳫' => ' yàn', + '鳵' => ' bǎo', + '鳴' => ' míng', + '鳳' => ' fèng', + '鳲' => ' shī', + '鳱' => ' gān', + '鳰' => ' ru', + '鳯' => ' fèng', + '鳮' => ' jī', + '鳭' => ' diāo', + '鳬' => ' fǔ', + '鸆' => ' yú', + '鸈' => ' yè', + '鱨' => ' cháng', + '鼚' => ' chāng', + '鼤' => ' wén', + '鼣' => ' fèi', + '鼢' => ' fén', + '鼡' => ' shǔ', + '鼠' => ' shǔ', + '鼟' => ' tēng', + '鼞' => ' tāng', + '鼝' => ' yuān', + '鼜' => ' qì', + '鼛' => ' gāo', + '鼙' => ' pí', + '鼦' => ' diāo', + '鼘' => ' yuān', + '鼗' => ' táo', + '鼖' => ' fén', + '鼕' => ' dōng', + '鼔' => ' gǔ', + '鼓' => ' gǔ', + '鼒' => ' zī', + '鼑' => ' dǐng', + '鼐' => ' nài', + '鼏' => ' mì', + '鼥' => ' bá', + '鼧' => ' tuó', + '鼍' => ' tuó', + '鼴' => ' yǎn', + '鼾' => ' hān', + '鼽' => ' qiú', + '鼼' => ' yǎo', + '鼻' => ' bí', + '鼺' => ' léi', + '鼹' => ' yǎn', + '鼸' => ' xiàn', + '鼷' => ' xī', + '鼶' => ' sī', + '鼵' => ' tū', + '鼳' => ' jú', + '鼨' => ' zhōng', + '鼲' => ' hún', + '鼱' => ' jīng', + '鼰' => ' niàn', + '鼯' => ' wú', + '鼮' => ' tíng', + '鼭' => ' shí', + '鼬' => ' yòu', + '鼫' => ' shí', + '鼪' => ' shēng', + '鼩' => ' qú', + '鼎' => ' dǐng', + '鼌' => ' cháo', + '齀' => ' wù', + '黦' => ' yuè', + '黰' => ' zhěn', + '黯' => ' àn', + '黮' => ' dǎn', + '黭' => ' yǎn', + '黬' => ' yán', + '黫' => ' yān', + '黪' => ' cǎn', + '黩' => ' dú', + '黨' => ' dǎng', + '黧' => ' lí', + '黥' => ' qíng', + '黲' => ' cǎn', + '黤' => ' yǎn', + '黣' => ' měi', + '黢' => ' qū', + '黡' => ' yǎn', + '黠' => ' xiá', + '黟' => ' yī', + '點' => ' diǎn', + '黝' => ' yǒu', + '黜' => ' chù', + '黛' => ' dài', + '黱' => ' dài', + '黳' => ' yī', + '鼋' => ' yuán', + '鼀' => ' cù', + '鼊' => ' bì', + '鼉' => ' tuó', + '鼈' => ' biē', + '鼇' => ' áo', + '鼆' => ' méng', + '鼅' => ' zhī', + '鼄' => ' zhū', + '鼃' => ' wā', + '鼂' => ' cháo', + '鼁' => ' qù', + '黿' => ' yuán', + '黴' => ' méi', + '黾' => ' miǎn', + '黽' => ' miǎn', + '黼' => ' fǔ', + '黻' => ' fú', + '黺' => ' fěn', + '黹' => ' zhǐ', + '黸' => ' lú', + '黷' => ' dú', + '黶' => ' yǎn', + '黵' => ' zhǎn', + '鼿' => ' wù', + '齁' => ' hōu', + '黙' => ' mò', + '龂' => ' yín', + '龌' => ' wò', + '龋' => ' qǔ', + '龊' => ' chuò', + '龉' => ' yǔ', + '龈' => ' kěn', + '龇' => ' zī', + '龆' => ' tiáo', + '龅' => ' bāo', + '龄' => ' líng', + '龃' => ' jǔ', + '龁' => ' hé', + '龎' => ' páng', + '龀' => ' chèn', + '齿' => ' chǐ', + '齾' => ' yà', + '齽' => ' jìn', + '齼' => ' chǔ', + '齻' => ' diān', + '齺' => ' zōu', + '齹' => ' cī', + '齸' => ' yì', + '齷' => ' wò', + '龍' => ' lóng', + '龏' => ' gōng', + '齵' => ' óu', + '龜' => ' guī', + '鿃' => ' shǎn', + '龥' => ' yù', + '龤' => ' xié', + '龣' => ' jué', + '龢' => ' hé', + '龡' => ' chuì', + '龠' => ' yuè', + '龟' => ' guī', + '龞' => ' biē', + '龝' => ' qiū', + '龛' => ' kān', + '龐' => ' páng', + '龚' => ' gōng', + '龙' => ' lóng', + '龘' => ' dá', + '龗' => ' líng', + '龖' => ' dá', + '龕' => ' kān', + '龔' => ' gōng', + '龓' => ' lǒng', + '龒' => ' lóng', + '龑' => ' yǎn', + '齶' => ' è', + '齴' => ' yǎn', + '齂' => ' xiè', + '齎' => ' jī', + '齘' => ' xiè', + '齗' => ' yín', + '齖' => ' yá', + '齕' => ' hé', + '齔' => ' chèn', + '齓' => ' chèn', + '齒' => ' chǐ', + '齑' => ' jī', + '齐' => ' qí', + '齏' => ' jī', + '齍' => ' zī', + '齚' => ' zé', + '齌' => ' jì', + '齋' => ' zhāi', + '齊' => ' qí', + '齉' => ' nàng', + '齈' => ' nòng', + '齇' => ' zhā', + '齆' => ' wèng', + '齅' => ' xiù', + '齄' => ' zhā', + '齃' => ' è', + '齙' => ' bāo', + '齛' => ' xiè', + '齳' => ' yǔn', + '齨' => ' jiù', + '齲' => ' qǔ', + '齱' => ' zōu', + '齰' => ' zé', + '齯' => ' ní', + '齮' => ' yǐ', + '齭' => ' chǔ', + '齬' => ' yǔ', + '齫' => ' kǔn', + '齪' => ' chuò', + '齩' => ' yǎo', + '齧' => ' niè', + '齜' => ' chái', + '齦' => ' kěn', + '齥' => ' xiè', + '齤' => ' quán', + '齣' => ' chū', + '齢' => ' líng', + '齡' => ' líng', + '齠' => ' tiáo', + '齟' => ' jǔ', + '齞' => ' yǎn', + '齝' => ' chī', + '黚' => ' qián', + '默' => ' mò', + '鸉' => ' yáng', + '鹉' => ' wǔ', + '鹓' => ' yuān', + '鹒' => ' gēng', + '鹑' => ' chún', + '鹐' => ' qiān', + '鹏' => ' péng', + '鹎' => ' bēi', + '鹍' => ' kūn', + '鹌' => ' ān', + '鹋' => ' miáo', + '鹊' => ' què', + '鹈' => ' tí', + '鹕' => ' hú', + '鹇' => ' xián', + '鹆' => ' yù', + '鹅' => ' é', + '鹄' => ' gǔ', + '鹃' => ' juān', + '鹂' => ' lí', + '鹁' => ' bó', + '鹀' => ' wú', + '鸿' => ' hóng', + '鸾' => ' luán', + '鹔' => ' sù', + '鹖' => ' hé', + '鸼' => ' zhōu', + '鹣' => ' jiān', + '鹭' => ' lù', + '鹬' => ' yù', + '鹫' => ' jiù', + '鹪' => ' jiāo', + '鹩' => ' liáo', + '鹨' => ' liù', + '鹧' => ' zhè', + '鹦' => ' yīng', + '鹥' => ' yī', + '鹤' => ' hè', + '鹢' => ' yì', + '鹗' => ' è', + '鹡' => ' jí', + '鹠' => ' liú', + '鹟' => ' wēng', + '鹞' => ' yào', + '鹝' => ' yì', + '鹜' => ' wù', + '鹛' => ' méi', + '鹚' => ' cí', + '鹙' => ' qiū', + '鹘' => ' gǔ', + '鸽' => ' gē', + '鸻' => ' héng', + '鹯' => ' zhān', + '鸕' => ' lú', + '鸟' => ' niǎo', + '鸞' => ' luán', + '鸝' => ' lí', + '鸜' => ' qú', + '鸛' => ' guàn', + '鸚' => ' yīng', + '鸙' => ' yuè', + '鸘' => ' shuāng', + '鸗' => ' lóng', + '鸖' => ' hè', + '鸔' => ' bào', + '鸡' => ' jī', + '鸓' => ' lěi', + '鸒' => ' yù', + '鸑' => ' yuè', + '鸐' => ' dí', + '鸏' => ' méng', + '鸎' => ' yīng', + '鸍' => ' mí', + '鸌' => ' hù', + '鸋' => ' níng', + '鸊' => ' pì', + '鸠' => ' jiū', + '鸢' => ' yuān', + '鸺' => ' xiū', + '鸯' => ' yāng', + '鸹' => ' guā', + '鸸' => ' ér', + '鸷' => ' zhì', + '鸶' => ' sī', + '鸵' => ' tuó', + '鸴' => ' xué', + '鸳' => ' yuān', + '鸲' => ' qú', + '鸱' => ' chī', + '鸰' => ' líng', + '鸮' => ' xiāo', + '鸣' => ' míng', + '鸭' => ' yā', + '鸬' => ' lú', + '鸫' => ' dōng', + '鸪' => ' gū', + '鸩' => ' zhèn', + '鸨' => ' bǎo', + '鸧' => ' cāng', + '鸦' => ' yā', + '鸥' => ' ōu', + '鸤' => ' shī', + '鹮' => ' huán', + '鹰' => ' yīng', + '黗' => ' tūn', + '麲' => ' xiàn', + '麼' => ' me', + '麻' => ' má', + '麺' => ' miàn', + '麹' => ' qū', + '麸' => ' fū', + '麷' => ' fēng', + '麶' => ' chi', + '麵' => ' miàn', + '麴' => ' qū', + '麳' => ' lái', + '麱' => ' fū', + '麾' => ' huī', + '麰' => ' móu', + '麯' => ' qū', + '麮' => ' qù', + '麭' => ' pào', + '麬' => ' fū', + '麫' => ' miǎn', + '麪' => ' miàn', + '麩' => ' fū', + '麨' => ' chǎo', + '麧' => ' hé', + '麽' => ' mó', + '麿' => ' mo', + '麥' => ' mài', + '黌' => ' hóng', + '黖' => ' xì', + '黕' => ' dǎn', + '黔' => ' qián', + '黓' => ' yì', + '黒' => ' hēi', + '黑' => ' hēi', + '黐' => ' chī', + '黏' => ' nián', + '黎' => ' lí', + '黍' => ' shǔ', + '黋' => ' kuàng', + '黀' => ' zōu', + '黊' => ' huà', + '黉' => ' hóng', + '黈' => ' tǒu', + '黇' => ' tiān', + '黆' => ' guāng', + '黅' => ' jīn', + '黄' => ' huáng', + '黃' => ' huáng', + '黂' => ' fén', + '黁' => ' nún', + '麦' => ' mài', + '麤' => ' cū', + '鹱' => ' hù', + '鹽' => ' yán', + '麇' => ' jūn', + '麆' => ' zhù', + '麅' => ' páo', + '麄' => ' cū', + '麃' => ' páo', + '麂' => ' jǐ', + '麁' => ' cū', + '麀' => ' yōu', + '鹿' => ' lù', + '鹾' => ' cuó', + '鹼' => ' jiǎn', + '麉' => ' jiān', + '鹻' => ' jiǎn', + '鹺' => ' cuó', + '鹹' => ' xián', + '鹸' => ' jiǎn', + '鹷' => ' líng', + '鹶' => ' jīn', + '鹵' => ' lǔ', + '鹴' => ' shuāng', + '鹳' => ' guàn', + '鹲' => ' méng', + '麈' => ' zhǔ', + '麊' => ' mí', + '麣' => ' yán', + '麘' => ' xiāng', + '麢' => ' líng', + '麡' => ' qí', + '麠' => ' jīng', + '麟' => ' lín', + '麞' => ' zhāng', + '麝' => ' shè', + '麜' => ' lì', + '麛' => ' mí', + '麚' => ' jiā', + '麙' => ' xián', + '麗' => ' lì', + '麋' => ' mí', + '麖' => ' jīng', + '麕' => ' jūn', + '麔' => ' jiù', + '麓' => ' lù', + '麒' => ' qí', + '麑' => ' ní', + '麐' => ' lín', + '麏' => ' jūn', + '麎' => ' chén', + '麍' => ' liú', + '麌' => ' yǔ', + '鱩' => ' lei', + '鱧' => ' lǐ', + '館' => ' guǎn', + '騸' => ' shàn', + '驂' => ' cān', + '驁' => ' ào', + '驀' => ' mò', + '騿' => ' zhāng', + '騾' => ' luó', + '騽' => ' xí', + '騼' => ' lù', + '騻' => ' shuāng', + '騺' => ' zhì', + '騹' => ' lí', + '騷' => ' sāo', + '驄' => ' cōng', + '騶' => ' zōu', + '騵' => ' yuán', + '騴' => ' yàn', + '騳' => ' dú', + '騲' => ' cǎo', + '騱' => ' xí', + '騰' => ' téng', + '騯' => ' péng', + '騮' => ' liú', + '騭' => ' zhì', + '驃' => ' biāo', + '驅' => ' qū', + '騫' => ' qiān', + '驒' => ' tuó', + '驜' => ' yè', + '驛' => ' yì', + '驚' => ' jīng', + '驙' => ' zhān', + '驘' => ' luó', + '驗' => ' yàn', + '驖' => ' tiě', + '驕' => ' jiāo', + '驔' => ' diàn', + '驓' => ' céng', + '驑' => ' liú', + '驆' => ' bì', + '驐' => ' dūn', + '驏' => ' zhàn', + '驎' => ' lín', + '驍' => ' xiāo', + '驌' => ' sù', + '驋' => ' bō', + '驊' => ' huá', + '驉' => ' xū', + '驈' => ' yù', + '驇' => ' zhì', + '騬' => ' chéng', + '騪' => ' sōu', + '驞' => ' pīn', + '騄' => ' lù', + '騎' => ' qí', + '騍' => ' kè', + '騌' => ' zōng', + '騋' => ' lái', + '騊' => ' táo', + '騉' => ' kūn', + '騈' => ' pián', + '騇' => ' shè', + '騆' => ' zhōu', + '騅' => ' zhuī', + '騃' => ' ái', + '騐' => ' yàn', + '騂' => ' xīng', + '騁' => ' chěng', + '騀' => ' ě', + '駿' => ' jùn', + '駾' => ' tuì', + '駽' => ' xuān', + '駼' => ' tú', + '駻' => ' hàn', + '駺' => ' láng', + '駹' => ' máng', + '騏' => ' qí', + '騑' => ' fēi', + '騩' => ' guī', + '騞' => ' huō', + '騨' => ' tuó', + '騧' => ' guā', + '騦' => ' sī', + '騥' => ' róu', + '騤' => ' kuí', + '騣' => ' zōng', + '騢' => ' xiá', + '騡' => ' quán', + '騠' => ' tí', + '騟' => ' yú', + '騝' => ' qián', + '騒' => ' sāo', + '騜' => ' huáng', + '騛' => ' fēi', + '騚' => ' qián', + '騙' => ' piàn', + '騘' => ' cōng', + '騗' => ' piàn', + '騖' => ' wù', + '騕' => ' yǎo', + '騔' => ' gé', + '験' => ' yǎn', + '驝' => ' tuō', + '驟' => ' zhòu', + '駷' => ' sǒng', + '骠' => ' biāo', + '骪' => ' wěi', + '骩' => ' wěi', + '骨' => ' gǔ', + '骧' => ' xiāng', + '骦' => ' shuāng', + '骥' => ' jì', + '骤' => ' zhòu', + '骣' => ' chǎn', + '骢' => ' cōng', + '骡' => ' luó', + '骟' => ' shàn', + '骬' => ' yú', + '骞' => ' qiān', + '骝' => ' liú', + '骜' => ' ào', + '骛' => ' wù', + '骚' => ' sāo', + '骙' => ' kuí', + '骘' => ' zhì', + '骗' => ' piàn', + '骖' => ' cān', + '骕' => ' sù', + '骫' => ' wěi', + '骭' => ' gàn', + '骓' => ' zhuī', + '骺' => ' hóu', + '髄' => ' suǐ', + '髃' => ' yú', + '髂' => ' qià', + '髁' => ' kē', + '髀' => ' bì', + '骿' => ' pián', + '骾' => ' gěng', + '骽' => ' tuǐ', + '骼' => ' gé', + '骻' => ' kuà', + '骹' => ' qiāo', + '骮' => ' yì', + '骸' => ' hái', + '骷' => ' kū', + '骶' => ' dǐ', + '骵' => ' tǐ', + '骴' => ' cī', + '骳' => ' bèi', + '骲' => ' bào', + '骱' => ' jiè', + '骰' => ' tóu', + '骯' => ' āng', + '骔' => ' zōng', + '骒' => ' kè', + '驠' => ' yàn', + '马' => ' mǎ', + '驶' => ' shǐ', + '驵' => ' zǎng', + '驴' => ' lǘ', + '驳' => ' bó', + '驲' => ' rì', + '驱' => ' qū', + '驰' => ' chí', + '驯' => ' xún', + '驮' => ' tuó', + '驭' => ' yù', + '驫' => ' biāo', + '驸' => ' fù', + '驪' => ' lí', + '驩' => ' huān', + '驨' => ' xí', + '驧' => ' jú', + '驦' => ' shuāng', + '驥' => ' jì', + '驤' => ' xiāng', + '驣' => ' téng', + '驢' => ' lǘ', + '驡' => ' lóng', + '驷' => ' sì', + '驹' => ' jū', + '骑' => ' qí', + '骆' => ' luò', + '骐' => ' qí', + '骏' => ' jùn', + '骎' => ' qīn', + '骍' => ' xīng', + '验' => ' yàn', + '骋' => ' chěng', + '骊' => ' lí', + '骉' => ' biāo', + '骈' => ' pián', + '骇' => ' hài', + '骅' => ' huá', + '驺' => ' zōu', + '骄' => ' jiāo', + '骃' => ' yīn', + '骂' => ' mà', + '骁' => ' xiāo', + '骀' => ' dài', + '驿' => ' yì', + '驾' => ' jià', + '驽' => ' nú', + '驼' => ' tuó', + '驻' => ' zhù', + '駸' => ' qīn', + '駶' => ' jú', + '髆' => ' bó', + '饨' => ' tún', + '饲' => ' sì', + '饱' => ' bǎo', + '饰' => ' shì', + '饯' => ' jiàn', + '饮' => ' yǐn', + '饭' => ' fàn', + '饬' => ' chì', + '饫' => ' yù', + '饪' => ' rèn', + '饩' => ' xì', + '饧' => ' táng', + '饴' => ' yí', + '饦' => ' tuō', + '饥' => ' jī', + '饤' => ' dìng', + '饣' => ' shí', + '饢' => ' náng', + '饡' => ' zàn', + '饠' => ' luó', + '饟' => ' xiǎng', + '饞' => ' chán', + '饝' => ' mó', + '饳' => ' duò', + '饵' => ' ěr', + '饛' => ' méng', + '馂' => ' jùn', + '馌' => ' yè', + '馋' => ' chán', + '馊' => ' sōu', + '馉' => ' gǔ', + '馈' => ' kuì', + '馇' => ' chā', + '馆' => ' guǎn', + '馅' => ' xiàn', + '馄' => ' hún', + '馃' => ' guǒ', + '馁' => ' něi', + '饶' => ' ráo', + '馀' => ' yú', + '饿' => ' è', + '饾' => ' dòu', + '饽' => ' bō', + '饼' => ' bǐng', + '饻' => ' xī', + '饺' => ' jiǎo', + '饹' => ' le', + '饸' => ' hé', + '饷' => ' xiǎng', + '饜' => ' yàn', + '饚' => ' hài', + '馎' => ' bó', + '餴' => ' fēn', + '餾' => ' liù', + '餽' => ' kuì', + '餼' => ' xì', + '餻' => ' gāo', + '餺' => ' bó', + '餹' => ' táng', + '餸' => ' sòng', + '餷' => ' chā', + '餶' => ' gǔ', + '餵' => ' wèi', + '餳' => ' táng', + '饀' => ' táo', + '餲' => ' ài', + '餱' => ' hóu', + '餰' => ' jiān', + '餯' => ' huì', + '餮' => ' tiè', + '餭' => ' huáng', + '餬' => ' hú', + '餫' => ' yùn', + '餪' => ' nuǎn', + '餩' => ' è', + '餿' => ' sōu', + '饁' => ' yè', + '饙' => ' fēn', + '饎' => ' chì', + '饘' => ' zhān', + '饗' => ' xiǎng', + '饖' => ' wèi', + '饕' => ' tāo', + '饔' => ' yōng', + '饓' => ' chēng', + '饒' => ' ráo', + '饑' => ' jī', + '饐' => ' yì', + '饏' => ' dàn', + '饍' => ' shàn', + '饂' => ' yún', + '饌' => ' zhuàn', + '饋' => ' kuì', + '饊' => ' sǎn', + '饉' => ' jǐn', + '饈' => ' xiū', + '饇' => ' yù', + '饆' => ' bì', + '饅' => ' mán', + '饄' => ' táng', + '饃' => ' mó', + '馍' => ' mó', + '馏' => ' liú', + '駵' => ' liú', + '駐' => ' zhù', + '駚' => ' yǎng', + '駙' => ' fù', + '駘' => ' tái', + '駗' => ' zhěn', + '駖' => ' líng', + '駕' => ' jià', + '駔' => ' zǎng', + '駓' => ' pī', + '駒' => ' jū', + '駑' => ' nú', + '駏' => ' jù', + '駜' => ' bì', + '駎' => ' zhòu', + '駍' => ' péi', + '駌' => ' yuān', + '駋' => ' zhāo', + '駊' => ' pǒ', + '駉' => ' jiōng', + '駈' => ' qū', + '駇' => ' wén', + '駆' => ' qū', + '駅' => ' yì', + '駛' => ' shǐ', + '駝' => ' tuó', + '駃' => ' jué', + '駪' => ' shēn', + '駴' => ' xiè', + '駳' => ' dàn', + '駲' => ' zhou', + '駱' => ' luò', + '駰' => ' yīn', + '駯' => ' zhū', + '駮' => ' bó', + '駭' => ' hài', + '駬' => ' ěr', + '駫' => ' jiōng', + '駩' => ' quān', + '駞' => ' tuó', + '駨' => ' xūn', + '駧' => ' dòng', + '駦' => ' téng', + '駥' => ' róng', + '駤' => ' zhì', + '駣' => ' táo', + '駢' => ' pián', + '駡' => ' mà', + '駠' => ' liú', + '駟' => ' sì', + '駄' => ' tuó', + '駂' => ' bǎo', + '馐' => ' xiū', + '馜' => ' nǐ', + '馦' => ' xiān', + '馥' => ' fù', + '馤' => ' ài', + '馣' => ' ān', + '馢' => ' jiān', + '馡' => ' fēi', + '馠' => ' hān', + '馟' => ' tú', + '馞' => ' bó', + '馝' => ' bì', + '馛' => ' bó', + '馨' => ' xīn', + '馚' => ' fén', + '香' => ' xiāng', + '馘' => ' guó', + '馗' => ' kuí', + '首' => ' shǒu', + '馕' => ' náng', + '馔' => ' zhuàn', + '馓' => ' sǎn', + '馒' => ' mán', + '馑' => ' jǐn', + '馧' => ' yūn', + '馩' => ' fén', + '駁' => ' bó', + '馶' => ' zhī', + '駀' => ' yóu', + '馿' => ' lǘ', + '馾' => ' dàn', + '馽' => ' zhí', + '馼' => ' wén', + '馻' => ' yǔn', + '馺' => ' sà', + '馹' => ' rì', + '馸' => ' xìn', + '馷' => ' pèi', + '馵' => ' zhù', + '馪' => ' pīn', + '馴' => ' xún', + '馳' => ' chí', + '馲' => ' zhé', + '馱' => ' tuó', + '馰' => ' dí', + '馯' => ' hàn', + '馮' => ' féng', + '馭' => ' yù', + '馬' => ' mǎ', + '馫' => ' xīn', + '髅' => ' lóu', + '髇' => ' xiāo', + '鱦' => ' yìng', + '鯙' => ' chún', + '鯣' => ' yì', + '鯢' => ' ní', + '鯡' => ' fèi', + '鯠' => ' lái', + '鯟' => ' dōng', + '鯞' => ' zhǒu', + '鯝' => ' gù', + '鯜' => ' qiè', + '鯛' => ' diāo', + '鯚' => ' jì', + '鯘' => ' něi', + '鯥' => ' lù', + '鯗' => ' xiǎng', + '鯖' => ' zhēng', + '鯕' => ' qí', + '鯔' => ' zī', + '鯓' => ' ní', + '鯒' => ' yǒng', + '鯑' => ' xi', + '鯐' => ' zou', + '鯏' => ' li', + '鯎' => ' cheng', + '鯤' => ' kūn', + '鯦' => ' jiù', + '鯌' => ' kào', + '鯳' => ' di', + '鯽' => ' zéi', + '鯼' => ' zōng', + '鯻' => ' là', + '鯺' => ' zhū', + '鯹' => ' xīng', + '鯸' => ' hóu', + '鯷' => ' tí', + '鯶' => ' hǔn', + '鯵' => ' shēn', + '鯴' => ' shī', + '鯲' => ' yu', + '鯧' => ' chāng', + '鯱' => ' hu', + '鯰' => ' nián', + '鯯' => ' zhì', + '鯮' => ' zōng', + '鯭' => ' měng', + '鯬' => ' lí', + '鯫' => ' zōu', + '鯪' => ' líng', + '鯩' => ' lún', + '鯨' => ' jīng', + '鯍' => ' méng', + '鯋' => ' shā', + '鯿' => ' biān', + '鮥' => ' luò', + '鮯' => ' gé', + '鮮' => ' xiān', + '鮭' => ' guī', + '鮬' => ' kū', + '鮫' => ' jiāo', + '鮪' => ' wěi', + '鮩' => ' bìng', + '鮨' => ' yì', + '鮧' => ' tǐ', + '鮦' => ' tóng', + '鮤' => ' liè', + '鮱' => ' lao', + '鮣' => ' yìn', + '鮢' => ' zhū', + '鮡' => ' zhào', + '鮠' => ' wéi', + '鮟' => ' àn', + '鮞' => ' ér', + '鮝' => ' xiǎng', + '鮜' => ' hòu', + '鮛' => ' shū', + '鮚' => ' jié', + '鮰' => ' huí', + '鮲' => ' fu', + '鯊' => ' shā', + '鮿' => ' zhé', + '鯉' => ' lǐ', + '鯈' => ' tiáo', + '鯇' => ' huàn', + '鯆' => ' pū', + '鯅' => ' shān', + '鯄' => ' qiú', + '鯃' => ' wú', + '鯂' => ' su', + '鯁' => ' gěng', + '鯀' => ' gǔn', + '鮾' => ' něi', + '鮳' => ' kào', + '鮽' => ' yú', + '鮼' => ' qīn', + '鮻' => ' suō', + '鮺' => ' zhǎ', + '鮹' => ' shāo', + '鮸' => ' miǎn', + '鮷' => ' tí', + '鮶' => ' jūn', + '鮵' => ' duó', + '鮴' => ' xiu', + '鯾' => ' biān', + '鰀' => ' huàn', + '鮘' => ' dai', + '鱁' => ' zhú', + '鱋' => ' qū', + '鱊' => ' yù', + '鱉' => ' biē', + '鱈' => ' xuě', + '鱇' => ' kāng', + '鱆' => ' zhāng', + '鱅' => ' yōng', + '鱄' => ' zhuān', + '鱃' => ' xiū', + '鱂' => ' jiāng', + '鱀' => ' jì', + '鱍' => ' bō', + '鰿' => ' jì', + '鰾' => ' biào', + '鰽' => ' qiú', + '鰼' => ' xí', + '鰻' => ' mán', + '鰺' => ' shēn', + '鰹' => ' jiān', + '鰸' => ' qū', + '鰷' => ' tiáo', + '鰶' => ' jì', + '鱌' => ' xiàng', + '鱎' => ' jiǎo', + '鰴' => ' huī', + '鱛' => ' zeng', + '鱥' => ' guì', + '鱤' => ' gǎn', + '鱣' => ' zhān', + '鱢' => ' sāo', + '鱡' => ' zéi', + '鱠' => ' kuài', + '鱟' => ' hòu', + '鱞' => ' guān', + '鱝' => ' fèn', + '鱜' => ' xiang', + '鱚' => ' xǐ', + '鱏' => ' xún', + '鱙' => ' miáo', + '鱘' => ' xún', + '鱗' => ' lín', + '鱖' => ' guì', + '鱕' => ' fān', + '鱔' => ' shàn', + '鱓' => ' shàn', + '鱒' => ' zūn', + '鱑' => ' huáng', + '鱐' => ' sù', + '鰵' => ' mǐn', + '鰳' => ' lè', + '鰁' => ' quán', + '鰍' => ' qiū', + '鰗' => ' hú', + '鰖' => ' tuǒ', + '鰕' => ' xiā', + '鰔' => ' gǎn', + '鰓' => ' sāi', + '鰒' => ' fù', + '鰑' => ' yáng', + '鰐' => ' è', + '鰏' => ' bī', + '鰎' => ' jiǎn', + '鰌' => ' qiū', + '鰙' => ' ruò', + '鰋' => ' yǎn', + '鰊' => ' liàn', + '鰉' => ' huáng', + '鰈' => ' dié', + '鰇' => ' róu', + '鰆' => ' chūn', + '鰅' => ' yú', + '鰄' => ' wēi', + '鰃' => ' wēi', + '鰂' => ' zéi', + '鰘' => ' shi', + '鰚' => ' xuan', + '鰲' => ' áo', + '鰧' => ' téng', + '鰱' => ' lián', + '鰰' => ' shen', + '鰯' => ' ruò', + '鰮' => ' wēn', + '鰭' => ' qí', + '鰬' => ' qián', + '鰫' => ' yóng', + '鰪' => ' é', + '鰩' => ' yáo', + '鰨' => ' tǎ', + '鰦' => ' zī', + '鰛' => ' wēn', + '鰥' => ' guān', + '鰤' => ' shī', + '鰣' => ' shí', + '鰢' => ' mǎ', + '鰡' => ' liú', + '鰠' => ' sāo', + '鰟' => ' fáng', + '鰞' => ' wū', + '鰝' => ' hào', + '鰜' => ' qiàn', + '鮙' => ' tà', + '鮗' => ' dong', + '髈' => ' bǎng', + '鬈' => ' quán', + '鬒' => ' zhěn', + '鬑' => ' lián', + '鬐' => ' qí', + '鬏' => ' jiū', + '鬎' => ' là', + '鬍' => ' hú', + '鬌' => ' tuǒ', + '鬋' => ' jiǎn', + '鬊' => ' shùn', + '鬉' => ' zōng', + '鬇' => ' zhēng', + '鬔' => ' péng', + '鬆' => ' sōng', + '鬅' => ' péng', + '鬄' => ' dí', + '鬃' => ' zōng', + '鬂' => ' bìn', + '鬁' => ' lì', + '鬀' => ' tì', + '髿' => ' suō', + '髾' => ' shāo', + '髽' => ' zhuā', + '鬓' => ' bìn', + '鬕' => ' mà', + '髻' => ' jì', + '鬢' => ' bìn', + '鬬' => ' dòu', + '鬫' => ' hǎn', + '鬪' => ' dòu', + '鬩' => ' xì', + '鬨' => ' hòng', + '鬧' => ' nào', + '鬦' => ' dòu', + '鬥' => ' dòu', + '鬤' => ' ráng', + '鬣' => ' liè', + '鬡' => ' níng', + '鬖' => ' sān', + '鬠' => ' kuò', + '鬟' => ' huán', + '鬞' => ' náng', + '鬝' => ' qiān', + '鬜' => ' qiān', + '鬛' => ' liè', + '鬚' => ' xū', + '鬙' => ' sēng', + '鬘' => ' mán', + '鬗' => ' mán', + '髼' => ' péng', + '髺' => ' kuò', + '鬮' => ' jiū', + '體' => ' tǐ', + '髞' => ' sào', + '髝' => ' láo', + '髜' => ' qiǎo', + '髛' => ' kāo', + '髚' => ' qiào', + '髙' => ' gāo', + '高' => ' gāo', + '髗' => ' lú', + '髖' => ' kuān', + '髕' => ' bìn', + '髓' => ' suǐ', + '髠' => ' kūn', + '髒' => ' zāng', + '髑' => ' dú', + '髐' => ' xiāo', + '髏' => ' lóu', + '髎' => ' liáo', + '髍' => ' mó', + '髌' => ' bìn', + '髋' => ' kuān', + '髊' => ' cī', + '髉' => ' bó', + '髟' => ' biāo', + '髡' => ' kūn', + '髹' => ' xiū', + '髮' => ' fà', + '髸' => ' gōng', + '髷' => ' qū', + '髶' => ' róng', + '髵' => ' ér', + '髴' => ' fú', + '髳' => ' máo', + '髲' => ' bì', + '髱' => ' bào', + '髰' => ' tì', + '髯' => ' rán', + '髭' => ' zī', + '髢' => ' dí', + '髬' => ' pī', + '髫' => ' tiáo', + '髪' => ' fà', + '髩' => ' bìn', + '髨' => ' kūn', + '髧' => ' dàn', + '髦' => ' máo', + '髥' => ' rán', + '髤' => ' xiū', + '髣' => ' fǎng', + '鬭' => ' dòu', + '鬯' => ' chàng', + '鮖' => ' shi', + '魱' => ' hú', + '魻' => ' xiá', + '魺' => ' hé', + '魹' => ' mo', + '魸' => ' pian', + '魷' => ' yóu', + '魶' => ' nà', + '魵' => ' fén', + '魴' => ' fáng', + '魳' => ' zā', + '魲' => ' lú', + '魰' => ' wén', + '魽' => ' hán', + '魯' => ' lǔ', + '魮' => ' pí', + '魭' => ' yuán', + '魬' => ' bǎn', + '魫' => ' shěn', + '魪' => ' jiè', + '魩' => ' mò', + '魨' => ' tún', + '魧' => ' háng', + '魦' => ' shā', + '魼' => ' qū', + '魾' => ' pī', + '魤' => ' é', + '鮋' => ' yóu', + '鮕' => ' gū', + '鮔' => ' jù', + '鮓' => ' zhǎ', + '鮒' => ' fù', + '鮑' => ' bào', + '鮐' => ' tái', + '鮏' => ' xīng', + '鮎' => ' nián', + '鮍' => ' pī', + '鮌' => ' gǔn', + '鮊' => ' bà', + '魿' => ' líng', + '鮉' => ' diāo', + '鮈' => ' jū', + '鮇' => ' wèi', + '鮆' => ' cǐ', + '鮅' => ' bì', + '鮄' => ' fú', + '鮃' => ' píng', + '鮂' => ' qiú', + '鮁' => ' bō', + '鮀' => ' tuó', + '魥' => ' è', + '魣' => ' xù', + '鬰' => ' yù', + '鬼' => ' guǐ', + '魆' => ' xū', + '魅' => ' mèi', + '魄' => ' pò', + '魃' => ' bá', + '魂' => ' hún', + '魁' => ' kuí', + '魀' => ' gà', + '鬿' => ' qí', + '鬾' => ' jì', + '鬽' => ' mèi', + '鬻' => ' yù', + '魈' => ' xiāo', + '鬺' => ' shāng', + '鬹' => ' guī', + '鬸' => ' liù', + '鬷' => ' zōng', + '鬶' => ' guī', + '鬵' => ' qín', + '鬴' => ' fǔ', + '鬳' => ' yàn', + '鬲' => ' gé', + '鬱' => ' yù', + '魇' => ' yǎn', + '魉' => ' liǎng', + '魢' => ' jǐ', + '魗' => ' chǒu', + '魡' => ' diào', + '魠' => ' tuō', + '魟' => ' hóng', + '魞' => ' ba', + '魝' => ' jié', + '魜' => ' rén', + '魛' => ' dāo', + '魚' => ' yú', + '魙' => ' zhān', + '魘' => ' yǎn', + '魖' => ' xū', + '魊' => ' yù', + '魕' => ' jǐ', + '魔' => ' mó', + '魓' => ' bì', + '魒' => ' piāo', + '魑' => ' chī', + '魐' => ' gān', + '魏' => ' wèi', + '魎' => ' liǎng', + '魍' => ' wǎng', + '魌' => ' qī', + '魋' => ' tuí', + '銧' => ' guāng', + '銥' => ' yī', + '薣' => ' gǔ', + '觴' => ' shāng', + '觾' => ' yàn', + '觽' => ' xī', + '觼' => ' jué', + '觻' => ' lì', + '觺' => ' yí', + '觹' => ' xī', + '觸' => ' chù', + '觷' => ' xué', + '觶' => ' zhì', + '觵' => ' gōng', + '觳' => ' hú', + '言' => ' yán', + '觲' => ' xīng', + '觱' => ' bì', + '觰' => ' zhā', + '觯' => ' zhì', + '觮' => ' lù', + '觭' => ' jī', + '觬' => ' ní', + '觫' => ' sù', + '觪' => ' xīng', + '觩' => ' qiú', + '觿' => ' xī', + '訁' => ' yán', + '觧' => ' jiě', + '討' => ' tǎo', + '記' => ' jì', + '託' => ' tuō', + '訖' => ' qì', + '訕' => ' shàn', + '訔' => ' yín', + '訓' => ' xùn', + '訒' => ' rèn', + '訑' => ' yí', + '訐' => ' jié', + '訏' => ' xū', + '訍' => ' chài', + '訂' => ' dìng', + '訌' => ' hòng', + '訋' => ' diào', + '訊' => ' xùn', + '訉' => ' fān', + '計' => ' jì', + '訇' => ' hōng', + '訆' => ' jiào', + '訅' => ' qiú', + '訄' => ' qiú', + '訃' => ' fù', + '觨' => ' hùn', + '触' => ' chù', + '訚' => ' yín', + '觀' => ' guān', + '觊' => ' jì', + '觉' => ' jué', + '览' => ' lǎn', + '觇' => ' chān', + '视' => ' shì', + '觅' => ' mì', + '规' => ' guī', + '觃' => ' yàn', + '观' => ' guān', + '见' => ' jiàn', + '覿' => ' dí', + '觌' => ' dí', + '覾' => ' shěn', + '覽' => ' lǎn', + '覼' => ' luó', + '覻' => ' qū', + '覺' => ' jué', + '覹' => ' wéi', + '覸' => ' jiān', + '覷' => ' qù', + '覶' => ' luó', + '覵' => ' jiàn', + '觋' => ' xí', + '觍' => ' tiǎn', + '觥' => ' gōng', + '觚' => ' gū', + '觤' => ' guǐ', + '解' => ' jiě', + '觢' => ' shì', + '觡' => ' gé', + '觠' => ' quán', + '觟' => ' huà', + '觞' => ' shāng', + '觝' => ' dǐ', + '觜' => ' zī', + '觛' => ' dàn', + '觙' => ' jí', + '觎' => ' yú', + '觘' => ' chào', + '觗' => ' zhì', + '觖' => ' jué', + '觕' => ' cū', + '觔' => ' jīn', + '觓' => ' qiú', + '角' => ' jiǎo', + '觑' => ' qù', + '觐' => ' jìn', + '觏' => ' gòu', + '訙' => ' xùn', + '訛' => ' é', + '観' => ' guān', + '詜' => ' tāo', + '試' => ' shì', + '詥' => ' hé', + '詤' => ' huǎng', + '詣' => ' yì', + '詢' => ' xún', + '詡' => ' xǔ', + '詠' => ' yǒng', + '詟' => ' zhé', + '詞' => ' cí', + '詝' => ' zhǔ', + '詛' => ' zǔ', + '詨' => ' xiào', + '詚' => ' dá', + '詙' => ' bá', + '詘' => ' qū', + '詗' => ' xiòng', + '詖' => ' bì', + '評' => ' píng', + '詔' => ' zhào', + '詓' => ' qǔ', + '詒' => ' yí', + '詑' => ' yí', + '詧' => ' chá', + '詩' => ' shī', + '詏' => ' yào', + '詶' => ' zhòu', + '誀' => ' èr', + '詿' => ' guà', + '詾' => ' xiōng', + '詽' => ' yán', + '詼' => ' huī', + '詻' => ' è', + '詺' => ' mìng', + '詹' => ' zhān', + '詸' => ' mí', + '詷' => ' tóng', + '詵' => ' shēn', + '詪' => ' hěn', + '詴' => ' wēi', + '詳' => ' xiáng', + '該' => ' gāi', + '話' => ' huà', + '詰' => ' jié', + '詯' => ' huì', + '詮' => ' quán', + '詭' => ' guǐ', + '詬' => ' gòu', + '詫' => ' chà', + '詐' => ' zhà', + '詎' => ' jù', + '訜' => ' fēn', + '訨' => ' zhǐ', + '訲' => ' yì', + '許' => ' xǔ', + '訰' => ' zhùn', + '訯' => ' sǎ', + '訮' => ' xiān', + '設' => ' shè', + '訬' => ' chāo', + '訫' => ' xìn', + '訪' => ' fǎng', + '訩' => ' xiōng', + '訧' => ' yóu', + '訴' => ' sù', + '訦' => ' chén', + '訥' => ' nè', + '訤' => ' xiáo', + '訣' => ' jué', + '訢' => ' xīn', + '訡' => ' yín', + '訠' => ' shěn', + '訟' => ' sòng', + '訞' => ' yāo', + '訝' => ' yà', + '訳' => ' yì', + '訵' => ' chī', + '詍' => ' yì', + '詂' => ' fù', + '詌' => ' gàn', + '詋' => ' zhòu', + '詊' => ' pàn', + '詉' => ' náo', + '詈' => ' lì', + '詇' => ' yàng', + '詆' => ' dǐ', + '詅' => ' líng', + '詄' => ' dié', + '詃' => ' jiǎn', + '詁' => ' gǔ', + '訶' => ' hē', + '詀' => ' zhān', + '訿' => ' zǐ', + '訾' => ' zī', + '訽' => ' gòu', + '証' => ' zhèng', + '註' => ' zhù', + '診' => ' zhěn', + '訹' => ' xù', + '訸' => ' hé', + '訷' => ' shēn', + '覴' => ' dēng', + '覲' => ' jìn', + '誂' => ' tiǎo', + '褤' => ' yuàn', + '褮' => ' yīng', + '褭' => ' niǎo', + '褬' => ' sǎng', + '褫' => ' chǐ', + '褪' => ' tuì', + '褩' => ' bān', + '褨' => ' suǒ', + '褧' => ' jiǒng', + '褦' => ' nài', + '褥' => ' rù', + '褣' => ' róng', + '褰' => ' qiān', + '褢' => ' huái', + '褡' => ' dā', + '褠' => ' gōu', + '褟' => ' tā', + '褞' => ' yǔn', + '褝' => ' dān', + '褜' => ' pao', + '褛' => ' lǚ', + '褚' => ' chǔ', + '褙' => ' bèi', + '褯' => ' jiè', + '褱' => ' huái', + '褗' => ' yǎn', + '褾' => ' biǎo', + '襈' => ' zhuàn', + '襇' => ' jiǎn', + '襆' => ' fú', + '襅' => ' bi', + '襄' => ' xiāng', + '襃' => ' bāo', + '襂' => ' sēn', + '襁' => ' qiǎng', + '襀' => ' jī', + '褿' => ' cáo', + '褽' => ' wèi', + '褲' => ' kù', + '褼' => ' xiān', + '褻' => ' xiè', + '褺' => ' diē', + '褹' => ' yì', + '褸' => ' lǚ', + '褷' => ' shī', + '褶' => ' zhě', + '褵' => ' lí', + '褴' => ' lán', + '褳' => ' lián', + '褘' => ' huī', + '褖' => ' tuàn', + '襊' => ' cuì', + '裰' => ' duō', + '裺' => ' yǎn', + '裹' => ' guǒ', + '裸' => ' luǒ', + '裷' => ' yuān', + '裶' => ' fēi', + '裵' => ' péi', + '裴' => ' péi', + '裳' => ' shang', + '裲' => ' liǎng', + '裱' => ' biǎo', + '裯' => ' chóu', + '裼' => ' tì', + '裮' => ' chāng', + '裭' => ' chǐ', + '裬' => ' líng', + '裫' => ' yuàn', + '裪' => ' táo', + '裩' => ' kūn', + '裨' => ' bì', + '裧' => ' chān', + '裦' => ' fóu', + '裥' => ' jiǎn', + '裻' => ' dú', + '製' => ' zhì', + '褕' => ' yú', + '褊' => ' biǎn', + '褔' => ' fù', + '褓' => ' bǎo', + '褒' => ' bāo', + '褑' => ' yuàn', + '褐' => ' hè', + '褏' => ' xiù', + '褎' => ' xiù', + '褍' => ' duān', + '褌' => ' kūn', + '褋' => ' dié', + '褉' => ' xiè', + '裾' => ' jū', + '褈' => ' chóng', + '複' => ' fù', + '褆' => ' tí', + '褅' => ' tì', + '褄' => ' qi', + '褃' => ' kèn', + '褂' => ' guà', + '褁' => ' zhí', + '褀' => ' jì', + '裿' => ' yǐ', + '襉' => ' jiǎn', + '襋' => ' jí', + '覱' => ' zhàn', + '覌' => ' guān', + '視' => ' shì', + '覕' => ' miè', + '覔' => ' mì', + '覓' => ' mì', + '覒' => ' mào', + '覑' => ' piǎn', + '覐' => ' jué', + '規' => ' guī', + '覎' => ' yàn', + '覍' => ' biàn', + '見' => ' jiàn', + '覘' => ' chān', + '覊' => ' jī', + '覉' => ' jī', + '覈' => ' hé', + '覇' => ' bà', + '覆' => ' fù', + '覅' => ' fiào', + '覄' => ' fu', + '覃' => ' tán', + '覂' => ' fěng', + '要' => ' yào', + '覗' => ' sì', + '覙' => ' luó', + '西' => ' xī', + '覦' => ' yú', + '覰' => ' qū', + '覯' => ' gòu', + '覮' => ' yíng', + '覭' => ' míng', + '覬' => ' jì', + '覫' => ' pǎng', + '親' => ' qīn', + '覩' => ' dǔ', + '覨' => ' è', + '覧' => ' lǎn', + '覥' => ' tiǎn', + '覚' => ' jué', + '覤' => ' xì', + '覣' => ' wēi', + '覢' => ' shǎn', + '覡' => ' xí', + '覠' => ' jūn', + '覟' => ' zhì', + '覞' => ' yào', + '覝' => ' lián', + '覜' => ' tiào', + '覛' => ' mì', + '覀' => ' xī', + '襾' => ' yà', + '襌' => ' dān', + '襘' => ' guì', + '襢' => ' tǎn', + '襡' => ' shǔ', + '襠' => ' dāng', + '襟' => ' jīn', + '襞' => ' bì', + '襝' => ' liǎn', + '襜' => ' chān', + '襛' => ' nóng', + '襚' => ' suì', + '襙' => ' cào', + '襗' => ' zé', + '襤' => ' lán', + '襖' => ' ǎo', + '襕' => ' lán', + '襔' => ' mǎn', + '襓' => ' ráo', + '襒' => ' bié', + '襑' => ' xín', + '襐' => ' xiàng', + '襏' => ' bó', + '襎' => ' fán', + '襍' => ' zá', + '襣' => ' bì', + '襥' => ' pú', + '襽' => ' lan', + '襲' => ' xí', + '襼' => ' yì', + '襻' => ' pàn', + '襺' => ' jiǎn', + '襹' => ' shī', + '襸' => ' zàn', + '襷' => ' ju', + '襶' => ' dài', + '襵' => ' zhě', + '襴' => ' lán', + '襳' => ' xiān', + '襱' => ' lóng', + '襦' => ' rú', + '襰' => ' lài', + '襯' => ' chèn', + '襮' => ' bó', + '襭' => ' xié', + '襬' => ' bǎi', + '襫' => ' shì', + '襪' => ' wà', + '襩' => ' shǔ', + '襨' => ' dùi', + '襧' => ' zhǐ', + '誁' => ' bìng', + '誃' => ' yí', + '裣' => ' liǎn', + '讕' => ' lán', + '讟' => ' dú', + '讞' => ' yàn', + '讝' => ' zhán', + '讜' => ' dǎng', + '讛' => ' yì', + '讚' => ' zàn', + '讙' => ' huān', + '讘' => ' niè', + '讗' => ' xié', + '讖' => ' chèn', + '讔' => ' yǐn', + '计' => ' jì', + '讓' => ' ràng', + '讒' => ' chán', + '讑' => ' yào', + '讐' => ' chóu', + '讏' => ' wèi', + '讎' => ' chóu', + '讍' => ' è', + '讌' => ' yàn', + '讋' => ' zhé', + '變' => ' biàn', + '讠' => ' yán', + '订' => ' dìng', + '讈' => ' lì', + '讯' => ' xùn', + '讹' => ' é', + '许' => ' xǔ', + '讷' => ' nè', + '讶' => ' yà', + '讵' => ' jù', + '讴' => ' ōu', + '讳' => ' huì', + '讲' => ' jiǎng', + '讱' => ' rèn', + '记' => ' jì', + '议' => ' yì', + '讣' => ' fù', + '训' => ' xùn', + '讬' => ' tuō', + '讫' => ' qì', + '讪' => ' shàn', + '让' => ' ràng', + '讨' => ' tǎo', + '讧' => ' hòng', + '讦' => ' jié', + '讥' => ' jī', + '认' => ' rèn', + '讉' => ' yí', + '讇' => ' chǎn', + '讻' => ' xiōng', + '譡' => ' dǎng', + '譫' => ' zhān', + '譪' => ' ài', + '譩' => ' yī', + '譨' => ' náng', + '譧' => ' zhàn', + '警' => ' jǐng', + '譥' => ' jiào', + '譤' => ' jī', + '譣' => ' xiǎn', + '譢' => ' suì', + '譠' => ' tán', + '譭' => ' huǐ', + '譟' => ' zào', + '譞' => ' xuān', + '譝' => ' shéng', + '譜' => ' pǔ', + '譛' => ' zèn', + '譚' => ' tán', + '譙' => ' qiào', + '識' => ' shí', + '譗' => ' zhá', + '譖' => ' zèn', + '譬' => ' pì', + '譮' => ' huà', + '讆' => ' wèi', + '譻' => ' yīng', + '讅' => ' shěn', + '讄' => ' lěi', + '讃' => ' zàn', + '讂' => ' xuàn', + '讁' => ' zhé', + '讀' => ' dú', + '譿' => ' huì', + '譾' => ' jiǎn', + '譽' => ' yù', + '譼' => ' jiàn', + '譺' => ' ài', + '譯' => ' yì', + '譹' => ' háo', + '譸' => ' zhōu', + '護' => ' hù', + '譶' => ' tà', + '譵' => ' zhuì', + '譴' => ' qiǎn', + '譳' => ' nòu', + '譲' => ' ràng', + '譱' => ' shàn', + '議' => ' yì', + '论' => ' lùn', + '讼' => ' sòng', + '譔' => ' zhuàn', + '诽' => ' fěi', + '谇' => ' suì', + '谆' => ' zhūn', + '谅' => ' liàng', + '谄' => ' chǎn', + '调' => ' diào', + '谂' => ' shěn', + '谁' => ' shuí', + '谀' => ' yú', + '诿' => ' wěi', + '课' => ' kè', + '诼' => ' zhuó', + '谉' => ' shěn', + '读' => ' dú', + '诺' => ' nuò', + '诹' => ' zōu', + '诸' => ' zhū', + '请' => ' qǐng', + '诶' => ' éi', + '诵' => ' sòng', + '说' => ' shuō', + '诳' => ' kuáng', + '诲' => ' huì', + '谈' => ' tán', + '谊' => ' yì', + '诰' => ' gào', + '谗' => ' chán', + '谡' => ' sù', + '谠' => ' dǎng', + '谟' => ' mó', + '谞' => ' xū', + '谝' => ' piǎn', + '谜' => ' mí', + '谛' => ' dì', + '谚' => ' yàn', + '谙' => ' ān', + '谘' => ' zī', + '谖' => ' xuān', + '谋' => ' móu', + '谕' => ' yù', + '谔' => ' è', + '谓' => ' wèi', + '谒' => ' yè', + '谑' => ' xuè', + '谐' => ' xié', + '谏' => ' jiàn', + '谎' => ' huǎng', + '谍' => ' dié', + '谌' => ' chén', + '诱' => ' yòu', + '误' => ' wù', + '讽' => ' fěng', + '诉' => ' sù', + '诓' => ' kuāng', + '诒' => ' yí', + '译' => ' yì', + '诐' => ' bì', + '诏' => ' zhào', + '诎' => ' qū', + '词' => ' cí', + '诌' => ' zhōu', + '诋' => ' dǐ', + '诊' => ' zhěn', + '诈' => ' zhà', + '试' => ' shì', + '诇' => ' xiòng', + '识' => ' shí', + '诅' => ' zǔ', + '评' => ' píng', + '诃' => ' hē', + '诂' => ' gǔ', + '证' => ' zhèng', + '诀' => ' jué', + '访' => ' fǎng', + '设' => ' shè', + '诔' => ' lěi', + '诖' => ' guà', + '诮' => ' qiào', + '诣' => ' yì', + '语' => ' yǔ', + '诬' => ' wū', + '诫' => ' jiè', + '诪' => ' zhōu', + '诩' => ' xǔ', + '诨' => ' hùn', + '诧' => ' chà', + '详' => ' xiáng', + '该' => ' gāi', + '诤' => ' zhēng', + '询' => ' xún', + '诗' => ' shī', + '诡' => ' guǐ', + '诠' => ' quán', + '诟' => ' gòu', + '诞' => ' dàn', + '话' => ' huà', + '诜' => ' shēn', + '殖' => ' zhí', + '诚' => ' chéng', + '诙' => ' huī', + '诘' => ' jí', + '譕' => ' wú', + '譓' => ' huì', + '誄' => ' lěi', + '諄' => ' zhūn', + '諎' => ' zé', + '諍' => ' zhèng', + '諌' => ' dǒng', + '請' => ' qǐng', + '諊' => ' jú', + '諉' => ' wěi', + '諈' => ' zhuì', + '談' => ' tán', + '諆' => ' qī', + '諅' => ' jì', + '諃' => ' chēn', + '諐' => ' qiān', + '諂' => ' chǎn', + '諁' => ' zhuó', + '諀' => ' pǐ', + '調' => ' diào', + '誾' => ' yín', + '誽' => ' nì', + '誼' => ' yì', + '誻' => ' tà', + '誺' => ' chī', + '誹' => ' fěi', + '諏' => ' zōu', + '諑' => ' zhuó', + '誷' => ' wǎng', + '諞' => ' piǎn', + '諨' => ' fú', + '諧' => ' xié', + '諦' => ' dì', + '諥' => ' zhòng', + '諤' => ' è', + '諣' => ' huà', + '諢' => ' hùn', + '諡' => ' shì', + '諠' => ' xuān', + '諟' => ' shì', + '諝' => ' xū', + '諒' => ' liàng', + '諜' => ' dié', + '諛' => ' yú', + '諚' => ' pián', + '諙' => ' huài', + '諘' => ' biǎo', + '諗' => ' shěn', + '論' => ' lùn', + '諕' => ' háo', + '諔' => ' chù', + '諓' => ' jiàn', + '誸' => ' xián', + '誶' => ' suì', + '諪' => ' tíng', + '誐' => ' é', + '誚' => ' qiào', + '誙' => ' kēng', + '誘' => ' yòu', + '誗' => ' chán', + '誖' => ' bèi', + '誕' => ' dàn', + '誔' => ' tǐng', + '誓' => ' shì', + '誒' => ' éi', + '誑' => ' kuáng', + '誏' => ' lǎng', + '誜' => ' shuà', + '誎' => ' cù', + '認' => ' rèn', + '誌' => ' zhì', + '誋' => ' jì', + '誊' => ' téng', + '誉' => ' yù', + '誈' => ' wú', + '誇' => ' kuā', + '誆' => ' kuāng', + '誅' => ' zhū', + '誛' => ' qīn', + '誝' => ' ān', + '誵' => ' xiáo', + '說' => ' shuō', + '誴' => ' cóng', + '誳' => ' qū', + '課' => ' kè', + '誱' => ' jié', + '誰' => ' shuí', + '誯' => ' chàng', + '誮' => ' hua', + '読' => ' dú', + '説' => ' shuō', + '誫' => ' zhèn', + '誩' => ' jìng', + '語' => ' yǔ', + '誨' => ' huì', + '誧' => ' bū', + '誦' => ' sòng', + '誥' => ' gào', + '誤' => ' wù', + '誣' => ' wū', + '誢' => ' xiàn', + '誡' => ' jiè', + '誠' => ' chéng', + '誟' => ' xiào', + '諩' => ' pǔ', + '諫' => ' jiàn', + '譒' => ' bò', + '謭' => ' jiǎn', + '謷' => ' áo', + '謶' => ' zhuó', + '謵' => ' xí', + '謴' => ' gùn', + '謳' => ' ōu', + '謲' => ' càn', + '謱' => ' lóu', + '謰' => ' lián', + '謯' => ' jiē', + '謮' => ' zé', + '謬' => ' miù', + '謹' => ' jǐn', + '謫' => ' zhé', + '謪' => ' shāng', + '謩' => ' mò', + '謨' => ' mó', + '謧' => ' lí', + '謦' => ' qìng', + '謥' => ' còng', + '謤' => ' biāo', + '謣' => ' yú', + '謢' => ' zhi', + '謸' => ' áo', + '謺' => ' zhé', + '謠' => ' yáo', + '譇' => ' zhā', + '譑' => ' jiǎo', + '譐' => ' zǔn', + '譏' => ' jī', + '譎' => ' jué', + '譍' => ' yīng', + '譌' => ' é', + '譋' => ' lán', + '譊' => ' náo', + '證' => ' zhèng', + '譈' => ' duì', + '譆' => ' xī', + '謻' => ' yí', + '譅' => ' sè', + '譄' => ' zēng', + '譃' => ' xū', + '譂' => ' chǎn', + '譁' => ' huá', + '譀' => ' hàn', + '謿' => ' cháo', + '謾' => ' mán', + '謽' => ' jiàng', + '謼' => ' hū', + '謡' => ' yáo', + '謟' => ' tāo', + '諬' => ' qǐ', + '諸' => ' zhū', + '謂' => ' wèi', + '謁' => ' yè', + '謀' => ' móu', + '諿' => ' qī', + '諾' => ' nuò', + '諽' => ' gé', + '諼' => ' xuān', + '諻' => ' huáng', + '諺' => ' yàn', + '諹' => ' yáng', + '諷' => ' fěng', + '謄' => ' téng', + '諶' => ' chén', + '諵' => ' nán', + '諴' => ' xián', + '諳' => ' ān', + '諲' => ' yīn', + '諱' => ' huì', + '諰' => ' xǐ', + '諯' => ' zhuān', + '諮' => ' zī', + '諭' => ' yù', + '謃' => ' xing', + '謅' => ' zhōu', + '謞' => ' hè', + '謓' => ' chēn', + '謝' => ' xiè', + '謜' => ' yuán', + '講' => ' jiǎng', + '謚' => ' shì', + '謙' => ' qiān', + '謘' => ' chí', + '謗' => ' bàng', + '謖' => ' sù', + '謕' => ' tí', + '謔' => ' xuè', + '謒' => ' qiāng', + '謆' => ' shàn', + '謑' => ' xǐ', + '謐' => ' mì', + '謏' => ' xiǎo', + '謎' => ' mí', + '謍' => ' yíng', + '謌' => ' gē', + '謋' => ' huò', + '謊' => ' huǎng', + '謉' => ' kuì', + '謈' => ' pó', + '謇' => ' jiǎn', + '裤' => ' kù', + '裢' => ' lián', + '谣' => ' yáo', + '蚳' => ' chí', + '蚽' => ' pí', + '蚼' => ' gǒu', + '蚻' => ' zhá', + '蚺' => ' rán', + '蚹' => ' fù', + '蚸' => ' lì', + '蚷' => ' jù', + '蚶' => ' hān', + '蚵' => ' hé', + '蚴' => ' yòu', + '蚲' => ' píng', + '蚿' => ' xián', + '蚱' => ' zhà', + '蚰' => ' yóu', + '蚯' => ' qiū', + '蚮' => ' tè', + '蚭' => ' ní', + '蚬' => ' xiǎn', + '蚫' => ' bào', + '蚪' => ' dǒu', + '蚩' => ' chī', + '蚨' => ' fú', + '蚾' => ' pí', + '蛀' => ' zhù', + '蚦' => ' rán', + '蛍' => ' yíng', + '蛗' => ' fù', + '蛖' => ' máng', + '蛕' => ' huí', + '蛔' => ' huí', + '蛓' => ' cì', + '蛒' => ' gé', + '蛑' => ' móu', + '蛐' => ' qū', + '蛏' => ' chēng', + '蛎' => ' lì', + '蛌' => ' gǔ', + '蛁' => ' diāo', + '蛋' => ' dàn', + '蛊' => ' gǔ', + '蛉' => ' líng', + '蛈' => ' tiě', + '蛇' => ' shé', + '蛆' => ' qū', + '蛅' => ' zhān', + '蛄' => ' gū', + '蛃' => ' bǐng', + '蛂' => ' bié', + '蚧' => ' jiè', + '蚥' => ' fù', + '蛙' => ' wā', + '虿' => ' chài', + '蚉' => ' wén', + '蚈' => ' qiān', + '蚇' => ' chǐ', + '蚆' => ' bā', + '蚅' => ' è', + '蚄' => ' fāng', + '蚃' => ' xiàng', + '蚂' => ' mǎ', + '蚁' => ' yǐ', + '蚀' => ' shí', + '虾' => ' xiā', + '蚋' => ' ruì', + '虽' => ' suī', + '虼' => ' gè', + '虻' => ' méng', + '虺' => ' huī', + '虹' => ' hóng', + '虸' => ' zǐ', + '虷' => ' hán', + '虶' => ' yū', + '虵' => ' shé', + '虴' => ' zhé', + '蚊' => ' wén', + '蚌' => ' bàng', + '蚤' => ' zǎo', + '蚙' => ' qín', + '蚣' => ' gōng', + '蚢' => ' háng', + '蚡' => ' fén', + '蚠' => ' fén', + '蚟' => ' wáng', + '蚞' => ' mù', + '蚝' => ' háo', + '蚜' => ' yá', + '蚛' => ' zhòng', + '蚚' => ' qí', + '蚘' => ' huí', + '蚍' => ' pí', + '蚗' => ' jué', + '蚖' => ' yuán', + '蚕' => ' cán', + '蚔' => ' qí', + '蚓' => ' yǐn', + '蚒' => ' tóng', + '蚑' => ' qí', + '蚐' => ' jūn', + '蚏' => ' yuè', + '蚎' => ' yuè', + '蛘' => ' yáng', + '蛚' => ' liè', + '虲' => ' xiā', + '蜛' => ' jū', + '蜥' => ' xī', + '蜤' => ' sī', + '蜣' => ' qiāng', + '蜢' => ' měng', + '蜡' => ' là', + '蜠' => ' jùn', + '蜟' => ' yù', + '蜞' => ' qí', + '蜝' => ' qí', + '蜜' => ' mì', + '蜚' => ' fēi', + '蜧' => ' lì', + '蜙' => ' sōng', + '蜘' => ' zhī', + '蜗' => ' wō', + '蜖' => ' huí', + '蜕' => ' tuì', + '蜔' => ' diàn', + '蜓' => ' tíng', + '蜒' => ' yán', + '蜑' => ' dàn', + '蜐' => ' jié', + '蜦' => ' lún', + '蜨' => ' dié', + '蜎' => ' yuān', + '蜵' => ' yuān', + '蜿' => ' wān', + '蜾' => ' guǒ', + '蜽' => ' liǎng', + '蜼' => ' wèi', + '蜻' => ' qīng', + '蜺' => ' ní', + '蜹' => ' ruì', + '蜸' => ' qiǎn', + '蜷' => ' quán', + '蜶' => ' suò', + '蜴' => ' yì', + '蜩' => ' tiáo', + '蜳' => ' dūn', + '蜲' => ' wēi', + '蜱' => ' pí', + '蜰' => ' féi', + '蜯' => ' bàng', + '蜮' => ' yù', + '蜭' => ' hàn', + '蜬' => ' hán', + '蜫' => ' kūn', + '蜪' => ' táo', + '蜏' => ' yǒu', + '蜍' => ' chú', + '蛛' => ' zhū', + '蛧' => ' wǎng', + '蛱' => ' jiá', + '蛰' => ' zhé', + '蛯' => ' lao', + '蛮' => ' mán', + '蛭' => ' zhì', + '蛬' => ' qióng', + '蛫' => ' guǐ', + '蛪' => ' qiè', + '蛩' => ' qióng', + '蛨' => ' mò', + '蛦' => ' yí', + '蛳' => ' sī', + '蛥' => ' shé', + '蛤' => ' há', + '蛣' => ' qī', + '蛢' => ' píng', + '蛡' => ' yì', + '蛠' => ' lì', + '蛟' => ' jiāo', + '蛞' => ' kuò', + '蛝' => ' xián', + '蛜' => ' yī', + '蛲' => ' náo', + '蛴' => ' qí', + '蜌' => ' bì', + '蜁' => ' xuán', + '蜋' => ' láng', + '蜊' => ' lí', + '蜉' => ' fú', + '蜈' => ' wú', + '蜇' => ' zhē', + '蜆' => ' xiàn', + '蜅' => ' fǔ', + '蜄' => ' shèn', + '蜃' => ' shèn', + '蜂' => ' fēng', + '蜀' => ' shǔ', + '蛵' => ' xīng', + '蛿' => ' hàn', + '蛾' => ' é', + '蛽' => ' bài', + '蛼' => ' chē', + '蛻' => ' tuì', + '蛺' => ' jiá', + '蛹' => ' yǒng', + '蛸' => ' shāo', + '蛷' => ' qiú', + '蛶' => ' jiè', + '虳' => ' jué', + '虱' => ' shī', + '蝁' => ' è', + '藣' => ' bēi', + '藭' => ' qióng', + '藬' => ' tuī', + '藫' => ' tán', + '藪' => ' sǒu', + '藩' => ' fān', + '藨' => ' biāo', + '藧' => ' huàn', + '藦' => ' mò', + '藥' => ' yào', + '藤' => ' téng', + '藢' => ' zhǐ', + '藯' => ' wèi', + '藡' => ' dí', + '藠' => ' jiào', + '藟' => ' lěi', + '藞' => ' lǎ', + '藝' => ' yì', + '藜' => ' lí', + '藛' => ' xiě', + '藚' => ' xù', + '藙' => ' yì', + '藘' => ' lǘ', + '藮' => ' qiáo', + '藰' => ' liú', + '藖' => ' xián', + '藽' => ' qìn', + '蘇' => ' sū', + '蘆' => ' lú', + '蘅' => ' héng', + '蘄' => ' qí', + '蘃' => ' ruǐ', + '蘂' => ' ruǐ', + '蘁' => ' wù', + '蘀' => ' tuò', + '藿' => ' huò', + '藾' => ' lài', + '藼' => ' xuān', + '藱' => ' huì', + '藻' => ' zǎo', + '藺' => ' lìn', + '藹' => ' ǎi', + '藸' => ' chú', + '藷' => ' shǔ', + '藶' => ' lì', + '藵' => ' bao', + '藴' => ' yùn', + '藳' => ' gǎo', + '藲' => ' ou', + '藗' => ' sù', + '藕' => ' ǒu', + '蘉' => ' méng', + '薯' => ' shǔ', + '薹' => ' tái', + '薸' => ' piáo', + '薷' => ' rú', + '薶' => ' mái', + '薵' => ' chóu', + '薴' => ' níng', + '薳' => ' wěi', + '薲' => ' pín', + '薱' => ' duì', + '薰' => ' xūn', + '薮' => ' sǒu', + '薻' => ' zǎo', + '薭' => ' bai', + '薬' => ' yào', + '薫' => ' xūn', + '薪' => ' xīn', + '薩' => ' sà', + '薨' => ' hōng', + '薧' => ' hāo', + '薦' => ' jiàn', + '薥' => ' shǔ', + '薤' => ' xiè', + '薺' => ' jì', + '薼' => ' chén', + '藔' => ' liáo', + '藉' => ' jí', + '藓' => ' xiǎn', + '藒' => ' qiè', + '藑' => ' qióng', + '藐' => ' miǎo', + '藏' => ' cáng', + '藎' => ' jìn', + '藍' => ' lán', + '藌' => ' mì', + '藋' => ' diào', + '藊' => ' biǎn', + '藈' => ' kuí', + '薽' => ' zhēn', + '藇' => ' xù', + '藆' => ' jiǎn', + '藅' => ' fá', + '藄' => ' qí', + '藃' => ' xiāo', + '藂' => ' cóng', + '藁' => ' gǎo', + '藀' => ' yíng', + '薿' => ' nǐ', + '薾' => ' ěr', + '蘈' => ' tuí', + '蘊' => ' yùn', + '虰' => ' dīng', + '虋' => ' mén', + '處' => ' chù', + '虔' => ' qián', + '虓' => ' xiāo', + '虒' => ' sī', + '虑' => ' lǜ', + '虐' => ' nüè', + '虏' => ' lǔ', + '虎' => ' hǔ', + '虍' => ' hū', + '虌' => ' biē', + '虊' => ' luán', + '虗' => ' xū', + '虉' => ' yì', + '虈' => ' xiāo', + '虇' => ' quǎn', + '虆' => ' léi', + '虅' => ' teng', + '虄' => ' sà', + '虃' => ' jiān', + '虂' => ' lù', + '虁' => ' kuí', + '虀' => ' jī', + '虖' => ' hū', + '虘' => ' cuó', + '蘾' => ' huài', + '虥' => ' zhàn', + '虯' => ' qiú', + '虮' => ' jǐ', + '虭' => ' diāo', + '虬' => ' qiú', + '虫' => ' chóng', + '虪' => ' shù', + '虩' => ' xì', + '虨' => ' bīn', + '虧' => ' kuī', + '虦' => ' zhàn', + '虤' => ' yán', + '虙' => ' fú', + '虣' => ' bào', + '虢' => ' guó', + '虡' => ' jù', + '虠' => ' jiāo', + '號' => ' hào', + '虞' => ' yú', + '虝' => ' hǔ', + '虜' => ' lǔ', + '虛' => ' xū', + '虚' => ' xū', + '蘿' => ' luó', + '蘽' => ' lěi', + '蘋' => ' píng', + '蘗' => ' bò', + '蘡' => ' yīng', + '蘠' => ' qiáng', + '蘟' => ' yǐn', + '蘞' => ' liǎn', + '蘝' => ' liàn', + '蘜' => ' jú', + '蘛' => ' yú', + '蘚' => ' xiǎn', + '蘙' => ' yì', + '蘘' => ' ráng', + '蘖' => ' niè', + '蘣' => ' tǒu', + '蘕' => ' feng', + '蘔' => ' jiōng', + '蘓' => ' sū', + '蘒' => ' qiu', + '蘑' => ' mó', + '蘐' => ' xuān', + '蘏' => ' jiōng', + '蘎' => ' jì', + '蘍' => ' xūn', + '蘌' => ' yǔ', + '蘢' => ' lóng', + '蘤' => ' wěi', + '蘼' => ' mí', + '蘱' => ' lèi', + '蘻' => ' jì', + '蘺' => ' lí', + '蘹' => ' huái', + '蘸' => ' zhàn', + '蘷' => ' kuí', + '蘶' => ' wèi', + '蘵' => ' zhī', + '蘴' => ' fēng', + '蘳' => ' huī', + '蘲' => ' léi', + '蘰' => ' man', + '蘥' => ' yuè', + '蘯' => ' dàng', + '蘮' => ' jì', + '蘭' => ' lán', + '蘬' => ' kuī', + '蘫' => ' hàn', + '蘪' => ' méi', + '蘩' => ' fán', + '蘨' => ' yáo', + '蘧' => ' qú', + '蘦' => ' líng', + '蝀' => ' dōng', + '蝂' => ' bǎn', + '裡' => ' lǐ', + '衔' => ' xián', + '衞' => ' wèi', + '衝' => ' chōng', + '衜' => ' dào', + '衛' => ' wèi', + '衚' => ' hú', + '衙' => ' yá', + '衘' => ' xián', + '街' => ' jiē', + '衖' => ' xiàng', + '衕' => ' tòng', + '術' => ' shù', + '衠' => ' zhūn', + '衒' => ' xuàn', + '衑' => ' líng', + '衐' => ' qu', + '衏' => ' yuàn', + '衎' => ' kàn', + '衍' => ' yǎn', + '行' => ' xíng', + '衋' => ' xì', + '衊' => ' miè', + '衉' => ' kā', + '衟' => ' dào', + '衡' => ' héng', + '衇' => ' mài', + '衮' => ' gǔn', + '衸' => ' jiè', + '衷' => ' zhōng', + '衶' => ' zhòng', + '衵' => ' yì', + '衴' => ' dǎn', + '衳' => ' zhōng', + '衲' => ' nà', + '衱' => ' jié', + '衰' => ' shuāi', + '衯' => ' fēn', + '衭' => ' fū', + '衢' => ' qú', + '衬' => ' chèn', + '衫' => ' shān', + '衪' => ' yí', + '衩' => ' chǎ', + '表' => ' biǎo', + '衧' => ' yú', + '衦' => ' gǎn', + '补' => ' bǔ', + '衤' => ' yī', + '衣' => ' yī', + '衈' => ' èr', + '衆' => ' zhòng', + '衺' => ' xié', + '蠠' => ' mǐn', + '蠪' => ' lóng', + '蠩' => ' zhū', + '蠨' => ' xiāo', + '蠧' => ' dù', + '蠦' => ' lú', + '蠥' => ' niè', + '蠤' => ' qiū', + '蠣' => ' lì', + '蠢' => ' chǔn', + '蠡' => ' lí', + '蠟' => ' là', + '蠬' => ' lóng', + '蠞' => ' jié', + '蠝' => ' léi', + '蠜' => ' fán', + '蠛' => ' miè', + '蠚' => ' hē', + '蠙' => ' pín', + '蠘' => ' jié', + '蠗' => ' zhuó', + '蠖' => ' huò', + '蠕' => ' rú', + '蠫' => ' lì', + '蠭' => ' fēng', + '衅' => ' xìn', + '蠺' => ' cán', + '衄' => ' nǜ', + '衃' => ' pēi', + '衂' => ' niù', + '衁' => ' huāng', + '血' => ' xuè', + '蠿' => ' zhuō', + '蠾' => ' zhú', + '蠽' => ' jié', + '蠼' => ' qú', + '蠻' => ' mán', + '蠹' => ' dù', + '蠮' => ' yē', + '蠸' => ' quán', + '蠷' => ' qú', + '蠶' => ' cán', + '蠵' => ' xī', + '蠴' => ' shu', + '蠳' => ' yīng', + '蠲' => ' juān', + '蠱' => ' gǔ', + '蠰' => ' náng', + '蠯' => ' bèng', + '衹' => ' zhǐ', + '衻' => ' rán', + '蠓' => ' měng', + '袼' => ' gē', + '裆' => ' dāng', + '装' => ' zhuāng', + '裄' => ' xing', + '裃' => ' ka', + '裂' => ' liè', + '裁' => ' cái', + '裀' => ' yīn', + '袿' => ' guī', + '袾' => ' zhū', + '袽' => ' rú', + '袻' => ' ér', + '裈' => ' kūn', + '袺' => ' jié', + '袹' => ' bó', + '袸' => ' jiàn', + '袷' => ' jiá', + '袶' => ' jiàng', + '袵' => ' rèn', + '袴' => ' kù', + '袳' => ' chǐ', + '袲' => ' chǐ', + '袱' => ' fú', + '裇' => ' xū', + '裉' => ' kèn', + '袯' => ' bó', + '裖' => ' zhěn', + '裠' => ' qún', + '裟' => ' shā', + '裞' => ' shuì', + '裝' => ' zhuāng', + '補' => ' bǔ', + '裛' => ' yì', + '裚' => ' jì', + '裙' => ' qún', + '裘' => ' qiú', + '裗' => ' liú', + '裕' => ' yù', + '裊' => ' niǎo', + '裔' => ' yì', + '裓' => ' gé', + '裒' => ' póu', + '裑' => ' shēn', + '裐' => ' juān', + '裏' => ' lǐ', + '裎' => ' chéng', + '裍' => ' kǔn', + '裌' => ' jiá', + '裋' => ' shù', + '袰' => ' bō', + '袮' => ' ni', + '衼' => ' zhī', + '袈' => ' jiā', + '袒' => ' tǎn', + '袑' => ' shào', + '袐' => ' bì', + '袏' => ' zuò', + '袎' => ' yào', + '袍' => ' páo', + '袌' => ' bào', + '袋' => ' dài', + '袊' => ' lǐng', + '袉' => ' tuó', + '袇' => ' rán', + '袔' => ' hè', + '袆' => ' huī', + '袅' => ' niǎo', + '袄' => ' ǎo', + '袃' => ' chài', + '袂' => ' mèi', + '袁' => ' yuán', + '袀' => ' jūn', + '衿' => ' jīn', + '衾' => ' qīn', + '衽' => ' rèn', + '袓' => ' jù', + '袕' => ' xué', + '袭' => ' xí', + '袢' => ' pàn', + '袬' => ' gǔn', + '被' => ' bèi', + '袪' => ' qū', + '袩' => ' zhé', + '袨' => ' xuàn', + '袧' => ' gōu', + '袦' => ' nà', + '袥' => ' tuō', + '袤' => ' mào', + '袣' => ' yì', + '袡' => ' rán', + '袖' => ' xiù', + '袠' => ' zhì', + '袟' => ' zhì', + '袞' => ' gǔn', + '袝' => ' fù', + '袜' => ' wà', + '袛' => ' dī', + '袚' => ' bō', + '袙' => ' pà', + '袘' => ' yí', + '袗' => ' zhěn', + '蠔' => ' háo', + '蠒' => ' jiǎn', + '蝃' => ' dì', + '螃' => ' páng', + '融' => ' róng', + '螌' => ' bān', + '螋' => ' sōu', + '螊' => ' lián', + '螉' => ' wēng', + '螈' => ' yuán', + '螇' => ' xī', + '螆' => ' cì', + '螅' => ' xī', + '螄' => ' sī', + '螂' => ' láng', + '螏' => ' jí', + '螁' => ' ban', + '螀' => ' jiāng', + '蝿' => ' yíng', + '蝾' => ' róng', + '蝽' => ' chūn', + '蝼' => ' lóu', + '蝻' => ' nǎn', + '蝺' => ' qǔ', + '蝹' => ' yūn', + '蝸' => ' wō', + '螎' => ' róng', + '螐' => ' wū', + '蝶' => ' dié', + '螝' => ' guī', + '螧' => ' qi', + '螦' => ' sao', + '螥' => ' cāng', + '螤' => ' zhōng', + '螣' => ' tè', + '螢' => ' yíng', + '螡' => ' wén', + '螠' => ' yì', + '螟' => ' míng', + '螞' => ' mǎ', + '螜' => ' hú', + '螑' => ' xiù', + '螛' => ' hé', + '螚' => ' nài', + '螙' => ' dù', + '螘' => ' yǐ', + '螗' => ' táng', + '螖' => ' huá', + '螕' => ' bī', + '螔' => ' yí', + '螓' => ' qín', + '螒' => ' hàn', + '蝷' => ' lì', + '蝵' => ' qiū', + '螩' => ' tiao', + '蝏' => ' tíng', + '蝙' => ' biān', + '蝘' => ' yǎn', + '蝗' => ' huáng', + '蝖' => ' xuān', + '蝕' => ' shí', + '蝔' => ' jiē', + '蝓' => ' yú', + '蝒' => ' mián', + '蝑' => ' xū', + '蝐' => ' mào', + '蝎' => ' xiē', + '蝛' => ' wēi', + '蝍' => ' jié', + '蝌' => ' kē', + '蝋' => ' là', + '蝊' => ' dìng', + '蝉' => ' chán', + '蝈' => ' guō', + '蝇' => ' yíng', + '蝆' => ' yǎng', + '蝅' => ' cán', + '蝄' => ' wǎng', + '蝚' => ' róu', + '蝜' => ' fù', + '蝴' => ' hú', + '蝩' => ' chóng', + '蝳' => ' dú', + '蝲' => ' là', + '蝱' => ' méng', + '蝰' => ' kuí', + '蝯' => ' yuán', + '蝮' => ' fù', + '蝭' => ' tí', + '蝬' => ' zōng', + '蝫' => ' zhū', + '蝪' => ' tāng', + '蝨' => ' shī', + '蝝' => ' yuán', + '蝧' => ' yīng', + '蝦' => ' xiā', + '蝥' => ' máo', + '蝤' => ' qiú', + '蝣' => ' yóu', + '蝢' => ' xié', + '蝡' => ' rú', + '蝠' => ' fú', + '蝟' => ' wèi', + '蝞' => ' mèi', + '螨' => ' mǎn', + '螪' => ' shāng', + '蠑' => ' róng', + '蟬' => ' chán', + '蟶' => ' chēng', + '蟵' => ' chu', + '蟴' => ' sī', + '蟳' => ' xún', + '蟲' => ' chóng', + '蟱' => ' wú', + '蟰' => ' xiāo', + '蟯' => ' náo', + '蟮' => ' shàn', + '蟭' => ' jiāo', + '蟫' => ' yín', + '蟸' => ' lǐ', + '蟪' => ' huì', + '蟩' => ' jué', + '蟨' => ' jué', + '蟧' => ' láo', + '蟦' => ' féi', + '蟥' => ' huáng', + '蟤' => ' zhuān', + '蟣' => ' jǐ', + '蟢' => ' xǐ', + '蟡' => ' guǐ', + '蟷' => ' dāng', + '蟹' => ' xiè', + '蟟' => ' liáo', + '蠆' => ' chài', + '蠐' => ' qí', + '蠏' => ' xiè', + '蠎' => ' mǎng', + '蠍' => ' xiē', + '蠌' => ' zé', + '蠋' => ' zhú', + '蠊' => ' lián', + '蠉' => ' xuān', + '蠈' => ' zéi', + '蠇' => ' lì', + '蠅' => ' yíng', + '蟺' => ' shàn', + '蠄' => ' qín', + '蠃' => ' luǒ', + '蠂' => ' shè', + '蠁' => ' xiǎng', + '蠀' => ' cī', + '蟿' => ' qì', + '蟾' => ' chán', + '蟽' => ' dá', + '蟼' => ' jǐng', + '蟻' => ' yǐ', + '蟠' => ' pán', + '蟞' => ' biē', + '螫' => ' shì', + '螷' => ' pí', + '蟁' => ' wén', + '蟀' => ' shuài', + '螿' => ' jiāng', + '螾' => ' yǐn', + '螽' => ' zhōng', + '螼' => ' qǐn', + '螻' => ' lóu', + '螺' => ' luó', + '螹' => ' jiàn', + '螸' => ' yú', + '螶' => ' qú', + '蟃' => ' wàn', + '螵' => ' piāo', + '螴' => ' chén', + '螳' => ' táng', + '螲' => ' zhì', + '螱' => ' wèi', + '螰' => ' lù', + '螯' => ' áo', + '螮' => ' dì', + '螭' => ' chī', + '螬' => ' cáo', + '蟂' => ' xiāo', + '蟄' => ' zhé', + '蟝' => ' qú', + '蟒' => ' mǎng', + '蟜' => ' jiǎo', + '蟛' => ' péng', + '蟚' => ' péng', + '蟙' => ' zhí', + '蟘' => ' tè', + '蟗' => ' qiū', + '蟖' => ' sī', + '蟕' => ' zuī', + '蟔' => ' mò', + '蟓' => ' xiàng', + '蟑' => ' zhāng', + '蟅' => ' zhè', + '蟐' => ' chang', + '蟏' => ' xiāo', + '蟎' => ' mǎn', + '蟍' => ' lí', + '蟌' => ' cōng', + '蟋' => ' xī', + '蟊' => ' máo', + '蟉' => ' liú', + '蟈' => ' guō', + '蟇' => ' má', + '蟆' => ' má', + '谢' => ' xiè', + '谤' => ' bàng', + '銤' => ' mǐ', + '遶' => ' rào', + '邀' => ' yāo', + '避' => ' bì', + '遾' => ' shì', + '遽' => ' jù', + '遼' => ' liáo', + '遻' => ' wù', + '遺' => ' yí', + '遹' => ' yù', + '選' => ' xuǎn', + '遷' => ' qiān', + '遵' => ' zūn', + '邂' => ' xiè', + '遴' => ' lín', + '遳' => ' cuō', + '遲' => ' chí', + '遱' => ' lóu', + '遰' => ' dì', + '遯' => ' dùn', + '遮' => ' zhē', + '遭' => ' zāo', + '遬' => ' sù', + '遫' => ' chì', + '邁' => ' mài', + '邃' => ' suì', + '適' => ' shì', + '邐' => ' lǐ', + '邚' => ' rú', + '邙' => ' máng', + '邘' => ' yú', + '邗' => ' hán', + '邖' => ' shān', + '邕' => ' yōng', + '邔' => ' qǐ', + '邓' => ' dèng', + '邒' => ' tíng', + '邑' => ' yì', + '邏' => ' luó', + '還' => ' hái', + '邎' => ' yáo', + '邍' => ' yuán', + '邌' => ' lí', + '邋' => ' lā', + '邊' => ' biān', + '邉' => ' biān', + '邈' => ' miǎo', + '邇' => ' ěr', + '邆' => ' téng', + '邅' => ' zhān', + '遪' => ' cà', + '遨' => ' áo', + '邜' => ' wan', + '遂' => ' suì', + '遌' => ' è', + '運' => ' yùn', + '遊' => ' yóu', + '遉' => ' zhēn', + '遈' => ' shí', + '遇' => ' yù', + '遆' => ' tí', + '遅' => ' chí', + '遄' => ' chuán', + '遃' => ' yǎn', + '遁' => ' dùn', + '過' => ' guò', + '遀' => ' suí', + '逿' => ' dàng', + '逾' => ' yú', + '逽' => ' nuò', + '逼' => ' bī', + '逻' => ' luó', + '逺' => ' yuǎn', + '逹' => ' dá', + '逸' => ' yì', + '逷' => ' tì', + '遍' => ' biàn', + '遏' => ' è', + '遧' => ' zhāng', + '遜' => ' xùn', + '遦' => ' guàn', + '遥' => ' yáo', + '遤' => ' ma', + '遣' => ' qiǎn', + '遢' => ' tà', + '遡' => ' sù', + '遠' => ' yuǎn', + '遟' => ' chí', + '遞' => ' dì', + '遝' => ' tà', + '遛' => ' liú', + '遐' => ' xiá', + '遚' => ' chòu', + '遙' => ' yáo', + '遘' => ' gòu', + '遗' => ' yí', + '遖' => ' nan', + '違' => ' wéi', + '達' => ' dá', + '道' => ' dào', + '遒' => ' qiú', + '遑' => ' huáng', + '邛' => ' qióng', + '邝' => ' kuàng', + '逵' => ' kuí', + '郞' => ' láng', + '部' => ' bù', + '郧' => ' yún', + '郦' => ' lì', + '郥' => ' bèi', + '郤' => ' xì', + '郣' => ' bó', + '郢' => ' yǐng', + '郡' => ' jùn', + '郠' => ' gěng', + '郟' => ' jiá', + '郝' => ' hǎo', + '郪' => ' qī', + '郜' => ' gào', + '郛' => ' fú', + '郚' => ' wú', + '郙' => ' fǔ', + '郘' => ' lǘ', + '郗' => ' xī', + '郖' => ' dòu', + '郕' => ' chéng', + '郔' => ' yán', + '郓' => ' yùn', + '郩' => ' xiáo', + '郫' => ' pí', + '郑' => ' zhèng', + '郸' => ' dān', + '鄂' => ' è', + '鄁' => ' bèi', + '鄀' => ' ruò', + '郿' => ' méi', + '郾' => ' yǎn', + '都' => ' dōu', + '郼' => ' yī', + '郻' => ' qiāo', + '郺' => ' yōng', + '郹' => ' jú', + '郷' => ' xiāng', + '郬' => ' qīng', + '郶' => ' bù', + '郵' => ' yóu', + '郴' => ' chēn', + '郳' => ' ní', + '郲' => ' lái', + '郱' => ' píng', + '郰' => ' zōu', + '郯' => ' tán', + '郮' => ' zhōu', + '郭' => ' guō', + '郒' => ' lang', + '郐' => ' kuài', + '邞' => ' fū', + '邪' => ' xié', + '邴' => ' bǐng', + '邳' => ' pī', + '邲' => ' bì', + '邱' => ' qiū', + '邰' => ' tái', + '邯' => ' hán', + '邮' => ' yóu', + '邭' => ' jù', + '邬' => ' wū', + '邫' => ' bāng', + '邩' => ' huǒ', + '邶' => ' bèi', + '邨' => ' cūn', + '邧' => ' yuán', + '邦' => ' bāng', + '邥' => ' shěn', + '邤' => ' xīn', + '那' => ' nà', + '邢' => ' xíng', + '邡' => ' fāng', + '邠' => ' bīn', + '邟' => ' kàng', + '邵' => ' shào', + '邷' => ' wǎ', + '郏' => ' jiá', + '郄' => ' qiè', + '郎' => ' láng', + '郍' => ' nuó', + '郌' => ' guī', + '郋' => ' xí', + '郊' => ' jiāo', + '郉' => ' xíng', + '郈' => ' hòu', + '郇' => ' huán', + '郆' => ' jí', + '郅' => ' zhì', + '郃' => ' hé', + '邸' => ' dǐ', + '郂' => ' gāi', + '郁' => ' yù', + '郀' => ' kū', + '邿' => ' shī', + '邾' => ' zhū', + '邽' => ' guī', + '邼' => ' kuāng', + '邻' => ' lín', + '邺' => ' yè', + '邹' => ' zōu', + '逶' => ' wēi', + '逴' => ' chuō', + '鄄' => ' juàn', + '辦' => ' bàn', + '辰' => ' chén', + '辯' => ' biàn', + '辮' => ' biàn', + '辭' => ' cí', + '辬' => ' bān', + '辫' => ' biàn', + '辪' => ' xuē', + '辩' => ' biàn', + '辨' => ' biàn', + '辧' => ' biàn', + '辥' => ' xuē', + '農' => ' nóng', + '辤' => ' cí', + '辣' => ' là', + '辢' => ' là', + '辡' => ' biàn', + '辠' => ' zuì', + '辟' => ' pì', + '辞' => ' cí', + '辝' => ' cí', + '辜' => ' gū', + '辛' => ' xīn', + '辱' => ' rǔ', + '辳' => ' nóng', + '辙' => ' zhé', + '迀' => ' gān', + '迊' => ' zā', + '迉' => ' qī', + '迈' => ' mài', + '过' => ' guò', + '迆' => ' yí', + '迅' => ' xùn', + '迄' => ' qì', + '迃' => ' yū', + '迂' => ' yū', + '迁' => ' qiān', + '辿' => ' chān', + '辴' => ' chǎn', + '达' => ' dá', + '辽' => ' liáo', + '込' => ' ru', + '辻' => ' shí', + '辺' => ' biān', + '边' => ' biān', + '辸' => ' réng', + '辷' => ' yi', + '辶' => ' chuò', + '辵' => ' chuò', + '辚' => ' lín', + '辘' => ' lù', + '迌' => ' tù', + '轲' => ' kē', + '轼' => ' shì', + '轻' => ' qīng', + '轺' => ' yáo', + '轹' => ' lì', + '轸' => ' zhěn', + '轷' => ' hū', + '轶' => ' yì', + '轵' => ' zhǐ', + '轴' => ' zhóu', + '轳' => ' lú', + '轱' => ' gū', + '轾' => ' zhì', + '轰' => ' hōng', + '软' => ' ruǎn', + '轮' => ' lún', + '轭' => ' è', + '转' => ' zhuǎn', + '轫' => ' rèn', + '轪' => ' dài', + '轩' => ' xuān', + '轨' => ' guǐ', + '轧' => ' yà', + '载' => ' zài', + '轿' => ' jiào', + '辗' => ' niǎn', + '辌' => ' liáng', + '辖' => ' xiá', + '辕' => ' yuán', + '辔' => ' pèi', + '输' => ' shū', + '辒' => ' wēn', + '辑' => ' jí', + '辐' => ' fú', + '辏' => ' còu', + '辎' => ' zī', + '辍' => ' chuò', + '辋' => ' wǎng', + '辀' => ' zhōu', + '辊' => ' gǔn', + '辉' => ' huī', + '辈' => ' bèi', + '辇' => ' niǎn', + '辆' => ' liàng', + '辅' => ' fǔ', + '辄' => ' zhé', + '较' => ' jiào', + '辂' => ' lù', + '辁' => ' quán', + '迋' => ' wàng', + '迍' => ' zhūn', + '逳' => ' yù', + '逎' => ' qiú', + '逘' => ' yǐ', + '逗' => ' dòu', + '逖' => ' tì', + '逕' => ' jìng', + '途' => ' tú', + '逓' => ' dì', + '递' => ' dì', + '逑' => ' qiú', + '逐' => ' zhú', + '透' => ' tòu', + '逍' => ' xiāo', + '通' => ' tōng', + '逌' => ' yōu', + '逋' => ' bū', + '逊' => ' xùn', + '选' => ' xuǎn', + '逈' => ' jiǒng', + '逇' => ' dùn', + '逆' => ' nì', + '逅' => ' hòu', + '逄' => ' páng', + '逃' => ' táo', + '這' => ' zhè', + '逛' => ' guàng', + '送' => ' sòng', + '逨' => ' lái', + '進' => ' jìn', + '週' => ' zhōu', + '逰' => ' yóu', + '逯' => ' lù', + '逮' => ' dǎi', + '逭' => ' huàn', + '逬' => ' bèng', + '逫' => ' jué', + '逪' => ' cuò', + '逩' => ' bèn', + '逧' => ' gu', + '逜' => ' wù', + '逦' => ' lǐ', + '逥' => ' huí', + '逤' => ' suò', + '連' => ' lián', + '逢' => ' féng', + '逡' => ' qūn', + '造' => ' zào', + '速' => ' sù', + '逞' => ' chěng', + '逝' => ' shì', + '适' => ' shì', + '退' => ' tuì', + '迎' => ' yíng', + '迚' => ' zhong', + '迤' => ' yí', + '迣' => ' zhì', + '迢' => ' tiáo', + '迡' => ' nì', + '迠' => ' chè', + '迟' => ' chí', + '连' => ' lián', + '违' => ' wéi', + '远' => ' yuǎn', + '进' => ' jìn', + '这' => ' zhè', + '迦' => ' jiā', + '还' => ' hái', + '迗' => ' é', + '迖' => ' dá', + '迕' => ' wù', + '返' => ' fǎn', + '迓' => ' yà', + '迒' => ' háng', + '近' => ' jìn', + '运' => ' yùn', + '迏' => ' tì', + '迥' => ' jiǒng', + '迧' => ' chén', + '迿' => ' xùn', + '迴' => ' huí', + '迾' => ' liè', + '追' => ' zhuī', + '迼' => ' jié', + '迻' => ' yí', + '迺' => ' nǎi', + '迹' => ' jī', + '迸' => ' bèng', + '迷' => ' mí', + '迶' => ' yòu', + '迵' => ' dòng', + '迳' => ' jìng', + '迨' => ' dài', + '迲' => ' qu', + '迱' => ' tuó', + '述' => ' shù', + '迯' => ' táo', + '迮' => ' zé', + '迭' => ' dié', + '迬' => ' zhù', + '迫' => ' pò', + '迪' => ' dí', + '迩' => ' ěr', + '鄃' => ' shū', + '鄅' => ' yǔ', + '轥' => ' lìn', + '鈗' => ' yǔn', + '鈡' => ' zhōng', + '鈠' => ' yì', + '鈟' => ' shī', + '鈞' => ' jūn', + '鈝' => ' yín', + '鈜' => ' hóng', + '鈛' => ' guō', + '鈚' => ' pī', + '鈙' => ' qín', + '鈘' => ' jǐ', + '鈖' => ' fēn', + '鈣' => ' gài', + '鈕' => ' niǔ', + '鈔' => ' chāo', + '鈓' => ' rén', + '鈒' => ' sà', + '鈑' => ' bǎn', + '鈐' => ' qián', + '鈏' => ' yǐn', + '鈎' => ' gōu', + '鈍' => ' dùn', + '鈌' => ' jué', + '鈢' => ' xǐ', + '鈤' => ' rì', + '鈊' => ' xīn', + '鈱' => ' mín', + '鈻' => ' sì', + '鈺' => ' yù', + '鈹' => ' pī', + '鈸' => ' bó', + '鈷' => ' gǔ', + '鈶' => ' sì', + '鈵' => ' bǐng', + '鈴' => ' líng', + '鈳' => ' kē', + '鈲' => ' gū', + '鈰' => ' shì', + '鈥' => ' huǒ', + '鈯' => ' tú', + '鈮' => ' nǐ', + '鈭' => ' zī', + '鈬' => ' duó', + '鈫' => ' wen', + '鈪' => ' è', + '鈩' => ' lu', + '鈨' => ' yuan', + '鈧' => ' kàng', + '鈦' => ' tài', + '鈋' => ' é', + '鈉' => ' nà', + '鈽' => ' bū', + '釣' => ' diào', + '釭' => ' gāng', + '釬' => ' hàn', + '釫' => ' huá', + '釪' => ' huá', + '釩' => ' fǎn', + '釨' => ' zǐ', + '釧' => ' chuàn', + '釦' => ' kòu', + '釥' => ' qiǎo', + '釤' => ' shàn', + '釢' => ' nǎi', + '釯' => ' máng', + '釡' => ' fǔ', + '釠' => ' luàn', + '釟' => ' bā', + '釞' => ' zhí', + '針' => ' zhēn', + '釜' => ' fǔ', + '釛' => ' bā', + '釚' => ' qiú', + '釙' => ' pò', + '釘' => ' dīng', + '釮' => ' qí', + '釰' => ' rì', + '鈈' => ' pī', + '釽' => ' pì', + '鈇' => ' fū', + '鈆' => ' qiān', + '鈅' => ' yuè', + '鈄' => ' dǒu', + '鈃' => ' xíng', + '鈂' => ' chén', + '鈁' => ' fāng', + '鈀' => ' bǎ', + '釿' => ' jīn', + '釾' => ' yé', + '釼' => ' jiàn', + '釱' => ' dì', + '釻' => ' qiu', + '釺' => ' qiān', + '釹' => ' nǚ', + '釸' => ' xī', + '釷' => ' tǔ', + '釶' => ' shī', + '釵' => ' chāi', + '釴' => ' yì', + '釳' => ' xì', + '釲' => ' sì', + '鈼' => ' zuó', + '鈾' => ' yóu', + '釖' => ' dāo', + '鉿' => ' jiā', + '銉' => ' yù', + '銈' => ' jī', + '銇' => ' lèi', + '銆' => ' mò', + '銅' => ' tóng', + '銄' => ' xiǎng', + '銃' => ' chòng', + '銂' => ' zhōu', + '銁' => ' jūn', + '銀' => ' yín', + '鉾' => ' móu', + '銋' => ' rén', + '鉽' => ' shì', + '鉼' => ' bǐng', + '鉻' => ' luò', + '鉺' => ' èr', + '鉹' => ' chǐ', + '鉸' => ' jiǎo', + '鉷' => ' hóng', + '鉶' => ' xíng', + '鉵' => ' tóng', + '鉴' => ' jiàn', + '銊' => ' xù', + '銌' => ' zùn', + '鉲' => ' kǎ', + '銙' => ' kuǎ', + '銣' => ' rú', + '銢' => ' pǐ', + '銡' => ' jí', + '銠' => ' lǎo', + '銟' => ' chā', + '銞' => ' jūn', + '銝' => ' xiū', + '銜' => ' xián', + '銛' => ' xiān', + '銚' => ' yáo', + '銘' => ' míng', + '銍' => ' zhì', + '銗' => ' xiàng', + '銖' => ' zhū', + '銕' => ' tiě', + '銔' => ' pī', + '銓' => ' quán', + '銒' => ' xíng', + '銑' => ' xiǎn', + '銐' => ' chì', + '銏' => ' shàn', + '銎' => ' qióng', + '鉳' => ' běi', + '鉱' => ' kuàng', + '鈿' => ' tián', + '鉋' => ' bào', + '鉕' => ' pō', + '鉔' => ' zā', + '鉓' => ' chì', + '鉒' => ' zhù', + '鉑' => ' bó', + '鉐' => ' shí', + '鉏' => ' chú', + '鉎' => ' shēng', + '鉍' => ' bì', + '鉌' => ' hé', + '鉊' => ' zhāo', + '鉗' => ' qián', + '鉉' => ' xuàn', + '鉈' => ' shī', + '鉇' => ' shī', + '鉆' => ' chān', + '鉅' => ' jù', + '鉄' => ' zhí', + '鉃' => ' shì', + '鉂' => ' shǐ', + '鉁' => ' zhēn', + '鉀' => ' jiǎ', + '鉖' => ' tóng', + '鉘' => ' fú', + '鉰' => ' sī', + '鉥' => ' shù', + '鉯' => ' yǐ', + '鉮' => ' huán', + '鉭' => ' tǎn', + '鉬' => ' mù', + '鉫' => ' jiā', + '鉪' => ' dì', + '鉩' => ' xǐ', + '鉨' => ' xǐ', + '鉧' => ' mǔ', + '鉦' => ' zhēng', + '鉤' => ' gōu', + '鉙' => ' zhǎi', + '鉣' => ' jié', + '鉢' => ' bō', + '鉡' => ' bàn', + '鉠' => ' yāng', + '鉟' => ' pī', + '鉞' => ' yuè', + '鉝' => ' lì', + '鉜' => ' fú', + '鉛' => ' qiān', + '鉚' => ' liǔ', + '釗' => ' zhāo', + '釕' => ' liǎo', + '鄆' => ' yùn', + '酆' => ' fēng', + '酐' => ' gān', + '酏' => ' yǐ', + '酎' => ' zhòu', + '配' => ' pèi', + '酌' => ' zhuó', + '酋' => ' qiú', + '酊' => ' dīng', + '酉' => ' yǒu', + '酈' => ' lì', + '酇' => ' zàn', + '酅' => ' xī', + '酒' => ' jiǔ', + '酄' => ' huān', + '酃' => ' líng', + '酂' => ' cuó', + '酁' => ' chán', + '酀' => ' yàn', + '鄿' => ' jī', + '鄾' => ' yōu', + '鄽' => ' chán', + '鄼' => ' zàn', + '鄻' => ' liǎn', + '酑' => ' yú', + '酓' => ' yǎn', + '鄹' => ' zōu', + '酠' => ' qiǎ', + '酪' => ' lào', + '酩' => ' mǐng', + '酨' => ' zài', + '酧' => ' chóu', + '酦' => ' fā', + '酥' => ' sū', + '酤' => ' gū', + '酣' => ' hān', + '酢' => ' cù', + '酡' => ' tuó', + '酟' => ' tiān', + '酔' => ' zuì', + '酞' => ' tài', + '酝' => ' yùn', + '酜' => ' fu', + '酛' => ' yuan', + '酚' => ' fēn', + '酙' => ' zhēn', + '酘' => ' dòu', + '酗' => ' xù', + '酖' => ' zhèn', + '酕' => ' máo', + '鄺' => ' kuàng', + '鄸' => ' méng', + '酬' => ' chóu', + '鄒' => ' zōu', + '鄜' => ' fū', + '鄛' => ' cháo', + '鄚' => ' mào', + '鄙' => ' bǐ', + '鄘' => ' yōng', + '鄗' => ' hào', + '鄖' => ' yún', + '鄕' => ' xiāng', + '鄔' => ' wū', + '鄓' => ' yè', + '鄑' => ' zī', + '鄞' => ' yín', + '鄐' => ' chù', + '鄏' => ' rǔ', + '鄎' => ' xī', + '鄍' => ' míng', + '鄌' => ' táng', + '鄋' => ' sōu', + '鄊' => ' xiāng', + '鄉' => ' xiāng', + '鄈' => ' kuí', + '鄇' => ' hóu', + '鄝' => ' liǎo', + '鄟' => ' zhuān', + '鄷' => ' fēng', + '鄬' => ' wéi', + '鄶' => ' kuài', + '鄵' => ' cào', + '鄴' => ' yè', + '鄳' => ' méng', + '鄲' => ' dān', + '鄱' => ' pó', + '鄰' => ' lín', + '鄯' => ' shàn', + '鄮' => ' mào', + '鄭' => ' zhèng', + '鄫' => ' zēng', + '鄠' => ' hù', + '鄪' => ' bì', + '鄩' => ' xún', + '鄨' => ' bì', + '鄧' => ' dèng', + '鄦' => ' xǔ', + '鄥' => ' qiāo', + '鄤' => ' màn', + '鄣' => ' zhāng', + '鄢' => ' yān', + '鄡' => ' qiāo', + '酫' => ' chuò', + '酭' => ' yòu', + '釔' => ' yǐ', + '醯' => ' xī', + '醹' => ' rú', + '醸' => ' niàng', + '醷' => ' yì', + '醶' => ' yàn', + '醵' => ' jù', + '醴' => ' lǐ', + '醳' => ' yì', + '醲' => ' nóng', + '醱' => ' fā', + '醰' => ' tán', + '醮' => ' jiào', + '醻' => ' chóu', + '醭' => ' bú', + '醬' => ' jiàng', + '醫' => ' yī', + '醪' => ' láo', + '醩' => ' zāo', + '醨' => ' lí', + '醧' => ' yù', + '醦' => ' chěn', + '醥' => ' piǎo', + '醤' => ' jiàng', + '醺' => ' xūn', + '醼' => ' yàn', + '醢' => ' hǎi', + '釉' => ' yòu', + '釓' => ' qiú', + '釒' => ' jīn', + '金' => ' jīn', + '釐' => ' lí', + '量' => ' liàng', + '野' => ' yě', + '重' => ' zhòng', + '里' => ' lǐ', + '釋' => ' shì', + '释' => ' shì', + '釈' => ' shì', + '醽' => ' líng', + '采' => ' cǎi', + '釆' => ' biàn', + '釅' => ' yàn', + '釄' => ' mí', + '釃' => ' shāi', + '釂' => ' jiào', + '釁' => ' xìn', + '釀' => ' niàng', + '醿' => ' mí', + '醾' => ' mí', + '醣' => ' táng', + '醡' => ' zhà', + '酮' => ' tóng', + '酺' => ' pú', + '醄' => ' táo', + '醃' => ' yān', + '醂' => ' lǎn', + '醁' => ' lù', + '醀' => ' wéi', + '酿' => ' niàng', + '酾' => ' shāi', + '酽' => ' yàn', + '酼' => ' hǎi', + '酻' => ' zuì', + '酹' => ' lèi', + '醆' => ' zhǎn', + '酸' => ' suān', + '酷' => ' kù', + '酶' => ' méi', + '酵' => ' jiào', + '酴' => ' tú', + '酳' => ' yìn', + '酲' => ' chéng', + '酱' => ' jiàng', + '酰' => ' xiān', + '酯' => ' zhǐ', + '醅' => ' pēi', + '醇' => ' chún', + '醠' => ' àng', + '醕' => ' chún', + '醟' => ' yòng', + '醞' => ' yùn', + '醝' => ' cuō', + '醜' => ' chǒu', + '醛' => ' quán', + '醚' => ' mí', + '醙' => ' sōu', + '醘' => ' kē', + '醗' => ' pò', + '醖' => ' yùn', + '醔' => ' qiú', + '醈' => ' tán', + '醓' => ' tǎn', + '醒' => ' xǐng', + '醑' => ' xǔ', + '醐' => ' hú', + '醏' => ' dū', + '醎' => ' xián', + '醍' => ' tí', + '醌' => ' kūn', + '醋' => ' cù', + '醊' => ' zhuì', + '醉' => ' zuì', + '车' => ' chē', + '轤' => ' lú', + '谥' => ' shì', + '贵' => ' guì', + '贿' => ' huì', + '贾' => ' jiǎ', + '贽' => ' zhì', + '贼' => ' zéi', + '贻' => ' yí', + '贺' => ' hè', + '费' => ' fèi', + '贸' => ' mào', + '贷' => ' dài', + '贶' => ' kuàng', + '贴' => ' tiē', + '赁' => ' lìn', + '贳' => ' shì', + '贲' => ' bēn', + '贱' => ' jiàn', + '贰' => ' èr', + '贯' => ' guàn', + '贮' => ' zhù', + '购' => ' gòu', + '贬' => ' biǎn', + '贫' => ' pín', + '贪' => ' tān', + '赀' => ' zī', + '赂' => ' lù', + '质' => ' zhì', + '赏' => ' shǎng', + '赙' => ' fù', + '赘' => ' zhuì', + '赗' => ' fèng', + '赖' => ' lài', + '赕' => ' dǎn', + '赔' => ' péi', + '赓' => ' gēng', + '赒' => ' zhōu', + '赑' => ' bì', + '赐' => ' cì', + '赎' => ' shú', + '赃' => ' zāng', + '赍' => ' jī', + '赌' => ' dǔ', + '赋' => ' fù', + '赊' => ' shē', + '赉' => ' lài', + '赈' => ' zhèn', + '赇' => ' qiú', + '赆' => ' jìn', + '赅' => ' gāi', + '资' => ' zī', + '贩' => ' fàn', + '货' => ' huò', + '赛' => ' sài', + '贁' => ' bài', + '贋' => ' yàn', + '贊' => ' zàn', + '贉' => ' dàn', + '贈' => ' zèng', + '贇' => ' yūn', + '贆' => ' biāo', + '贅' => ' zhuì', + '贄' => ' zhì', + '贃' => ' wàn', + '贂' => ' chěn', + '贀' => ' yì', + '贍' => ' shàn', + '賿' => ' liáo', + '賾' => ' zé', + '賽' => ' sài', + '購' => ' gòu', + '賻' => ' fù', + '賺' => ' zhuàn', + '賹' => ' yì', + '賸' => ' shèng', + '賷' => ' jī', + '賶' => ' càng', + '贌' => ' pu', + '贎' => ' wàn', + '账' => ' zhàng', + '贛' => ' gàn', + '败' => ' bài', + '贤' => ' xián', + '责' => ' zé', + '财' => ' cái', + '贡' => ' gòng', + '贠' => ' yuán', + '负' => ' fù', + '贞' => ' zhēn', + '贝' => ' bèi', + '贜' => ' zāng', + '贚' => ' lòng', + '贏' => ' yíng', + '贙' => ' xuàn', + '贘' => ' shǎng', + '贗' => ' yàn', + '贖' => ' shú', + '贕' => ' dú', + '贔' => ' bì', + '贓' => ' zāng', + '贒' => ' xián', + '贑' => ' gàn', + '贐' => ' jìn', + '赚' => ' zhuàn', + '赜' => ' zé', + '賴' => ' lài', + '趝' => ' jiàn', + '趧' => ' tí', + '趦' => ' zī', + '趥' => ' qiū', + '趤' => ' dàng', + '趣' => ' qù', + '趢' => ' lù', + '趡' => ' cuǐ', + '趠' => ' chuò', + '趟' => ' tàng', + '趞' => ' què', + '趜' => ' jú', + '趩' => ' chì', + '趛' => ' yǐn', + '趚' => ' sù', + '趙' => ' zhào', + '趘' => ' xí', + '趗' => ' cù', + '趖' => ' suō', + '趕' => ' gǎn', + '趔' => ' liè', + '趓' => ' duǒ', + '趒' => ' tiáo', + '趨' => ' qū', + '趪' => ' huáng', + '趐' => ' xuè', + '趷' => ' kē', + '跁' => ' bà', + '跀' => ' yuè', + '趿' => ' tā', + '趾' => ' zhǐ', + '趽' => ' fàng', + '趼' => ' jiǎn', + '趻' => ' chěn', + '趺' => ' fū', + '趹' => ' jué', + '趸' => ' dǔn', + '趶' => ' kù', + '趫' => ' qiáo', + '趵' => ' bào', + '趴' => ' pā', + '足' => ' zú', + '趲' => ' zǎn', + '趱' => ' zǎn', + '趰' => ' ěr', + '趯' => ' tì', + '趮' => ' zào', + '趭' => ' jiào', + '趬' => ' qiāo', + '趑' => ' zī', + '趏' => ' guā', + '赝' => ' yàn', + '赩' => ' xì', + '赳' => ' jiū', + '赲' => ' lì', + '赱' => ' zǒu', + '走' => ' zǒu', + '赯' => ' táng', + '赮' => ' xiá', + '赭' => ' zhě', + '赬' => ' chēng', + '赫' => ' hè', + '赪' => ' chēng', + '赨' => ' tóng', + '赵' => ' zhào', + '赧' => ' nǎn', + '赦' => ' shè', + '赥' => ' xī', + '赤' => ' chì', + '赣' => ' gàn', + '赢' => ' yíng', + '赡' => ' shàn', + '赠' => ' zèng', + '赟' => ' yūn', + '赞' => ' zàn', + '赴' => ' fù', + '赶' => ' gǎn', + '趎' => ' chú', + '趃' => ' dié', + '趍' => ' chí', + '趌' => ' jí', + '趋' => ' qū', + '越' => ' yuè', + '趉' => ' jué', + '趈' => ' zhān', + '趇' => ' xì', + '趆' => ' dī', + '超' => ' chāo', + '趄' => ' jū', + '趂' => ' chèn', + '起' => ' qǐ', + '趁' => ' chèn', + '趀' => ' cī', + '赿' => ' chí', + '赾' => ' qǐn', + '赽' => ' jué', + '赼' => ' cī', + '赻' => ' xiǎn', + '赺' => ' yǐn', + '赹' => ' qióng', + '赸' => ' shàn', + '賵' => ' fèng', + '賳' => ' zāi', + '跃' => ' yuè', + '豥' => ' gāi', + '豯' => ' xī', + '豮' => ' fén', + '豭' => ' jiā', + '豬' => ' zhū', + '豫' => ' yù', + '豪' => ' háo', + '豩' => ' bīn', + '豨' => ' xī', + '豧' => ' fū', + '豦' => ' jù', + '豤' => ' kěn', + '豱' => ' wēn', + '豣' => ' jiān', + '豢' => ' huàn', + '象' => ' xiàng', + '豠' => ' chú', + '豟' => ' è', + '豞' => ' hòu', + '豝' => ' bā', + '豜' => ' jiān', + '豛' => ' yì', + '豚' => ' tún', + '豰' => ' bó', + '豲' => ' huán', + '豘' => ' tún', + '豿' => ' gǒu', + '貉' => ' háo', + '貈' => ' hé', + '貇' => ' kūn', + '貆' => ' huán', + '貅' => ' xiū', + '貄' => ' sì', + '貃' => ' mò', + '貂' => ' diāo', + '貁' => ' yòu', + '貀' => ' nà', + '豾' => ' pī', + '豳' => ' bīn', + '豽' => ' nà', + '豼' => ' pí', + '豻' => ' àn', + '豺' => ' chái', + '豹' => ' bào', + '豸' => ' zhì', + '豷' => ' yì', + '豶' => ' fén', + '豵' => ' zōng', + '豴' => ' dí', + '豙' => ' yì', + '豗' => ' huī', + '貋' => ' hàn', + '谱' => ' pǔ', + '谻' => ' jí', + '谺' => ' xiā', + '谹' => ' hóng', + '谸' => ' qiān', + '谷' => ' gǔ', + '谶' => ' chèn', + '谵' => ' zhān', + '谴' => ' qiǎn', + '谳' => ' yàn', + '谲' => ' jué', + '谰' => ' lán', + '谽' => ' hān', + '谯' => ' qiáo', + '谮' => ' zèn', + '谭' => ' tán', + '谬' => ' miù', + '谫' => ' jiǎn', + '谪' => ' zhé', + '谩' => ' mán', + '谨' => ' jǐn', + '谧' => ' mì', + '谦' => ' qiān', + '谼' => ' hóng', + '谾' => ' hōng', + '豖' => ' chù', + '豋' => ' dēng', + '豕' => ' shǐ', + '豔' => ' yàn', + '豓' => ' yàn', + '豒' => ' zhì', + '豑' => ' zhì', + '豐' => ' fēng', + '豏' => ' xiàn', + '豎' => ' shù', + '豍' => ' bī', + '豌' => ' wān', + '豊' => ' lǐ', + '谿' => ' xī', + '豉' => ' shì', + '豈' => ' qǐ', + '豇' => ' jiāng', + '豆' => ' dòu', + '豅' => ' lóng', + '豄' => ' dú', + '豃' => ' hǎn', + '豂' => ' liáo', + '豁' => ' huō', + '豀' => ' xī', + '貊' => ' mò', + '貌' => ' mào', + '賲' => ' bǎo', + '賍' => ' zāng', + '賗' => ' chuàn', + '賖' => ' shē', + '賕' => ' qiú', + '賔' => ' bīn', + '賓' => ' bīn', + '賒' => ' shē', + '賑' => ' zhèn', + '賐' => ' xùn', + '賏' => ' yīng', + '賎' => ' jiàn', + '賌' => ' gài', + '賙' => ' zhōu', + '賋' => ' jiǎo', + '賊' => ' zéi', + '賉' => ' xù', + '賈' => ' jiǎ', + '資' => ' zī', + '賆' => ' pián', + '賅' => ' gāi', + '賄' => ' huì', + '賃' => ' lìn', + '賂' => ' lù', + '賘' => ' zāng', + '賚' => ' lài', + '賀' => ' hè', + '賧' => ' tàn', + '賱' => ' yǔn', + '賰' => ' chǔn', + '賯' => ' xiōng', + '賮' => ' jìn', + '賭' => ' dǔ', + '賬' => ' zhàng', + '賫' => ' jī', + '質' => ' zhì', + '賩' => ' cóng', + '賨' => ' cóng', + '賦' => ' fù', + '賛' => ' zàn', + '賥' => ' suì', + '賤' => ' jiàn', + '賣' => ' mài', + '賢' => ' xián', + '賡' => ' gēng', + '賠' => ' péi', + '賟' => ' tiǎn', + '賞' => ' shǎng', + '賝' => ' chēn', + '賜' => ' cì', + '賁' => ' bì', + '貿' => ' mào', + '貍' => ' lí', + '貙' => ' chū', + '貣' => ' tè', + '貢' => ' gòng', + '財' => ' cái', + '負' => ' fù', + '貟' => ' yuán', + '貞' => ' zhēn', + '貝' => ' bèi', + '貜' => ' jué', + '貛' => ' huān', + '貚' => ' tán', + '貘' => ' mò', + '貥' => ' háng', + '貗' => ' jù', + '貖' => ' è', + '貕' => ' xī', + '貔' => ' pí', + '貓' => ' māo', + '貒' => ' tuān', + '貑' => ' jiā', + '貐' => ' yǔ', + '貏' => ' bǐ', + '貎' => ' ní', + '貤' => ' yí', + '貦' => ' wàn', + '貾' => ' chí', + '貳' => ' èr', + '貽' => ' yí', + '貼' => ' tiē', + '費' => ' fèi', + '貺' => ' kuàng', + '貹' => ' shèng', + '貸' => ' dài', + '買' => ' mǎi', + '貶' => ' biǎn', + '貵' => ' piǎn', + '貴' => ' guì', + '貲' => ' zī', + '貧' => ' pín', + '貱' => ' bì', + '貰' => ' shì', + '貯' => ' zhù', + '貮' => ' èr', + '貭' => ' zhí', + '責' => ' zé', + '貫' => ' guàn', + '貪' => ' tān', + '販' => ' fàn', + '貨' => ' huò', + '跂' => ' qí', + '跄' => ' qiāng', + '轣' => ' lì', + '軖' => ' kuáng', + '軠' => ' rèn', + '軟' => ' ruǎn', + '軞' => ' máo', + '軝' => ' qí', + '軜' => ' nà', + '軛' => ' è', + '軚' => ' dài', + '軙' => ' chén', + '軘' => ' tún', + '軗' => ' shū', + '軕' => ' shān', + '転' => ' zhuǎn', + '軔' => ' rèn', + '軓' => ' fàn', + '軒' => ' xuān', + '軑' => ' dài', + '軐' => ' xìn', + '軏' => ' yuè', + '軎' => ' wèi', + '軍' => ' jūn', + '軌' => ' guǐ', + '軋' => ' yà', + '軡' => ' qián', + '軣' => ' hōng', + '軉' => ' yù', + '軰' => ' bèi', + '軺' => ' yáo', + '軹' => ' zhǐ', + '軸' => ' zhóu', + '軷' => ' bá', + '軶' => ' è', + '軵' => ' rǒng', + '軴' => ' zhù', + '軳' => ' páo', + '軲' => ' gū', + '軱' => ' gū', + '軯' => ' pēng', + '軤' => ' hū', + '軮' => ' yǎng', + '軭' => ' kuāng', + '軬' => ' fàn', + '軫' => ' zhěn', + '軪' => ' āo', + '軩' => ' dài', + '軨' => ' líng', + '軧' => ' dǐ', + '軦' => ' kuàng', + '軥' => ' qú', + '車' => ' chē', + '軈' => ' ying', + '軼' => ' yì', + '躢' => ' tà', + '躬' => ' gōng', + '身' => ' shēn', + '躪' => ' lìn', + '躩' => ' jué', + '躨' => ' kuí', + '躧' => ' xǐ', + '躦' => ' cuó', + '躥' => ' cuān', + '躤' => ' jiè', + '躣' => ' qú', + '躡' => ' niè', + '躮' => ' fen', + '躠' => ' sǎ', + '躟' => ' ráng', + '躞' => ' xiè', + '躝' => ' lán', + '躜' => ' zuān', + '躛' => ' wèi', + '躚' => ' xiān', + '躙' => ' lìn', + '躘' => ' lóng', + '躗' => ' wèi', + '躭' => ' dān', + '躯' => ' qū', + '軇' => ' dào', + '躼' => ' lào', + '軆' => ' tǐ', + '軅' => ' yan', + '軄' => ' zhí', + '軃' => ' duǒ', + '軂' => ' lào', + '軁' => ' lóu', + '軀' => ' qū', + '躿' => ' kāng', + '躾' => ' mei', + '躽' => ' yǎn', + '躻' => ' kong', + '躰' => ' tǐ', + '躺' => ' tǎng', + '躹' => ' jú', + '躸' => ' jī', + '躷' => ' ǎi', + '躶' => ' luǒ', + '躵' => ' ren', + '躴' => ' láng', + '躳' => ' gōng', + '躲' => ' duǒ', + '躱' => ' duǒ', + '軻' => ' kē', + '軽' => ' zhì', + '躕' => ' chú', + '輾' => ' zhǎn', + '轈' => ' cháo', + '轇' => ' jiāo', + '轆' => ' lù', + '轅' => ' yuán', + '轄' => ' xiá', + '轃' => ' zhēn', + '轂' => ' gǔ', + '轁' => ' tāo', + '轀' => ' wēn', + '輿' => ' yú', + '輽' => ' bèn', + '轊' => ' wèi', + '輼' => ' wēn', + '輻' => ' fú', + '輺' => ' zī', + '輹' => ' fù', + '輸' => ' shū', + '輷' => ' hōng', + '輶' => ' yóu', + '輵' => ' gé', + '輴' => ' chūn', + '輳' => ' còu', + '轉' => ' zhuǎn', + '轋' => ' hún', + '輱' => ' xián', + '轘' => ' huán', + '轢' => ' lì', + '轡' => ' pèi', + '轠' => ' léi', + '轟' => ' hōng', + '轞' => ' jiàn', + '轝' => ' yù', + '轜' => ' ér', + '轛' => ' zhuì', + '轚' => ' jí', + '轙' => ' yǐ', + '轗' => ' kǎn', + '轌' => ' xue', + '轖' => ' sè', + '轕' => ' gé', + '轔' => ' lín', + '轓' => ' fān', + '轒' => ' fén', + '轑' => ' lǎo', + '轐' => ' bú', + '轏' => ' zhàn', + '轎' => ' jiào', + '轍' => ' zhé', + '輲' => ' chuán', + '輰' => ' yáng', + '軾' => ' shì', + '輊' => ' zhì', + '輔' => ' fǔ', + '輓' => ' wǎn', + '輒' => ' zhé', + '輑' => ' yǐn', + '輐' => ' wàn', + '輏' => ' yóu', + '輎' => ' shāo', + '輍' => ' yù', + '輌' => ' liàng', + '輋' => ' shē', + '載' => ' zài', + '輖' => ' zhōu', + '輈' => ' zhōu', + '輇' => ' quán', + '輆' => ' kǎi', + '輅' => ' hé', + '輄' => ' guāng', + '較' => ' jiào', + '輂' => ' jú', + '輁' => ' gǒng', + '輀' => ' ér', + '軿' => ' píng', + '輕' => ' qīng', + '輗' => ' ní', + '輯' => ' jí', + '輤' => ' qiàn', + '輮' => ' róu', + '輭' => ' ruǎn', + '輬' => ' liáng', + '輫' => ' pái', + '輪' => ' lún', + '輩' => ' bèi', + '輨' => ' guǎn', + '輧' => ' píng', + '輦' => ' niǎn', + '輥' => ' gǔn', + '輣' => ' péng', + '輘' => ' léng', + '輢' => ' yǐ', + '輡' => ' kǎn', + '輠' => ' guǒ', + '輟' => ' chuò', + '輞' => ' wǎng', + '輝' => ' huī', + '輜' => ' zī', + '輛' => ' liàng', + '輚' => ' zhàn', + '輙' => ' zhé', + '躖' => ' duàn', + '躔' => ' chán', + '跅' => ' tuò', + '踅' => ' xué', + '踏' => ' tà', + '踎' => ' móu', + '踍' => ' qiāo', + '踌' => ' chóu', + '踋' => ' jiǎo', + '踊' => ' yǒng', + '踉' => ' liáng', + '踈' => ' shū', + '踇' => ' mǔ', + '踆' => ' cūn', + '踄' => ' bù', + '踑' => ' qí', + '踃' => ' xiāo', + '踂' => ' niè', + '踁' => ' jìng', + '踀' => ' chù', + '跿' => ' tú', + '跾' => ' shū', + '跽' => ' jì', + '跼' => ' jú', + '跻' => ' jī', + '跺' => ' duò', + '踐' => ' jiàn', + '踒' => ' wō', + '跸' => ' bì', + '踟' => ' chí', + '踩' => ' cǎi', + '踨' => ' zōng', + '踧' => ' cù', + '踦' => ' yǐ', + '踥' => ' qiè', + '踤' => ' zú', + '踣' => ' bó', + '踢' => ' tī', + '踡' => ' quán', + '踠' => ' wǎn', + '踞' => ' jù', + '踓' => ' wěi', + '踝' => ' huái', + '踜' => ' lèng', + '踛' => ' lù', + '踚' => ' lún', + '踙' => ' jū', + '踘' => ' jū', + '踗' => ' niè', + '踖' => ' jí', + '踕' => ' jié', + '踔' => ' chuō', + '跹' => ' xiān', + '跷' => ' qiāo', + '踫' => ' pèng', + '跑' => ' pǎo', + '跛' => ' bǒ', + '跚' => ' shān', + '跙' => ' jù', + '跘' => ' pán', + '跗' => ' fū', + '跖' => ' zhí', + '跕' => ' diǎn', + '跔' => ' jū', + '跓' => ' zhù', + '跒' => ' qiǎ', + '跐' => ' cī', + '距' => ' jù', + '跏' => ' jiā', + '跎' => ' tuó', + '跍' => ' kū', + '跌' => ' diē', + '跋' => ' bá', + '跊' => ' mèi', + '跉' => ' líng', + '跈' => ' niǎn', + '跇' => ' yì', + '跆' => ' tái', + '跜' => ' ní', + '跞' => ' lì', + '跶' => ' tà', + '跫' => ' qióng', + '践' => ' jiàn', + '跴' => ' cǎi', + '跳' => ' tiào', + '跲' => ' jiá', + '跱' => ' zhì', + '跰' => ' pián', + '路' => ' lù', + '跮' => ' chì', + '跭' => ' xiáng', + '跬' => ' kuǐ', + '跪' => ' guì', + '跟' => ' gēn', + '跩' => ' zhuǎi', + '跨' => ' kuà', + '跧' => ' quán', + '跦' => ' zhū', + '跥' => ' duò', + '跤' => ' jiāo', + '跣' => ' xiǎn', + '跢' => ' duò', + '跡' => ' jī', + '跠' => ' yí', + '踪' => ' zōng', + '踬' => ' zhì', + '躓' => ' zhì', + '蹮' => ' xiān', + '蹸' => ' lìn', + '蹷' => ' jué', + '蹶' => ' jué', + '蹵' => ' cù', + '蹴' => ' cù', + '蹳' => ' bō', + '蹲' => ' dūn', + '蹱' => ' zhōng', + '蹰' => ' chú', + '蹯' => ' fán', + '蹭' => ' cèng', + '蹺' => ' qiāo', + '蹬' => ' dēng', + '蹫' => ' jú', + '蹪' => ' tuí', + '蹩' => ' bié', + '蹨' => ' niǎn', + '蹧' => ' zāo', + '蹦' => ' bèng', + '蹥' => ' lián', + '蹤' => ' zōng', + '蹣' => ' pán', + '蹹' => ' tá', + '蹻' => ' juē', + '蹡' => ' qiāng', + '躈' => ' qiào', + '躒' => ' lì', + '躑' => ' zhí', + '躐' => ' liè', + '躏' => ' lìn', + '躎' => ' niǎn', + '躍' => ' yuè', + '躌' => ' wǔ', + '躋' => ' jī', + '躊' => ' chóu', + '躉' => ' dǔn', + '躇' => ' chú', + '蹼' => ' pǔ', + '躆' => ' jù', + '躅' => ' zhú', + '躄' => ' bì', + '躃' => ' bì', + '躂' => ' dá', + '躁' => ' zào', + '躀' => ' kuàng', + '蹿' => ' cuān', + '蹾' => ' dūn', + '蹽' => ' liāo', + '蹢' => ' dí', + '蹠' => ' zhí', + '踭' => ' zhēng', + '踹' => ' chuài', + '蹃' => ' nuò', + '蹂' => ' róu', + '蹁' => ' pián', + '蹀' => ' dié', + '踿' => ' zú', + '踾' => ' fú', + '踽' => ' jǔ', + '踼' => ' táng', + '踻' => ' guā', + '踺' => ' jiàn', + '踸' => ' chěn', + '蹅' => ' chǎ', + '踷' => ' zhǎ', + '踶' => ' dì', + '踵' => ' zhǒng', + '踴' => ' yǒng', + '踳' => ' chuǎn', + '踲' => ' dùn', + '踱' => ' duó', + '踰' => ' yú', + '踯' => ' zhí', + '踮' => ' diǎn', + '蹄' => ' tí', + '蹆' => ' tuǐ', + '蹟' => ' jī', + '蹔' => ' zàn', + '蹞' => ' kuǐ', + '蹝' => ' xǐ', + '蹜' => ' sù', + '蹛' => ' dài', + '蹚' => ' tāng', + '蹙' => ' cù', + '蹘' => ' liáo', + '蹗' => ' lù', + '蹖' => ' chōng', + '蹕' => ' bì', + '蹓' => ' liū', + '蹇' => ' jiǎn', + '蹒' => ' pán', + '蹑' => ' niè', + '蹐' => ' jí', + '蹏' => ' tí', + '蹎' => ' diān', + '蹍' => ' niǎn', + '蹌' => ' qiāng', + '蹋' => ' tà', + '蹊' => ' qī', + '蹉' => ' cuō', + '蹈' => ' dǎo', + '殗' => ' yè', + '敛' => ' liǎn', + '殕' => ' fǒu', + '䠒' => ' hú', + '䠝' => ' xūn', + '䠜' => ' róng', + '䠛' => ' yáo', + '䠚' => ' wà', + '䠙' => ' páng', + '䠘' => ' pì', + '䠗' => ' qiù', + '䠕' => ' cāi', + '䠔' => ' suì', + '䠓' => ' qiū', + '䠑' => ' kuǐ', + '䠟' => ' dié', + '䠐' => ' qù', + '䠏' => ' jì', + '䠎' => ' wò', + '䠍' => ' xiā', + '䠋' => ' bì', + '䠊' => ' fèi', + '䠉' => ' huàn', + '䠈' => ' tú', + '䠇' => ' jué', + '䠆' => ' cháng', + '䠞' => ' cù', + '䠠' => ' chì', + '䠄' => ' tiǎn', + '䠭' => ' lái', + '䠷' => ' tiǎo', + '䠶' => ' shè', + '䠵' => ' fù', + '䠴' => ' zhěn', + '䠳' => ' chēn', + '䠲' => ' líng', + '䠱' => ' zhú', + '䠰' => ' quán', + '䠯' => ' yuè', + '䠮' => ' téng', + '䠬' => ' dèng', + '䠡' => ' cuó', + '䠫' => ' zòu', + '䠪' => ' duàn', + '䠩' => ' guì', + '䠨' => ' chàn', + '䠧' => ' chú', + '䠦' => ' zhè', + '䠥' => ' bié', + '䠤' => ' duǒ', + '䠣' => ' xuǎn', + '䠢' => ' mèng', + '䠅' => ' kǔn', + '䠃' => ' liǎng', + '䠹' => ' ái', + '䟘' => ' hàng', + '䟢' => ' chén', + '䟡' => ' zhī', + '䟠' => ' yuè', + '䟟' => ' cù', + '䟞' => ' chú', + '䟝' => ' tòu', + '䟜' => ' nà', + '䟛' => ' bó', + '䟚' => ' qí', + '䟙' => ' qiè', + '䟗' => ' shì', + '䟤' => ' bì', + '䟖' => ' tàng', + '䟕' => ' chà', + '䟔' => ' fù', + '䟓' => ' chēng', + '䟒' => ' quán', + '䟑' => ' yuè', + '䟐' => ' lì', + '䟏' => ' lì', + '䟍' => ' biān', + '䟌' => ' jí', + '䟣' => ' chù', + '䟥' => ' méng', + '䠂' => ' chú', + '䟵' => ' qiú', + '䠀' => ' tāng', + '䟿' => ' lù', + '䟾' => ' zhuó', + '䟼' => ' wǔ', + '䟻' => ' dù', + '䟺' => ' bèi', + '䟹' => ' liè', + '䟸' => ' kuí', + '䟷' => ' chì', + '䟶' => ' zuò', + '䟴' => ' zhèn', + '䟦' => ' bá', + '䟰' => ' jiǎn', + '䟯' => ' kuò', + '䟮' => ' fú', + '䟭' => ' tiáo', + '䟬' => ' qiù', + '䟫' => ' chēng', + '䟪' => ' fěng', + '䟩' => ' liě', + '䟨' => ' mín', + '䟧' => ' tián', + '䠸' => ' kuā', + '䠻' => ' qióng', + '䟊' => ' qú', + '䢀' => ' qì', + '䢌' => ' bó', + '䢋' => ' jì', + '䢊' => ' yóu', + '䢉' => ' nóng', + '䢈' => ' chén', + '䢇' => ' rǒng', + '䢅' => ' chén', + '䢄' => ' xǐ', + '䢃' => ' yì', + '䢁' => ' yuè', + '䡿' => ' líng', + '䢐' => ' cú', + '䡾' => ' niè', + '䡽' => ' zuān', + '䡼' => ' líng', + '䡻' => ' mín', + '䡺' => ' wèi', + '䡹' => ' zhì', + '䡷' => ' kài', + '䡶' => ' bèi', + '䡵' => ' suì', + '䡴' => ' chōng', + '䢍' => ' fǎng', + '䢑' => ' dǐ', + '䡲' => ' chán', + '䢠' => ' sòng', + '䢪' => ' bó', + '䢩' => ' yù', + '䢨' => ' cōng', + '䢧' => ' liào', + '䢦' => ' shuài', + '䢥' => ' yán', + '䢤' => ' shù', + '䢣' => ' yáo', + '䢢' => ' càng', + '䢡' => ' yè', + '䢟' => ' yóu', + '䢒' => ' jiāo', + '䢞' => ' shù', + '䢝' => ' yà', + '䢛' => ' jiǒng', + '䢚' => ' gēng', + '䢙' => ' bài', + '䢗' => ' qū', + '䢖' => ' yù', + '䢕' => ' xù', + '䢔' => ' hé', + '䢓' => ' yú', + '䡳' => ' sī', + '䡱' => ' zhuǎn', + '䠼' => ' shù', + '䡉' => ' kǎng', + '䡓' => ' juàn', + '䡒' => ' tián', + '䡑' => ' mín', + '䡐' => ' tuó', + '䡏' => ' hóng', + '䡎' => ' lú', + '䡍' => ' fú', + '䡌' => ' hóng', + '䡋' => ' qí', + '䡊' => ' fǎn', + '䡈' => ' jué', + '䡕' => ' zhěng', + '䡇' => ' yuè', + '䡆' => ' róng', + '䡅' => ' chūn', + '䡃' => ' lì', + '䡂' => ' jiū', + '䡁' => ' lǒng', + '䡀' => ' zhǎn', + '䠿' => ' wài', + '䠾' => ' shǎn', + '䠽' => ' hái', + '䡔' => ' qǐ', + '䡖' => ' qìng', + '䡰' => ' kēng', + '䡤' => ' zhū', + '䡯' => ' cōng', + '䡭' => ' xiū', + '䡬' => ' màn', + '䡫' => ' péng', + '䡪' => ' shàn', + '䡩' => ' kēng', + '䡨' => ' chà', + '䡧' => ' wū', + '䡦' => ' sǎng', + '䡥' => ' róng', + '䡣' => ' hūn', + '䡗' => ' gǒng', + '䡢' => ' biàn', + '䡡' => ' xié', + '䡟' => ' pì', + '䡞' => ' jú', + '䡝' => ' yuān', + '䡜' => ' lù', + '䡛' => ' yìn', + '䡚' => ' mào', + '䡙' => ' láng', + '䡘' => ' tián', + '䟋' => ' zhǎn', + '䟉' => ' zhú', + '䢭' => ' yàn', + '䜯' => ' jú', + '䜻' => ' chǐ', + '䜺' => ' chǎi', + '䜹' => ' shù', + '䜸' => ' měi', + '䜷' => ' qīn', + '䜶' => ' xiáng', + '䜵' => ' chǐ', + '䜴' => ' chù', + '䜲' => ' liè', + '䜱' => ' mǎn', + '䜮' => ' liáo', + '䜽' => ' yú', + '䜭' => ' jùn', + '䜬' => ' sǒng', + '䜫' => ' jiāng', + '䜪' => ' qiú', + '䜩' => ' yàn', + '䜧' => ' nǎo', + '䜣' => ' xīn', + '䜢' => ' xiǎn', + '䜡' => ' yù', + '䜠' => ' chè', + '䜼' => ' gú', + '䜾' => ' yīn', + '䜞' => ' jì', + '䝏' => ' lóu', + '䝛' => ' bō', + '䝚' => ' ní', + '䝙' => ' chū', + '䝘' => ' yì', + '䝖' => ' zhǎo', + '䝕' => ' zhé', + '䝓' => ' liè', + '䝒' => ' zhù', + '䝑' => ' chōng', + '䝐' => ' wéi', + '䝎' => ' tuān', + '䝀' => ' liú', + '䝍' => ' jùn', + '䝌' => ' jué', + '䝋' => ' zòng', + '䝊' => ' shà', + '䝈' => ' è', + '䝅' => ' huī', + '䝄' => ' shuāng', + '䝃' => ' zhé', + '䝂' => ' shù', + '䝁' => ' láo', + '䜟' => ' chén', + '䜝' => ' tuǎn', + '䝝' => ' yǐ', + '䛶' => ' lěi', + '䜀' => ' nǎo', + '䛿' => ' gé', + '䛾' => ' sù', + '䛽' => ' zhǎ', + '䛼' => ' huǐ', + '䛻' => ' yòu', + '䛺' => ' jiè', + '䛹' => ' càn', + '䛸' => ' chè', + '䛷' => ' wǎn', + '䛵' => ' shòu', + '䜃' => ' duī', + '䛴' => ' jī', + '䛳' => ' yàn', + '䛲' => ' mán', + '䛱' => ' tí', + '䛰' => ' hùn', + '䛯' => ' jù', + '䛮' => ' jiù', + '䛭' => ' xìng', + '䛬' => ' táo', + '䛫' => ' zhāng', + '䜁' => ' xì', + '䜄' => ' chí', + '䜜' => ' wèi', + '䜑' => ' wù', + '䜛' => ' chán', + '䜚' => ' tà', + '䜙' => ' án', + '䜘' => ' jǐng', + '䜗' => ' xìn', + '䜖' => ' tàn', + '䜕' => ' mài', + '䜔' => ' suí', + '䜓' => ' shè', + '䜒' => ' ào', + '䜐' => ' huī', + '䜅' => ' wéi', + '䜏' => ' tuō', + '䜎' => ' láo', + '䜍' => ' liáo', + '䜌' => ' luán', + '䜋' => ' huì', + '䜊' => ' zāo', + '䜉' => ' chī', + '䜈' => ' chāo', + '䜇' => ' gǔn', + '䜆' => ' zhé', + '䝜' => ' suān', + '䝞' => ' hào', + '䟈' => ' zhí', + '䞢' => ' zuó', + '䞬' => ' tòu', + '䞫' => ' yǔn', + '䞪' => ' jiàng', + '䞩' => ' è', + '䞨' => ' guǐ', + '䞧' => ' hòu', + '䞦' => ' hé', + '䞥' => ' yòu', + '䞤' => ' qú', + '䞣' => ' chě', + '䞡' => ' tǎn', + '䞮' => ' tū', + '䞠' => ' zhī', + '䞟' => ' pò', + '䞞' => ' fú', + '䞝' => ' yù', + '䞜' => ' fù', + '䞛' => ' yān', + '䞚' => ' qí', + '䞙' => ' zá', + '䞘' => ' jí', + '䞗' => ' cāi', + '䞭' => ' cūn', + '䞯' => ' fù', + '䞕' => ' rú', + '䞽' => ' suǒ', + '䟇' => ' jī', + '䟆' => ' bì', + '䟅' => ' jiàn', + '䟄' => ' qì', + '䟃' => ' cān', + '䟂' => ' mán', + '䟁' => ' xiáo', + '䟀' => ' cāi', + '䞿' => ' qiān', + '䞾' => ' chí', + '䞼' => ' chuǐ', + '䞰' => ' zuó', + '䞻' => ' yǒng', + '䞺' => ' chūn', + '䞹' => ' huáng', + '䞸' => ' fù', + '䞷' => ' jué', + '䞶' => ' tāng', + '䞵' => ' juě', + '䞴' => ' zhāo', + '䞳' => ' bó', + '䞱' => ' hú', + '䞖' => ' shǔ', + '䞔' => ' wěi', + '䝟' => ' yà', + '䝫' => ' zuó', + '䝶' => ' liáng', + '䝵' => ' bù', + '䝳' => ' cán', + '䝲' => ' jìn', + '䝱' => ' xié', + '䝰' => ' zhì', + '䝯' => ' yì', + '䝮' => ' xuàn', + '䝭' => ' gòu', + '䝬' => ' zhù', + '䝪' => ' shǔ', + '䝸' => ' jì', + '䝩' => ' zhèn', + '䝨' => ' xián', + '䝧' => ' mín', + '䝦' => ' zhōng', + '䝥' => ' háo', + '䝤' => ' lǎo', + '䝣' => ' qú', + '䝢' => ' màn', + '䝡' => ' màn', + '䝠' => ' huán', + '䝷' => ' zhī', + '䝹' => ' wǎn', + '䞓' => ' chēng', + '䞆' => ' suǒ', + '䞒' => ' dòng', + '䞑' => ' hóng', + '䞐' => ' chǔn', + '䞍' => ' qíng', + '䞌' => ' shé', + '䞋' => ' chèn', + '䞊' => ' xùn', + '䞉' => ' shèng', + '䞈' => ' guì', + '䞇' => ' dié', + '䞅' => ' yí', + '䝺' => ' guàn', + '䞄' => ' biào', + '䞃' => ' zhì', + '䞂' => ' ruǎn', + '䞁' => ' yàn', + '䞀' => ' hòu', + '䝿' => ' guì', + '䝾' => ' fù', + '䝽' => ' ài', + '䝼' => ' jìng', + '䝻' => ' jū', + '䢫' => ' suí', + '䢮' => ' lèi', + '䛩' => ' wù', + '䧢' => ' qū', + '䧯' => ' chán', + '䧮' => ' xiàn', + '䧬' => ' bīn', + '䧫' => ' háo', + '䧪' => ' chè', + '䧨' => ' yè', + '䧧' => ' yǐ', + '䧦' => ' wéi', + '䧥' => ' huì', + '䧤' => ' pú', + '䧡' => ' yōng', + '䧲' => ' hàn', + '䧠' => ' shuàn', + '䧟' => ' xiàn', + '䧞' => ' mà', + '䧝' => ' chī', + '䧜' => ' táng', + '䧛' => ' bàng', + '䧚' => ' hào', + '䧙' => ' líng', + '䧘' => ' zhuàn', + '䧗' => ' bì', + '䧰' => ' hùn', + '䧳' => ' cí', + '䧕' => ' chéng', + '䨄' => ' yàn', + '䨐' => ' qià', + '䨏' => ' cí', + '䨎' => ' hóng', + '䨍' => ' yǐng', + '䨌' => ' báo', + '䨋' => ' nüè', + '䨊' => ' yuān', + '䨈' => ' bīn', + '䨆' => ' bí', + '䨅' => ' liáo', + '䨂' => ' qiū', + '䧴' => ' zhī', + '䨁' => ' wù', + '䨀' => ' dí', + '䧿' => ' què', + '䧽' => ' cuǐ', + '䧼' => ' hú', + '䧺' => ' xióng', + '䧹' => ' yīng', + '䧷' => ' róu', + '䧶' => ' kuí', + '䧵' => ' qí', + '䧖' => ' jiàn', + '䧔' => ' niàn', + '䨒' => ' yù', + '䦦' => ' qín', + '䦲' => ' yán', + '䦱' => ' wěi', + '䦯' => ' zhì', + '䦮' => ' chǔn', + '䦭' => ' hāng', + '䦬' => ' què', + '䦫' => ' yīng', + '䦪' => ' yà', + '䦨' => ' lán', + '䦧' => ' qié', + '䦥' => ' xián', + '䦴' => ' yì', + '䦤' => ' chuài', + '䦣' => ' fù', + '䦢' => ' kuò', + '䦡' => ' huán', + '䦠' => ' dū', + '䦟' => ' wěn', + '䦞' => ' rùn', + '䦝' => ' huō', + '䦜' => ' wú', + '䦛' => ' zhèng', + '䦳' => ' xiàng', + '䦵' => ' nǐ', + '䧓' => ' zhōu', + '䧅' => ' yí', + '䧒' => ' lái', + '䧑' => ' dī', + '䧎' => ' xuàn', + '䧍' => ' xì', + '䧌' => ' suī', + '䧋' => ' xiǎn', + '䧊' => ' kū', + '䧉' => ' lǐ', + '䧇' => ' yī', + '䧆' => ' hóng', + '䧄' => ' gè', + '䦶' => ' zhèng', + '䧃' => ' tián', + '䧂' => ' dào', + '䧁' => ' xǔ', + '䦾' => ' yuán', + '䦽' => ' xù', + '䦼' => ' jué', + '䦻' => ' zǐ', + '䦺' => ' dīng', + '䦹' => ' shí', + '䦷' => ' chuài', + '䨑' => ' tí', + '䨓' => ' léi', + '䦙' => ' sì', + '䩘' => ' dì', + '䩣' => ' tú', + '䩢' => ' zhì', + '䩡' => ' jiá', + '䩟' => ' yí', + '䩞' => ' tié', + '䩝' => ' páo', + '䩜' => ' zhòu', + '䩛' => ' bì', + '䩚' => ' dì', + '䩙' => ' xuàn', + '䩗' => ' bà', + '䩥' => ' dàn', + '䩕' => ' áng', + '䩔' => ' duò', + '䩓' => ' qí', + '䩒' => ' yú', + '䩑' => ' hóng', + '䩐' => ' hū', + '䩏' => ' miè', + '䩍' => ' liǎo', + '䩌' => ' qiáo', + '䩋' => ' mǒ', + '䩤' => ' xié', + '䩦' => ' tiáo', + '䩉' => ' fǔ', + '䩴' => ' hú', + '䩾' => ' zhè', + '䩽' => ' yū', + '䩼' => ' féng', + '䩻' => ' bà', + '䩺' => ' wēng', + '䩹' => ' é', + '䩸' => ' róng', + '䩷' => ' bāng', + '䩶' => ' chǎn', + '䩵' => ' yùn', + '䩳' => ' sōu', + '䩧' => ' xiè', + '䩲' => ' dū', + '䩱' => ' shù', + '䩰' => ' xuàn', + '䩯' => ' jí', + '䩮' => ' lù', + '䩬' => ' běng', + '䩫' => ' liǎng', + '䩪' => ' guǎn', + '䩩' => ' yuǎn', + '䩨' => ' chàng', + '䩊' => ' wǎn', + '䩈' => ' huì', + '䨔' => ' báo', + '䨡' => ' hán', + '䨬' => ' lián', + '䨫' => ' mài', + '䨪' => ' mái', + '䨩' => ' líng', + '䨨' => ' zhuī', + '䨦' => ' pāng', + '䨥' => ' huò', + '䨤' => ' dí', + '䨣' => ' gé', + '䨢' => ' dàn', + '䨠' => ' ǎi', + '䨮' => ' xuě', + '䨟' => ' wā', + '䨞' => ' yǔ', + '䨝' => ' qīng', + '䨜' => ' bēng', + '䨛' => ' sè', + '䨚' => ' hū', + '䨙' => ' cén', + '䨘' => ' xiàn', + '䨗' => ' fú', + '䨖' => ' jì', + '䨭' => ' xiāo', + '䨯' => ' zhèn', + '䩇' => ' zhān', + '䨼' => ' hù', + '䩆' => ' zhǎn', + '䩅' => ' zhǎn', + '䩄' => ' miǎn', + '䩃' => ' shì', + '䩂' => ' xiān', + '䩁' => ' fēi', + '䩀' => ' bèi', + '䨿' => ' zá', + '䨾' => ' fèi', + '䨽' => ' fěi', + '䨻' => ' bèng', + '䨰' => ' pò', + '䨺' => ' duì', + '䨹' => ' shū', + '䨸' => ' yǐn', + '䨷' => ' xiàn', + '䨶' => ' yǔn', + '䨵' => ' dàn', + '䨴' => ' duì', + '䨳' => ' xì', + '䨲' => ' nóu', + '䨱' => ' fù', + '䦚' => ' kuà', + '䦘' => ' xiàn', + '䢯' => ' lín', + '䣸' => ' rǎn', + '䤃' => ' yīn', + '䤂' => ' méi', + '䤁' => ' cén', + '䣿' => ' yū', + '䣾' => ' mì', + '䣽' => ' zhī', + '䣼' => ' liáng', + '䣻' => ' hān', + '䣺' => ' juān', + '䣹' => ' fá', + '䣷' => ' zhū', + '䤅' => ' tú', + '䣶' => ' huó', + '䣵' => ' èr', + '䣴' => ' xù', + '䣲' => ' fàn', + '䣱' => ' xù', + '䣰' => ' jù', + '䣯' => ' cú', + '䣮' => ' pò', + '䣭' => ' tài', + '䣬' => ' zǎi', + '䤄' => ' miǎn', + '䤆' => ' kuí', + '䣪' => ' pò', + '䤕' => ' xuè', + '䤟' => ' chén', + '䤞' => ' yǔn', + '䤝' => ' yìng', + '䤜' => ' dùn', + '䤛' => ' qiú', + '䤚' => ' lǐ', + '䤙' => ' lì', + '䤘' => ' chǎn', + '䤗' => ' gǎn', + '䤖' => ' bào', + '䤔' => ' jiàn', + '䤉' => ' mì', + '䤓' => ' méng', + '䤒' => ' jì', + '䤑' => ' wàng', + '䤐' => ' jǐn', + '䤏' => ' pǐ', + '䤎' => ' jú', + '䤍' => ' mí', + '䤌' => ' qiāng', + '䤋' => ' yù', + '䤊' => ' róng', + '䣫' => ' lí', + '䣩' => ' chún', + '䤡' => ' rǎn', + '䢾' => ' xiǎn', + '䣊' => ' dǎng', + '䣈' => ' hàng', + '䣇' => ' qiú', + '䣆' => ' xíng', + '䣅' => ' chén', + '䣄' => ' tú', + '䣂' => ' lěi', + '䣁' => ' yǔ', + '䣀' => ' guǐ', + '䢿' => ' ān', + '䢽' => ' xiàng', + '䣌' => ' dǐ', + '䢼' => ' gōng', + '䢻' => ' chén', + '䢺' => ' chū', + '䢹' => ' jǔ', + '䢸' => ' jū', + '䢵' => ' yún', + '䢳' => ' jǐ', + '䢲' => ' yuè', + '䢱' => ' dú', + '䢰' => ' tī', + '䣋' => ' cǎi', + '䣍' => ' yǎn', + '䣨' => ' chún', + '䣝' => ' tú', + '䣧' => ' yì', + '䣦' => ' lèi', + '䣥' => ' bǐ', + '䣤' => ' jué', + '䣣' => ' dǎng', + '䣢' => ' jí', + '䣡' => ' yí', + '䣠' => ' jié', + '䣟' => ' cán', + '䣞' => ' è', + '䣜' => ' cuó', + '䣎' => ' zī', + '䣛' => ' qī', + '䣚' => ' lóu', + '䣙' => ' péi', + '䣘' => ' táng', + '䣖' => ' mǎ', + '䣕' => ' mǎ', + '䣔' => ' suǒ', + '䣓' => ' lí', + '䣑' => ' chán', + '䣐' => ' yīng', + '䤠' => ' zhǐ', + '䤣' => ' lüè', + '䦗' => ' xù', + '䥩' => ' wéi', + '䥳' => ' yōu', + '䥲' => ' ōu', + '䥱' => ' xiě', + '䥰' => ' méng', + '䥯' => ' bēi', + '䥮' => ' zhú', + '䥭' => ' zhèng', + '䥬' => ' bó', + '䥫' => ' tiě', + '䥪' => ' xiǎn', + '䥨' => ' lǜ', + '䥶' => ' lì', + '䥧' => ' huán', + '䥥' => ' lián', + '䥤' => ' báo', + '䥣' => ' záo', + '䥢' => ' lóng', + '䥡' => ' yè', + '䥟' => ' yè', + '䥞' => ' jiǎo', + '䥝' => ' áo', + '䥜' => ' jiàn', + '䥵' => ' xiǎo', + '䥷' => ' zhá', + '䥙' => ' suì', + '䦌' => ' chù', + '䦖' => ' xié', + '䦕' => ' pēng', + '䦔' => ' tǎn', + '䦓' => ' zhān', + '䦒' => ' dàng', + '䦑' => ' xuè', + '䦐' => ' tǐng', + '䦏' => ' xiè', + '䦎' => ' guǎn', + '䦍' => ' wù', + '䦋' => ' áo', + '䥸' => ' mí', + '䦊' => ' niǎo', + '䦈' => ' jiē', + '䦇' => ' jì', + '䦆' => ' jué', + '䦅' => ' shàn', + '䦃' => ' zhuō', + '䦂' => ' shàn', + '䥾' => ' xiě', + '䥽' => ' pō', + '䥺' => ' yé', + '䥛' => ' jiē', + '䥘' => ' cù', + '䤤' => ' kāi', + '䤰' => ' yíng', + '䤺' => ' jīn', + '䤹' => ' sōu', + '䤸' => ' zuàn', + '䤷' => ' yǎn', + '䤶' => ' yè', + '䤵' => ' fèi', + '䤴' => ' hán', + '䤳' => ' yè', + '䤲' => ' chì', + '䤱' => ' shì', + '䤯' => ' xíng', + '䤼' => ' xiàn', + '䤮' => ' shè', + '䤭' => ' shì', + '䤬' => ' shā', + '䤫' => ' chán', + '䤪' => ' duǒ', + '䤩' => ' chá', + '䤨' => ' pì', + '䤧' => ' huì', + '䤦' => ' yuè', + '䤥' => ' guǐ', + '䤻' => ' duò', + '䤽' => ' guān', + '䥗' => ' kuǎn', + '䥌' => ' zhèng', + '䥖' => ' tián', + '䥕' => ' piě', + '䥔' => ' sù', + '䥓' => ' qī', + '䥒' => ' jiàng', + '䥑' => ' wàn', + '䥐' => ' móu', + '䥏' => ' yǔ', + '䥎' => ' chún', + '䥍' => ' zhì', + '䥋' => ' pēng', + '䤾' => ' tāo', + '䥉' => ' yuān', + '䥈' => ' mǔ', + '䥇' => ' shàn', + '䥆' => ' jǐn', + '䥅' => ' qiàn', + '䥄' => ' cù', + '䥃' => ' yuè', + '䥂' => ' mèng', + '䥁' => ' hán', + '䥀' => ' chǎn', + '䤿' => ' qiè', + '䛪' => ' qióng', + '䛨' => ' xìn', + '䪀' => ' guǎn', + '䑸' => ' zōng', + '䒄' => ' fù', + '䒃' => ' cào', + '䒂' => ' jiǎng', + '䒁' => ' xí', + '䒀' => ' bù', + '䑿' => ' sù', + '䑽' => ' tà', + '䑼' => ' zhōu', + '䑻' => ' yóu', + '䑹' => ' sōu', + '䑶' => ' qiàn', + '䒆' => ' chè', + '䑵' => ' mù', + '䑴' => ' qí', + '䑳' => ' lún', + '䑲' => ' zhào', + '䑱' => ' wǎn', + '䑰' => ' bù', + '䑯' => ' dì', + '䑭' => ' dì', + '䑬' => ' tāo', + '䑫' => ' bēng', + '䒅' => ' téng', + '䒇' => ' fù', + '䑨' => ' duò', + '䒔' => ' bài', + '䒠' => ' fēng', + '䒟' => ' dān', + '䒞' => ' chén', + '䒝' => ' xiáo', + '䒜' => ' niú', + '䒛' => ' huàn', + '䒚' => ' shǎo', + '䒗' => ' qì', + '䒖' => ' xìn', + '䒕' => ' xiǎo', + '䒓' => ' kǎi', + '䒈' => ' fèi', + '䒒' => ' tiáo', + '䒑' => ' cǎo', + '䒐' => ' méng', + '䒏' => ' sēng', + '䒎' => ' mǎng', + '䒍' => ' pǎng', + '䒌' => ' mìng', + '䒋' => ' yǎng', + '䒊' => ' xī', + '䒉' => ' wǔ', + '䑪' => ' è', + '䑧' => ' fú', + '䒢' => ' áng', + '䐽' => ' mán', + '䑇' => ' zhì', + '䑆' => ' yǎng', + '䑅' => ' méng', + '䑄' => ' pì', + '䑃' => ' méng', + '䑂' => ' ǎi', + '䑁' => ' wù', + '䑀' => ' pì', + '䐿' => ' ào', + '䐾' => ' duó', + '䐼' => ' xì', + '䑉' => ' yíng', + '䐻' => ' gū', + '䐺' => ' tàn', + '䐹' => ' sōu', + '䐸' => ' huò', + '䐷' => ' dān', + '䐶' => ' zān', + '䐵' => ' gōng', + '䐴' => ' guì', + '䐳' => ' yú', + '䐲' => ' zhé', + '䑈' => ' bó', + '䑊' => ' wéi', + '䑦' => ' gōu', + '䑙' => ' tān', + '䑥' => ' è', + '䑤' => ' jìn', + '䑣' => ' chēn', + '䑠' => ' liǎo', + '䑟' => ' huáng', + '䑞' => ' shùn', + '䑝' => ' jiǎ', + '䑜' => ' tà', + '䑛' => ' chǐ', + '䑚' => ' tián', + '䑘' => ' cuó', + '䑋' => ' rǎng', + '䑗' => ' dàng', + '䑕' => ' shǔ', + '䑔' => ' fèi', + '䑓' => ' tái', + '䑑' => ' pú', + '䑐' => ' zhěn', + '䑏' => ' quán', + '䑎' => ' chǎn', + '䑍' => ' yān', + '䑌' => ' lán', + '䒡' => ' yǐn', + '䒣' => ' rǎn', + '䐰' => ' xiū', + '䓮' => ' mào', + '䓸' => ' jiān', + '䓷' => ' xìng', + '䓶' => ' lài', + '䓵' => ' fū', + '䓴' => ' ruǎn', + '䓳' => ' hěn', + '䓲' => ' ruì', + '䓱' => ' chái', + '䓰' => ' yīn', + '䓯' => ' dú', + '䓭' => ' chà', + '䓺' => ' měi', + '䓫' => ' qí', + '䓨' => ' yīng', + '䓧' => ' cì', + '䓦' => ' diǎn', + '䓥' => ' là', + '䓤' => ' hū', + '䓣' => ' liǎng', + '䓢' => ' gù', + '䓡' => ' zhī', + '䓠' => ' tà', + '䓹' => ' yì', + '䓼' => ' máng', + '䓞' => ' lì', + '䔊' => ' bīng', + '䔖' => ' líng', + '䔕' => ' pú', + '䔔' => ' jìng', + '䔓' => ' xǔ', + '䔒' => ' bèi', + '䔑' => ' xié', + '䔐' => ' jiān', + '䔏' => ' chòu', + '䔎' => ' sù', + '䔋' => ' suō', + '䔉' => ' lì', + '䓽' => ' jì', + '䔈' => ' gòng', + '䔇' => ' qǐ', + '䔆' => ' lí', + '䔅' => ' gē', + '䔄' => ' yáo', + '䔃' => ' zǔ', + '䔂' => ' zǐ', + '䔁' => ' lì', + '䓿' => ' hàn', + '䓾' => ' suō', + '䓟' => ' zhōu', + '䓝' => ' mèng', + '䒤' => ' rì', + '䒳' => ' duǒ', + '䒾' => ' yī', + '䒽' => ' wǎng', + '䒼' => ' qū', + '䒻' => ' qǐ', + '䒺' => ' jǐn', + '䒹' => ' lèi', + '䒸' => ' xuè', + '䒷' => ' guā', + '䒵' => ' hào', + '䒴' => ' yǒu', + '䒲' => ' chà', + '䓂' => ' yán', + '䒰' => ' kuāng', + '䒭' => ' děng', + '䒬' => ' mò', + '䒫' => ' dài', + '䒪' => ' biàn', + '䒩' => ' hé', + '䒨' => ' shǐ', + '䒧' => ' qū', + '䒦' => ' fàn', + '䒥' => ' mán', + '䒿' => ' liáo', + '䓃' => ' yì', + '䓜' => ' zhī', + '䓐' => ' chún', + '䓛' => ' qū', + '䓚' => ' qū', + '䓙' => ' kuā', + '䓘' => ' gāo', + '䓗' => ' cōng', + '䓖' => ' qióng', + '䓕' => ' tuǒ', + '䓓' => ' chóu', + '䓒' => ' kuǎi', + '䓑' => ' píng', + '䓏' => ' fū', + '䓄' => ' yín', + '䓎' => ' chuò', + '䓍' => ' hǎn', + '䓌' => ' zhì', + '䓋' => ' zhī', + '䓊' => ' wú', + '䓉' => ' yé', + '䓈' => ' yì', + '䓇' => ' xì', + '䓆' => ' zhé', + '䓅' => ' qí', + '䐱' => ' zhài', + '䐯' => ' léi', + '䔘' => ' zuò', + '䎜' => ' hūn', + '䎨' => ' yè', + '䎧' => ' bàng', + '䎦' => ' yǎn', + '䎤' => ' jú', + '䎣' => ' sì', + '䎢' => ' qǐ', + '䎡' => ' ruǎn', + '䎠' => ' ér', + '䎟' => ' ér', + '䎝' => ' chú', + '䎚' => ' guàn', + '䎪' => ' nè', + '䎙' => ' pīn', + '䎘' => ' sù', + '䎗' => ' qiáo', + '䎖' => ' zēng', + '䎕' => ' hōng', + '䎔' => ' fū', + '䎓' => ' tà', + '䎒' => ' zhǎn', + '䎑' => ' lù', + '䎐' => ' chǎo', + '䎩' => ' zī', + '䎫' => ' chuàng', + '䎎' => ' nà', + '䎸' => ' wù', + '䏂' => ' sǒu', + '䏁' => ' zǎi', + '䏀' => ' là', + '䎿' => ' qiú', + '䎾' => ' gǔn', + '䎽' => ' wén', + '䎼' => ' lù', + '䎻' => ' zhòu', + '䎺' => ' zhì', + '䎹' => ' wén', + '䎷' => ' zhù', + '䎬' => ' bà', + '䎶' => ' èr', + '䎵' => ' bì', + '䎴' => ' gēng', + '䎳' => ' wà', + '䎲' => ' zhé', + '䎱' => ' bà', + '䎰' => ' zuó', + '䎯' => ' hàn', + '䎮' => ' tì', + '䎭' => ' cāo', + '䎏' => ' hán', + '䎌' => ' chù', + '䏄' => ' dǐ', + '䍢' => ' wú', + '䍮' => ' zhào', + '䍬' => ' pēng', + '䍫' => ' tuó', + '䍪' => ' wà', + '䍩' => ' yǎng', + '䍨' => ' pō', + '䍦' => ' lí', + '䍥' => ' lì', + '䍤' => ' jǐ', + '䍣' => ' léi', + '䍡' => ' lù', + '䍱' => ' xú', + '䍠' => ' lí', + '䍟' => ' zòng', + '䍞' => ' yù', + '䍝' => ' tà', + '䍜' => ' zhào', + '䍛' => ' gù', + '䍚' => ' làng', + '䍙' => ' méi', + '䍘' => ' mí', + '䍗' => ' xuàn', + '䍯' => ' guǐ', + '䍲' => ' nái', + '䎋' => ' kào', + '䎀' => ' xuè', + '䎊' => ' luò', + '䎉' => ' xù', + '䎈' => ' yì', + '䎇' => ' zhōu', + '䎆' => ' liào', + '䎅' => ' pò', + '䎄' => ' xiāo', + '䎃' => ' rǎn', + '䎂' => ' bǎo', + '䎁' => ' hú', + '䍿' => ' huáng', + '䍳' => ' què', + '䍾' => ' yǎn', + '䍽' => ' lì', + '䍼' => ' zān', + '䍻' => ' xuàn', + '䍺' => ' huàn', + '䍸' => ' bó', + '䍷' => ' wěi', + '䍶' => ' dōng', + '䍵' => ' zhēng', + '䍴' => ' wěi', + '䏃' => ' mián', + '䏅' => ' qì', + '䐮' => ' chǎn', + '䐉' => ' zì', + '䐓' => ' róu', + '䐒' => ' zhā', + '䐑' => ' zhé', + '䐐' => ' qiū', + '䐏' => ' chǔn', + '䐎' => ' dī', + '䐍' => ' chù', + '䐌' => ' tiǎn', + '䐋' => ' cōng', + '䐊' => ' kūn', + '䐈' => ' zhí', + '䐕' => ' jí', + '䐇' => ' wěn', + '䐆' => ' cǎi', + '䐅' => ' xī', + '䐄' => ' xiàn', + '䐃' => ' jùn', + '䐂' => ' lù', + '䐁' => ' zhuó', + '䐀' => ' jì', + '䏿' => ' qǐ', + '䏽' => ' bù', + '䐔' => ' bǐn', + '䐖' => ' xī', + '䏻' => ' néng', + '䐣' => ' sǔn', + '䐭' => ' zhì', + '䐬' => ' cáo', + '䐫' => ' cōng', + '䐪' => ' fàn', + '䐩' => ' gāi', + '䐨' => ' gǔ', + '䐧' => ' kào', + '䐦' => ' kē', + '䐥' => ' wěng', + '䐤' => ' chāi', + '䐢' => ' zhù', + '䐗' => ' zhū', + '䐡' => ' qí', + '䐠' => ' huǎng', + '䐟' => ' xiǎng', + '䐞' => ' ruò', + '䐝' => ' suò', + '䐜' => ' chēn', + '䐛' => ' dā', + '䐚' => ' jī', + '䐙' => ' gé', + '䐘' => ' jué', + '䏼' => ' cán', + '䏺' => ' pāng', + '䏆' => ' cáo', + '䏒' => ' piàn', + '䏜' => ' hē', + '䏛' => ' chǔn', + '䏚' => ' chǎo', + '䏙' => ' tǎn', + '䏘' => ' pì', + '䏗' => ' gài', + '䏖' => ' zhèn', + '䏕' => ' rèn', + '䏔' => ' niǔ', + '䏓' => ' guǎn', + '䏑' => ' dì', + '䏞' => ' mò', + '䏐' => ' jué', + '䏏' => ' xū', + '䏎' => ' féng', + '䏍' => ' yuàn', + '䏌' => ' qì', + '䏋' => ' sù', + '䏊' => ' lóng', + '䏉' => ' shī', + '䏈' => ' lián', + '䏇' => ' piào', + '䏝' => ' zhuān', + '䏟' => ' bié', + '䏹' => ' xiàn', + '䏮' => ' xí', + '䏸' => ' yú', + '䏷' => ' hàn', + '䏶' => ' bì', + '䏵' => ' měng', + '䏴' => ' shào', + '䏳' => ' zhè', + '䏲' => ' dié', + '䏱' => ' jú', + '䏰' => ' rùn', + '䏯' => ' zhì', + '䏬' => ' móu', + '䏠' => ' qì', + '䏫' => ' xiū', + '䏪' => ' èr', + '䏩' => ' xī', + '䏨' => ' huǐ', + '䏧' => ' nà', + '䏦' => ' guā', + '䏤' => ' sì', + '䏣' => ' jué', + '䏢' => ' bǐ', + '䏡' => ' shì', + '䔗' => ' xiáng', + '䔙' => ' diào', + '䛧' => ' mí', + '䙊' => ' xiè', + '䙗' => ' tì', + '䙕' => ' zǒng', + '䙔' => ' ōu', + '䙓' => ' bǎi', + '䙒' => ' xù', + '䙑' => ' cuī', + '䙐' => ' kè', + '䙏' => ' bó', + '䙎' => ' xié', + '䙌' => ' kuì', + '䙉' => ' xún', + '䙙' => ' chí', + '䙈' => ' hóu', + '䙇' => ' ruán', + '䙆' => ' kuì', + '䙅' => ' yāo', + '䙄' => ' qì', + '䙃' => ' duò', + '䙁' => ' cán', + '䙀' => ' běng', + '䘿' => ' jué', + '䘾' => ' guǎn', + '䙘' => ' chǔ', + '䙚' => ' niǎo', + '䘼' => ' wǎn', + '䙨' => ' guǒ', + '䙷' => ' dé', + '䙵' => ' xī', + '䙳' => ' biāo', + '䙱' => ' dú', + '䙰' => ' lí', + '䙯' => ' cù', + '䙮' => ' guàn', + '䙬' => ' yìng', + '䙪' => ' lóng', + '䙩' => ' méng', + '䙦' => ' méng', + '䙛' => ' guàn', + '䙥' => ' líng', + '䙤' => ' duǒ', + '䙣' => ' sà', + '䙢' => ' zèng', + '䙡' => ' kuì', + '䙠' => ' jué', + '䙟' => ' wéi', + '䙞' => ' dēng', + '䙝' => ' xiè', + '䙜' => ' féng', + '䘽' => ' ní', + '䘻' => ' yù', + '䙹' => ' xiàn', + '䘌' => ' nì', + '䘘' => ' yù', + '䘕' => ' háng', + '䘔' => ' kā', + '䘓' => ' kàn', + '䘒' => ' zuī', + '䘑' => ' mài', + '䘐' => ' nǜ', + '䘏' => ' xù', + '䘎' => ' wān', + '䘍' => ' chài', + '䘋' => ' jiān', + '䘚' => ' zhú', + '䘊' => ' miè', + '䘉' => ' cán', + '䘈' => ' lì', + '䘇' => ' wén', + '䘆' => ' xiǎn', + '䘅' => ' nái', + '䘄' => ' zhù', + '䘃' => ' mò', + '䘁' => ' gé', + '䘀' => ' fù', + '䘙' => ' wèi', + '䘝' => ' yì', + '䘺' => ' zhàn', + '䘬' => ' róng', + '䘹' => ' zuì', + '䘸' => ' yì', + '䘷' => ' biē', + '䘶' => ' hān', + '䘵' => ' lù', + '䘳' => ' jīn', + '䘱' => ' yù', + '䘰' => ' shān', + '䘮' => ' sāng', + '䘭' => ' zhì', + '䘫' => ' nòu', + '䘟' => ' diāo', + '䘪' => ' chōng', + '䘩' => ' xún', + '䘨' => ' jiǎo', + '䘦' => ' ní', + '䘥' => ' xiá', + '䘤' => ' shù', + '䘣' => ' zǐ', + '䘢' => ' zhǔ', + '䘡' => ' bǐ', + '䘠' => ' fú', + '䙸' => ' dé', + '䙺' => ' lián', + '䗾' => ' xiǎn', + '䛁' => ' nán', + '䛋' => ' jì', + '䛊' => ' xī', + '䛉' => ' miàn', + '䛈' => ' shì', + '䛇' => ' yuǎn', + '䛆' => ' zhòu', + '䛅' => ' xiá', + '䛄' => ' yuǎn', + '䛃' => ' wàn', + '䛂' => ' yāo', + '䛀' => ' fǎn', + '䛍' => ' fèi', + '䚿' => ' yìn', + '䚾' => ' nín', + '䚽' => ' hào', + '䚼' => ' nǜ', + '䚻' => ' yáo', + '䚺' => ' yáo', + '䚹' => ' pǐ', + '䚸' => ' xiè', + '䚷' => ' yī', + '䚶' => ' tiān', + '䛌' => ' táo', + '䛎' => ' xuè', + '䚴' => ' wà', + '䛜' => ' xùn', + '䛦' => ' sòng', + '䛥' => ' xī', + '䛤' => ' cù', + '䛣' => ' pīng', + '䛢' => ' tū', + '䛡' => ' huà', + '䛠' => ' dòu', + '䛟' => ' jiá', + '䛞' => ' hàn', + '䛝' => ' náo', + '䛛' => ' huì', + '䛏' => ' ní', + '䛚' => ' lüè', + '䛙' => ' xù', + '䛘' => ' rén', + '䛗' => ' zhǐ', + '䛖' => ' è', + '䛕' => ' yù', + '䛔' => ' ná', + '䛒' => ' biàn', + '䛑' => ' mì', + '䛐' => ' cí', + '䚵' => ' tǒu', + '䚳' => ' zhì', + '䙼' => ' shào', + '䚊' => ' jiǎn', + '䚖' => ' líng', + '䚕' => ' lì', + '䚔' => ' bīn', + '䚓' => ' niǎn', + '䚒' => ' chuáng', + '䚑' => ' mái', + '䚐' => ' jí', + '䚏' => ' lìn', + '䚍' => ' qì', + '䚋' => ' yùn', + '䚉' => ' qì', + '䚘' => ' chéng', + '䚈' => ' juàn', + '䚇' => ' shěng', + '䚆' => ' yǐng', + '䚅' => ' lài', + '䚄' => ' lù', + '䚃' => ' yóu', + '䚂' => ' hè', + '䙿' => ' wèi', + '䙾' => ' shī', + '䙽' => ' xié', + '䚗' => ' gāng', + '䚙' => ' xuān', + '䚲' => ' shàn', + '䚧' => ' liú', + '䚱' => ' xìn', + '䚰' => ' pǐ', + '䚯' => ' tǎo', + '䚮' => ' réng', + '䚭' => ' xuān', + '䚬' => ' lín', + '䚫' => ' xí', + '䚪' => ' guān', + '䚩' => ' jiǎo', + '䚨' => ' fèi', + '䚦' => ' zhì', + '䚚' => ' xiǎn', + '䚥' => ' nuò', + '䚣' => ' tí', + '䚢' => ' chè', + '䚡' => ' sāi', + '䚠' => ' hùn', + '䚟' => ' dǎi', + '䚞' => ' dǎi', + '䚝' => ' zú', + '䚜' => ' bī', + '䚛' => ' hú', + '䗿' => ' níng', + '䗽' => ' wèi', + '䔚' => ' chún', + '䕥' => ' nǐ', + '䕰' => ' fán', + '䕯' => ' piáo', + '䕮' => ' jú', + '䕭' => ' qián', + '䕫' => ' kuí', + '䕪' => ' zé', + '䕩' => ' lǎo', + '䕨' => ' téng', + '䕧' => ' xiào', + '䕦' => ' yíng', + '䕤' => ' jī', + '䕲' => ' lǐn', + '䕣' => ' hé', + '䕢' => ' zhǎ', + '䕡' => ' lǘ', + '䕠' => ' fèi', + '䕟' => ' xīng', + '䕞' => ' làng', + '䕝' => ' chēng', + '䕛' => ' jiá', + '䕚' => ' kuì', + '䕙' => ' jié', + '䕱' => ' tóu', + '䕳' => ' mí', + '䕗' => ' bì', + '䖁' => ' yì', + '䖌' => ' yì', + '䖋' => ' nüè', + '䖊' => ' yì', + '䖈' => ' nüè', + '䖇' => ' yù', + '䖆' => ' niàng', + '䖅' => ' líng', + '䖄' => ' rán', + '䖃' => ' yuè', + '䖂' => ' luán', + '䕿' => ' hàn', + '䕴' => ' zhuó', + '䕾' => ' yín', + '䕽' => ' zhú', + '䕼' => ' rán', + '䕻' => ' lì', + '䕺' => ' cóng', + '䕹' => ' zá', + '䕸' => ' jiē', + '䕷' => ' mí', + '䕶' => ' hù', + '䕵' => ' xié', + '䕘' => ' líng', + '䕕' => ' màn', + '䖎' => ' xiá', + '䔪' => ' shuǎng', + '䔶' => ' tí', + '䔴' => ' cè', + '䔳' => ' rán', + '䔲' => ' dēng', + '䔱' => ' yī', + '䔰' => ' fù', + '䔯' => ' kù', + '䔮' => ' sī', + '䔭' => ' nìng', + '䔬' => ' yì', + '䔧' => ' lí', + '䔸' => ' biǎo', + '䔤' => ' pā', + '䔣' => ' lí', + '䔢' => ' huá', + '䔡' => ' yú', + '䔠' => ' shǎo', + '䔟' => ' yí', + '䔞' => ' lǜ', + '䔝' => ' zhāi', + '䔜' => ' nán', + '䔛' => ' qǐng', + '䔷' => ' qín', + '䔹' => ' suì', + '䕔' => ' xián', + '䕈' => ' xiè', + '䕓' => ' chá', + '䕒' => ' jiā', + '䕑' => ' jùn', + '䕏' => ' é', + '䕎' => ' fú', + '䕍' => ' yì', + '䕌' => ' zhì', + '䕋' => ' táng', + '䕊' => ' tán', + '䕉' => ' zé', + '䕇' => ' huì', + '䔺' => ' wéi', + '䕆' => ' dòu', + '䕅' => ' sǎo', + '䕃' => ' yìn', + '䕁' => ' fěi', + '䕀' => ' kuǎn', + '䔿' => ' zǔn', + '䔾' => ' qì', + '䔽' => ' ài', + '䔼' => ' sè', + '䔻' => ' dūn', + '䖍' => ' qián', + '䖏' => ' chǔ', + '䗼' => ' xūn', + '䗓' => ' cōng', + '䗝' => ' cán', + '䗜' => ' liú', + '䗛' => ' xiū', + '䗚' => ' bó', + '䗙' => ' hán', + '䗘' => ' gé', + '䗗' => ' jī', + '䗖' => ' dì', + '䗕' => ' wǎn', + '䗔' => ' hóu', + '䗒' => ' bìng', + '䗟' => ' yì', + '䗑' => ' yì', + '䗐' => ' shī', + '䗏' => ' sōu', + '䗎' => ' yān', + '䗍' => ' lǐ', + '䗌' => ' xīng', + '䗋' => ' móu', + '䗊' => ' tàn', + '䗉' => ' chāng', + '䗈' => ' méng', + '䗞' => ' cán', + '䗠' => ' xuán', + '䗆' => ' guǎn', + '䗰' => ' guàn', + '䗻' => ' jié', + '䗺' => ' yǎn', + '䗹' => ' cì', + '䗸' => ' yōng', + '䗷' => ' yì', + '䗶' => ' là', + '䗵' => ' jiāng', + '䗴' => ' tíng', + '䗲' => ' lìn', + '䗱' => ' pú', + '䗯' => ' jìn', + '䗡' => ' yán', + '䗮' => ' shuǎng', + '䗫' => ' má', + '䗪' => ' zhè', + '䗩' => ' qī', + '䗨' => ' yú', + '䗧' => ' kāng', + '䗥' => ' zōng', + '䗤' => ' yóng', + '䗣' => ' hàn', + '䗢' => ' zǎo', + '䗇' => ' jú', + '䗅' => ' cháng', + '䖐' => ' yín', + '䖜' => ' yín', + '䖦' => ' qū', + '䖥' => ' lí', + '䖤' => ' wǎn', + '䖣' => ' zǎo', + '䖢' => ' miáo', + '䖡' => ' nǜ', + '䖠' => ' yuán', + '䖟' => ' mǎng', + '䖞' => ' zhǒu', + '䖝' => ' chóng', + '䖛' => ' suǒ', + '䖨' => ' shí', + '䖚' => ' wū', + '䖙' => ' tī', + '䖘' => ' tú', + '䖗' => ' yán', + '䖖' => ' xiá', + '䖕' => ' zǔ', + '䖔' => ' kǎn', + '䖓' => ' nà', + '䖒' => ' xī', + '䖑' => ' mì', + '䖧' => ' nà', + '䖩' => ' bì', + '䗄' => ' fǔ', + '䖸' => ' é', + '䗃' => ' zhàn', + '䗂' => ' hǔ', + '䗁' => ' jì', + '䗀' => ' chēng', + '䖿' => ' lí', + '䖽' => ' lí', + '䖼' => ' jué', + '䖻' => ' yóu', + '䖺' => ' tiáo', + '䖹' => ' yáng', + '䖷' => ' xī', + '䖪' => ' zī', + '䖶' => ' huī', + '䖵' => ' kūn', + '䖴' => ' yáo', + '䖳' => ' zhà', + '䖲' => ' xún', + '䖱' => ' kuāng', + '䖰' => ' pài', + '䖯' => ' kuí', + '䖮' => ' xiǎng', + '䖭' => ' juàn', + '䖫' => ' bàng', + '䩿' => ' fén', + '䪁' => ' bǔ', + '䍕' => ' dī', + '佘' => ' shé', + '佢' => ' qú', + '佡' => ' xiān', + '你' => ' nǐ', + '佟' => ' tóng', + '佞' => ' nìng', + '佝' => ' gōu', + '作' => ' zuò', + '佛' => ' fú', + '佚' => ' yì', + '余' => ' yú', + '佗' => ' tuó', + '佤' => ' wǎ', + '佖' => ' bì', + '何' => ' hé', + '佔' => ' zhàn', + '体' => ' tǐ', + '佒' => ' yǎng', + '佑' => ' yòu', + '佐' => ' zuǒ', + '住' => ' zhù', + '低' => ' dī', + '位' => ' wèi', + '佣' => ' yōng', + '佥' => ' qiān', + '佋' => ' zhāo', + '佲' => ' mǐng', + '佼' => ' jiǎo', + '佻' => ' tiāo', + '佺' => ' quán', + '佹' => ' guǐ', + '佸' => ' huó', + '佷' => ' hěn', + '佶' => ' jí', + '併' => ' bìng', + '佴' => ' èr', + '佳' => ' jiā', + '佱' => ' fǎ', + '佦' => ' shi', + '佰' => ' bǎi', + '佯' => ' yáng', + '佮' => ' gé', + '佭' => ' xiáng', + '佬' => ' lǎo', + '佫' => ' hè', + '佪' => ' huí', + '佩' => ' pèi', + '佨' => ' bao', + '佧' => ' kǎ', + '佌' => ' cǐ', + '佊' => ' bǐ', + '佾' => ' yì', + '伤' => ' shāng', + '伮' => ' nǔ', + '伭' => ' xián', + '伬' => ' ze', + '伫' => ' zhù', + '伪' => ' wěi', + '伩' => ' xìn', + '伨' => ' xùn', + '伧' => ' cāng', + '伦' => ' lún', + '伥' => ' chāng', + '伣' => ' xiàn', + '估' => ' gū', + '伢' => ' yá', + '伡' => ' chē', + '传' => ' chuán', + '伟' => ' wěi', + '伞' => ' sǎn', + '伝' => ' chuán', + '伜' => ' cuì', + '伛' => ' yǔ', + '会' => ' huì', + '伙' => ' huǒ', + '伯' => ' bó', + '伱' => ' nǐ', + '佉' => ' qū', + '伾' => ' pī', + '佈' => ' bù', + '佇' => ' zhù', + '但' => ' dàn', + '佅' => ' mài', + '佄' => ' hān', + '佃' => ' diàn', + '佂' => ' zhēng', + '佁' => ' yǐ', + '佀' => ' sì', + '伿' => ' yì', + '伽' => ' jiā', + '伲' => ' nì', + '似' => ' shì', + '伻' => ' bēng', + '伺' => ' cì', + '伹' => ' qū', + '伸' => ' shēn', + '伷' => ' zhòu', + '伶' => ' líng', + '伵' => ' xù', + '伴' => ' bàn', + '伳' => ' xiè', + '佽' => ' cì', + '使' => ' shǐ', + '众' => ' zhòng', + '俀' => ' tuǐ', + '俊' => ' jùn', + '俉' => ' wǔ', + '俈' => ' kù', + '俇' => ' guàng', + '俆' => ' xú', + '俅' => ' qiú', + '俄' => ' é', + '促' => ' cù', + '係' => ' xì', + '俁' => ' yǔ', + '便' => ' biàn', + '俌' => ' fǔ', + '侾' => ' xiāo', + '侽' => ' nán', + '侼' => ' bó', + '侻' => ' tuì', + '侺' => ' shèn', + '侹' => ' tǐng', + '侸' => ' shù', + '侷' => ' jú', + '侶' => ' lǚ', + '侵' => ' qīn', + '俋' => ' yì', + '俍' => ' liáng', + '侳' => ' zuò', + '俚' => ' lǐ', + '俤' => ' dì', + '俣' => ' yǔ', + '俢' => ' xiū', + '信' => ' xìn', + '俠' => ' xiá', + '俟' => ' qí', + '俞' => ' yú', + '保' => ' bǎo', + '俜' => ' pīng', + '俛' => ' fǔ', + '俙' => ' xī', + '俎' => ' zǔ', + '俘' => ' fú', + '俗' => ' sú', + '俖' => ' pěi', + '俕' => ' sàn', + '俔' => ' qiàn', + '俓' => ' jìng', + '俒' => ' hùn', + '俑' => ' yǒng', + '俐' => ' lì', + '俏' => ' qiào', + '侴' => ' chǒu', + '侲' => ' zhèn', + '侀' => ' xíng', + '侌' => ' yīn', + '侖' => ' lún', + '侕' => ' ér', + '侔' => ' móu', + '侓' => ' lù', + '侒' => ' ān', + '侑' => ' yòu', + '侐' => ' xù', + '侏' => ' zhū', + '侎' => ' mǐ', + '侍' => ' shì', + '例' => ' lì', + '侘' => ' chà', + '侊' => ' guāng', + '侉' => ' kuǎ', + '侈' => ' chǐ', + '侇' => ' yí', + '來' => ' lái', + '侅' => ' gāi', + '侄' => ' zhí', + '侃' => ' kǎn', + '侂' => ' tuō', + '侁' => ' shēn', + '侗' => ' dòng', + '侙' => ' chī', + '侱' => ' chěng', + '侦' => ' zhēn', + '侰' => ' jiǒng', + '侯' => ' hóu', + '侮' => ' wǔ', + '侭' => ' jǐn', + '侬' => ' nóng', + '侫' => ' nìng', + '侪' => ' chái', + '侩' => ' kuài', + '侨' => ' qiáo', + '侧' => ' cè', + '侥' => ' jiǎo', + '侚' => ' xùn', + '侤' => ' ta', + '侣' => ' lǚ', + '侢' => ' zài', + '価' => ' sì', + '侠' => ' xiá', + '侟' => ' cún', + '侞' => ' rú', + '依' => ' yī', + '侜' => ' zhōu', + '供' => ' gōng', + '优' => ' yōu', + '伖' => ' tǎng', + '俦' => ' chóu', + '予' => ' yǔ', + '互' => ' hù', + '云' => ' yún', + '亐' => ' yú', + '亏' => ' kuī', + '于' => ' yú', + '亍' => ' chù', + '二' => ' èr', + '事' => ' shì', + '亊' => ' shì', + '争' => ' zhēng', + '亇' => ' ma', + '五' => ' wǔ', + '了' => ' le', + '亅' => ' jué', + '亄' => ' yì', + '亃' => ' lǐn', + '亂' => ' luàn', + '亁' => ' gān', + '亀' => ' guī', + '乿' => ' zhì', + '乾' => ' gān', + '乽' => ' zhě', + '亓' => ' qí', + '井' => ' jǐng', + '乻' => ' yú', + '亢' => ' kàng', + '京' => ' jīng', + '享' => ' xiǎng', + '亪' => ' ye', + '亩' => ' mǔ', + '亨' => ' hēng', + '产' => ' chǎn', + '亦' => ' yì', + '亥' => ' hài', + '交' => ' jiāo', + '亣' => ' tà', + '亡' => ' wáng', + '亖' => ' sì', + '亠' => ' tóu', + '亟' => ' jí', + '亞' => ' yà', + '亝' => ' qí', + '亜' => ' yà', + '些' => ' xiē', + '亚' => ' yà', + '亙' => ' gèn', + '亘' => ' gèn', + '亗' => ' suì', + '乼' => ' cui', + '乺' => ' suǒ', + '亮' => ' liàng', + '乔' => ' qiáo', + '乞' => ' qǐ', + '九' => ' jiǔ', + '乜' => ' miē', + '乛' => ' ya', + '乚' => ' yǐn', + '乙' => ' yǐ', + '乘' => ' chéng', + '乗' => ' chéng', + '乖' => ' guāi', + '乕' => ' hǔ', + '乓' => ' pāng', + '习' => ' xí', + '乒' => ' pīng', + '乑' => ' yín', + '乐' => ' lè', + '乏' => ' fá', + '乎' => ' hū', + '乍' => ' zhà', + '乌' => ' wū', + '之' => ' zhī', + '乊' => ' yī', + '义' => ' yì', + '也' => ' yě', + '乡' => ' xiāng', + '乹' => ' gān', + '乮' => ' mǎo', + '乸' => ' nǎ', + '乷' => ' shā', + '乶' => ' fǔ', + '乵' => ' yǎn', + '乴' => ' xué', + '乳' => ' rǔ', + '乲' => ' zī', + '乱' => ' luàn', + '买' => ' mǎi', + '乯' => ' hū', + '乭' => ' shí', + '乢' => ' gài', + '乬' => ' jù', + '乫' => ' jiā', + '乪' => ' náng', + '乩' => ' jī', + '乨' => ' shǐ', + '乧' => ' dou', + '书' => ' shū', + '乥' => ' hù', + '乤' => ' xià', + '乣' => ' jiǔ', + '亭' => ' tíng', + '亯' => ' xiǎng', + '伕' => ' fū', + '仰' => ' yǎng', + '仺' => ' cāng', + '仹' => ' fēng', + '仸' => ' yǎo', + '价' => ' jià', + '件' => ' jiàn', + '仵' => ' wǔ', + '仴' => ' wò', + '仳' => ' pǐ', + '仲' => ' zhòng', + '仱' => ' qián', + '仯' => ' chào', + '仼' => ' wáng', + '仮' => ' jiǎ', + '仭' => ' rèn', + '们' => ' men', + '仫' => ' mù', + '仪' => ' yí', + '仩' => ' shang', + '仨' => ' sā', + '仧' => ' cháng', + '仦' => ' chào', + '以' => ' yǐ', + '任' => ' rèn', + '份' => ' fèn', + '代' => ' dài', + '伊' => ' yī', + '伔' => ' dǎn', + '伓' => ' pī', + '伒' => ' jìn', + '休' => ' xiū', + '伐' => ' fá', + '伏' => ' fú', + '伎' => ' jì', + '伍' => ' wǔ', + '伌' => ' ài', + '伋' => ' jí', + '伉' => ' kàng', + '仾' => ' dī', + '伈' => ' xǐn', + '伇' => ' yì', + '伆' => ' wù', + '伅' => ' dùn', + '伄' => ' diào', + '伃' => ' yú', + '伂' => ' pèi', + '企' => ' qǐ', + '伀' => ' zhōng', + '仿' => ' fǎng', + '令' => ' lìng', + '仢' => ' bó', + '亰' => ' jīng', + '亼' => ' jí', + '仆' => ' pū', + '仅' => ' jǐn', + '仄' => ' zè', + '仃' => ' dīng', + '仂' => ' lè', + '仁' => ' rén', + '什' => ' shén', + '亿' => ' yì', + '亾' => ' wáng', + '亽' => ' ji', + '亻' => ' rén', + '仈' => ' bā', + '人' => ' rén', + '亹' => ' mén', + '亸' => ' duǒ', + '亷' => ' lián', + '亶' => ' dǎn', + '亵' => ' xiè', + '亴' => ' yòu', + '亳' => ' bó', + '亲' => ' qīn', + '亱' => ' yè', + '仇' => ' chóu', + '仉' => ' zhǎng', + '仡' => ' gē', + '他' => ' tā', + '仠' => ' gǎn', + '仟' => ' qiān', + '仞' => ' rèn', + '仝' => ' tóng', + '仜' => ' hóng', + '仛' => ' tuō', + '仚' => ' xiān', + '仙' => ' xiān', + '付' => ' fù', + '仗' => ' zhàng', + '仕' => ' shì', + '今' => ' jīn', + '仔' => ' zǐ', + '仓' => ' cāng', + '仒' => ' bīng', + '仑' => ' lún', + '仐' => ' sǎn', + '仏' => ' fó', + '从' => ' cóng', + '仍' => ' réng', + '仌' => ' bīng', + '介' => ' jiè', + '俥' => ' chē', + '俧' => ' zhi', + '乇' => ' tuō', + '價' => ' jià', + '儃' => ' chán', + '儂' => ' nóng', + '儁' => ' jùn', + '儀' => ' yí', + '僿' => ' sài', + '僾' => ' ài', + '僽' => ' zhòu', + '僼' => ' fēng', + '僻' => ' pì', + '僺' => ' qiào', + '僸' => ' jìn', + '儅' => ' dàng', + '僷' => ' yè', + '僶' => ' mǐn', + '僵' => ' jiāng', + '僴' => ' xiàn', + '僳' => ' sù', + '僲' => ' xian', + '僱' => ' gù', + '僰' => ' bó', + '僯' => ' lìn', + '僮' => ' tóng', + '億' => ' yì', + '儆' => ' jǐng', + '僬' => ' jiāo', + '儓' => ' tái', + '儝' => ' qióng', + '儜' => ' níng', + '儛' => ' wǔ', + '儚' => ' méng', + '儙' => ' qiàn', + '儘' => ' jǐn', + '儗' => ' nǐ', + '儖' => ' lán', + '儕' => ' chái', + '儔' => ' chóu', + '儒' => ' rú', + '儇' => ' xuān', + '儑' => ' án', + '儐' => ' bīn', + '儏' => ' can', + '儎' => ' zài', + '儍' => ' shǎ', + '儌' => ' jiǎo', + '儋' => ' dān', + '儊' => ' chù', + '儉' => ' jiǎn', + '儈' => ' kuài', + '僭' => ' jiàn', + '僫' => ' è', + '償' => ' cháng', + '僅' => ' jǐn', + '像' => ' xiàng', + '僎' => ' zhuàn', + '働' => ' dòng', + '僌' => ' yíng', + '僋' => ' tàn', + '僊' => ' xiān', + '僉' => ' qiān', + '僈' => ' mán', + '僇' => ' lù', + '僆' => ' liàn', + '僄' => ' piào', + '僑' => ' qiáo', + '僃' => ' bèi', + '僂' => ' lóu', + '僁' => ' xiè', + '僀' => ' dì', + '傿' => ' yàn', + '傾' => ' qīng', + '傽' => ' zhāng', + '傼' => ' hàn', + '傻' => ' shǎ', + '傺' => ' chì', + '僐' => ' shàn', + '僒' => ' jiǒng', + '僪' => ' jú', + '僟' => ' jī', + '僩' => ' xiàn', + '僨' => ' fèn', + '僧' => ' sēng', + '僦' => ' jiù', + '僥' => ' jiǎo', + '僤' => ' dàn', + '僣' => ' tiě', + '僢' => ' chuǎn', + '僡' => ' huì', + '僠' => ' bō', + '僞' => ' wěi', + '僓' => ' tuǐ', + '僝' => ' chán', + '僜' => ' chēng', + '僛' => ' qī', + '僚' => ' liáo', + '僙' => ' guāng', + '僘' => ' chǎng', + '僗' => ' láo', + '僖' => ' xī', + '僕' => ' pú', + '僔' => ' zǔn', + '儞' => ' nǐ', + '儠' => ' liè', + '傸' => ' chuǎng', + '兣' => ' lǐ', + '六' => ' liù', + '公' => ' gōng', + '八' => ' bā', + '兪' => ' yú', + '兩' => ' liǎng', + '全' => ' quán', + '內' => ' nèi', + '兦' => ' wáng', + '入' => ' rù', + '兤' => ' huáng', + '兢' => ' jīng', + '兯' => ' han', + '兠' => ' dōu', + '兟' => ' shēn', + '兞' => ' máo', + '兝' => ' fēn', + '兜' => ' dōu', + '兛' => ' qiān', + '党' => ' dǎng', + '兘' => ' shǐ', + '兗' => ' yǎn', + '兖' => ' yǎn', + '兮' => ' xī', + '兰' => ' lán', + '兔' => ' tù', + '兽' => ' shòu', + '冇' => ' mǎo', + '円' => ' yuán', + '内' => ' nèi', + '冄' => ' rǎn', + '冃' => ' mào', + '冂' => ' jiōng', + '冁' => ' chǎn', + '冀' => ' jì', + '兿' => ' yì', + '兾' => ' jì', + '兼' => ' jiān', + '共' => ' gòng', + '养' => ' yǎng', + '兺' => ' fēn', + '兹' => ' zī', + '典' => ' diǎn', + '具' => ' jù', + '其' => ' qí', + '兵' => ' bīng', + '兴' => ' xìng', + '关' => ' guān', + '兲' => ' tiān', + '兕' => ' sì', + '兓' => ' jīn', + '儡' => ' lěi', + '儭' => ' chèn', + '儷' => ' lì', + '儶' => ' huì', + '儵' => ' shū', + '儴' => ' ráng', + '儳' => ' chán', + '儲' => ' chǔ', + '儱' => ' lǒng', + '儰' => ' wěi', + '儯' => ' téng', + '儮' => ' lì', + '儬' => ' chèn', + '儹' => ' zǎn', + '儫' => ' háo', + '優' => ' yōu', + '儩' => ' sì', + '儨' => ' zhí', + '儧' => ' zǎn', + '儦' => ' biāo', + '儥' => ' yù', + '儤' => ' bào', + '儣' => ' kuǎng', + '儢' => ' lǚ', + '儸' => ' luó', + '儺' => ' nuó', + '兒' => ' ér', + '兇' => ' xiōng', + '兑' => ' duì', + '児' => ' ér', + '兏' => ' cháng', + '兎' => ' tù', + '免' => ' miǎn', + '兌' => ' duì', + '克' => ' kè', + '兊' => ' duì', + '光' => ' guāng', + '先' => ' xiān', + '兆' => ' zhào', + '儻' => ' tǎng', + '充' => ' chōng', + '兄' => ' xiōng', + '元' => ' yuán', + '兂' => ' zān', + '允' => ' yǔn', + '兀' => ' wù', + '儿' => ' ér', + '儾' => ' nàng', + '儽' => ' léi', + '儼' => ' yǎn', + '傹' => ' jìng', + '傷' => ' shāng', + '俨' => ' yǎn', + '倨' => ' jù', + '倲' => ' dōng', + '倱' => ' hùn', + '倰' => ' lèng', + '倯' => ' sōng', + '倮' => ' luǒ', + '倭' => ' wō', + '倬' => ' zhuō', + '倫' => ' lún', + '倪' => ' ní', + '倩' => ' qiàn', + '倧' => ' zōng', + '倴' => ' bèn', + '倦' => ' juàn', + '倥' => ' kōng', + '値' => ' zhí', + '倣' => ' fǎng', + '倢' => ' jié', + '倡' => ' chàng', + '倠' => ' suī', + '借' => ' jiè', + '倞' => ' jìng', + '倝' => ' gàn', + '倳' => ' zì', + '倵' => ' wǔ', + '倛' => ' qī', + '偂' => ' jiān', + '偌' => ' ruò', + '偋' => ' bìng', + '偊' => ' yǔ', + '偉' => ' wěi', + '偈' => ' jì', + '假' => ' jiǎ', + '偆' => ' chǔn', + '偅' => ' zhòng', + '偄' => ' ruǎn', + '偃' => ' yǎn', + '偁' => ' chēng', + '倶' => ' jù', + '偀' => ' yīng', + '倿' => ' qie', + '倾' => ' qīng', + '倽' => ' shà', + '值' => ' zhí', + '倻' => ' yē', + '债' => ' zhài', + '倹' => ' jiǎn', + '倸' => ' cǎi', + '倷' => ' nǎi', + '倜' => ' tì', + '倚' => ' yǐ', + '偎' => ' wēi', + '俴' => ' jiàn', + '俾' => ' bǐ', + '俽' => ' xīn', + '俼' => ' yù', + '俻' => ' bèi', + '俺' => ' ǎn', + '俹' => ' yà', + '俸' => ' fèng', + '俷' => ' fèi', + '俶' => ' chù', + '俵' => ' biào', + '俳' => ' pái', + '倀' => ' chāng', + '俲' => ' xiào', + '俱' => ' jù', + '俰' => ' huò', + '俯' => ' fǔ', + '修' => ' xiū', + '俭' => ' jiǎn', + '俬' => ' sī', + '俫' => ' lái', + '俪' => ' lì', + '俩' => ' liǎ', + '俿' => ' hǔ', + '倁' => ' zhī', + '候' => ' hòu', + '倎' => ' tiǎn', + '倘' => ' tǎng', + '倗' => ' péng', + '倖' => ' xìng', + '倕' => ' chuí', + '倔' => ' jué', + '倓' => ' tán', + '倒' => ' dào', + '們' => ' men', + '倐' => ' shū', + '倏' => ' shū', + '倍' => ' bèi', + '倂' => ' bìng', + '倌' => ' guān', + '個' => ' gè', + '倊' => ' zòng', + '倉' => ' cāng', + '倈' => ' lái', + '倇' => ' wǎn', + '倆' => ' liǎ', + '倅' => ' cuì', + '倄' => ' yáo', + '倃' => ' jiù', + '偍' => ' tí', + '偏' => ' piān', + '傶' => ' zú', + '傑' => ' jié', + '傛' => ' yǒng', + '傚' => ' xiào', + '備' => ' bèi', + '傘' => ' sǎn', + '傗' => ' chù', + '傖' => ' cāng', + '傕' => ' jué', + '傔' => ' qiàn', + '傓' => ' shàn', + '傒' => ' xī', + '傐' => ' hào', + '傝' => ' tàn', + '傏' => ' táng', + '傎' => ' diān', + '傍' => ' bàng', + '傌' => ' mà', + '傋' => ' jiǎng', + '傊' => ' yùn', + '傉' => ' nù', + '傈' => ' lì', + '傇' => ' rǒng', + '傆' => ' yuàn', + '傜' => ' yáo', + '傞' => ' suō', + '傄' => ' xiā', + '傫' => ' lěi', + '債' => ' zhài', + '傴' => ' yǔ', + '傳' => ' chuán', + '傲' => ' ào', + '傱' => ' sǒng', + '傰' => ' bēng', + '傯' => ' zǒng', + '傮' => ' zāo', + '傭' => ' yōng', + '催' => ' cuī', + '傪' => ' cān', + '傟' => ' yǎng', + '傩' => ' nuó', + '储' => ' chǔ', + '傧' => ' bīn', + '傦' => ' gu', + '傥' => ' tǎng', + '傤' => ' zài', + '傣' => ' dǎi', + '傢' => ' jiā', + '傡' => ' bìng', + '傠' => ' fā', + '傅' => ' fù', + '傃' => ' sù', + '偐' => ' yàn', + '停' => ' tíng', + '偦' => ' xǔ', + '健' => ' jiàn', + '偤' => ' yóu', + '偣' => ' yān', + '偢' => ' chǒu', + '偡' => ' zhàn', + '偠' => ' yǎo', + '偟' => ' huáng', + '偞' => ' xiè', + '偝' => ' bèi', + '偛' => ' chā', + '偨' => ' cī', + '做' => ' zuò', + '偙' => ' dì', + '偘' => ' kǎn', + '偗' => ' shěng', + '偖' => ' chě', + '偕' => ' xié', + '偔' => ' è', + '偓' => ' wò', + '偒' => ' tǎng', + '偑' => ' fēng', + '偧' => ' zhā', + '偩' => ' fù', + '傂' => ' zhì', + '偷' => ' tōu', + '傁' => ' sǒu', + '傀' => ' guī', + '偿' => ' cháng', + '偾' => ' fèn', + '偽' => ' wěi', + '偼' => ' jié', + '偻' => ' lóu', + '偺' => ' zá', + '偹' => ' bèi', + '偸' => ' tōu', + '偶' => ' ǒu', + '偪' => ' bī', + '偵' => ' zhēn', + '側' => ' cè', + '偳' => ' duān', + '偲' => ' cāi', + '偱' => ' xún', + '偰' => ' xiè', + '偯' => ' yǐ', + '偮' => ' jí', + '偭' => ' miǎn', + '偬' => ' zǒng', + '偫' => ' zhì', + '么' => ' me', + '乆' => ' jiǔ', + '䪂' => ' gé', + '䮪' => ' chéng', + '䮴' => ' dèng', + '䮳' => ' fán', + '䮲' => ' huáng', + '䮱' => ' zhú', + '䮰' => ' zhé', + '䮯' => ' ào', + '䮮' => ' mài', + '䮭' => ' mì', + '䮬' => ' mò', + '䮫' => ' lóu', + '䮩' => ' gú', + '䮷' => ' dú', + '䮨' => ' zǎi', + '䮧' => ' hàn', + '䮥' => ' lì', + '䮤' => ' hé', + '䮢' => ' zhá', + '䮡' => ' bì', + '䮠' => ' bī', + '䮟' => ' sōu', + '䮞' => ' chǔn', + '䮝' => ' hún', + '䮵' => ' tóng', + '䮸' => ' wò', + '䮛' => ' fù', + '䯆' => ' yì', + '䯐' => ' hái', + '䯏' => ' guā', + '䯎' => ' gàn', + '䯍' => ' líng', + '䯌' => ' kāo', + '䯋' => ' bó', + '䯊' => ' qià', + '䯉' => ' wā', + '䯈' => ' wán', + '䯇' => ' kū', + '䯅' => ' niè', + '䮹' => ' wèi', + '䯄' => ' guā', + '䯂' => ' shēn', + '䯁' => ' luó', + '䯀' => ' niè', + '䮿' => ' jiǎn', + '䮾' => ' lóng', + '䮽' => ' biāo', + '䮼' => ' lín', + '䮻' => ' chì', + '䮺' => ' jì', + '䮜' => ' zhá', + '䮚' => ' lèng', + '䯒' => ' héng', + '䭯' => ' bó', + '䭹' => ' áng', + '䭸' => ' bǎo', + '䭷' => ' máo', + '䭶' => ' qí', + '䭵' => ' fán', + '䭴' => ' huán', + '䭳' => ' wèi', + '䭲' => ' yǐ', + '䭱' => ' bié', + '䭰' => ' bèng', + '䭮' => ' fú', + '䭻' => ' fù', + '䭭' => ' shǒu', + '䭬' => ' qǐ', + '䭫' => ' qǐ', + '䭪' => ' liáng', + '䭩' => ' mó', + '䭨' => ' shuì', + '䭧' => ' mǐ', + '䭦' => ' bō', + '䭥' => ' xuè', + '䭤' => ' qiǎn', + '䭺' => ' ǎng', + '䭼' => ' qí', + '䮙' => ' yù', + '䮍' => ' niǎo', + '䮘' => ' ér', + '䮗' => ' àn', + '䮖' => ' chāng', + '䮕' => ' zhuó', + '䮔' => ' zuī', + '䮓' => ' zhuō', + '䮒' => ' pū', + '䮑' => ' lèi', + '䮏' => ' wú', + '䮎' => ' xì', + '䮋' => ' lì', + '䭽' => ' qún', + '䮊' => ' yí', + '䮉' => ' lú', + '䮈' => ' chí', + '䮇' => ' yù', + '䮄' => ' xuán', + '䮂' => ' bá', + '䮁' => ' pián', + '䮀' => ' bó', + '䭿' => ' yì', + '䭾' => ' tuó', + '䯑' => ' kuāng', + '䯓' => ' kuī', + '䭢' => ' níng', + '䰙' => ' yǐ', + '䰣' => ' wǎng', + '䰢' => ' xìng', + '䰡' => ' chì', + '䰠' => ' shén', + '䰟' => ' hún', + '䰞' => ' zhǔ', + '䰝' => ' zèng', + '䰜' => ' lì', + '䰛' => ' lì', + '䰚' => ' wén', + '䰘' => ' liú', + '䰥' => ' huò', + '䰖' => ' zuǎn', + '䰕' => ' lú', + '䰔' => ' pán', + '䰓' => ' mián', + '䰒' => ' méng', + '䰐' => ' lán', + '䰏' => ' jì', + '䰎' => ' kuì', + '䰍' => ' cì', + '䰌' => ' zǒng', + '䰤' => ' dōng', + '䰦' => ' pǐ', + '䰊' => ' bó', + '䰵' => ' zī', + '䱀' => ' yāng', + '䰿' => ' tuó', + '䰾' => ' bā', + '䰽' => ' bèi', + '䰼' => ' qín', + '䰻' => ' yú', + '䰺' => ' jiè', + '䰹' => ' zé', + '䰸' => ' gōng', + '䰷' => ' bàng', + '䰴' => ' qì', + '䰧' => ' hū', + '䰲' => ' yà', + '䰱' => ' líng', + '䰰' => ' rú', + '䰯' => ' yì', + '䰭' => ' nòu', + '䰬' => ' jú', + '䰫' => ' chāo', + '䰪' => ' mèi', + '䰩' => ' chě', + '䰨' => ' mèi', + '䰋' => ' mán', + '䰉' => ' pán', + '䯔' => ' zé', + '䯠' => ' ǎo', + '䯫' => ' hào', + '䯪' => ' kǎo', + '䯨' => ' qiāo', + '䯧' => ' qǐng', + '䯦' => ' mà', + '䯥' => ' àn', + '䯤' => ' kuài', + '䯣' => ' kuì', + '䯢' => ' mó', + '䯡' => ' jiàn', + '䯟' => ' duì', + '䯭' => ' xiān', + '䯞' => ' kuā', + '䯝' => ' suǐ', + '䯜' => ' tì', + '䯛' => ' wàn', + '䯚' => ' yǎo', + '䯙' => ' pò', + '䯘' => ' huàn', + '䯗' => ' bì', + '䯖' => ' láng', + '䯕' => ' tīng', + '䯬' => ' duǒ', + '䯮' => ' nái', + '䰈' => ' cuó', + '䯼' => ' dí', + '䰇' => ' qí', + '䰆' => ' róu', + '䰄' => ' sāi', + '䰃' => ' péng', + '䰂' => ' cài', + '䰁' => ' fèi', + '䰀' => ' wǒ', + '䯿' => ' zú', + '䯾' => ' tiáo', + '䯽' => ' póu', + '䯺' => ' kuò', + '䯯' => ' suō', + '䯹' => ' xiān', + '䯸' => ' cì', + '䯷' => ' sōng', + '䯶' => ' mán', + '䯵' => ' niè', + '䯴' => ' cháng', + '䯳' => ' sōng', + '䯲' => ' pā', + '䯱' => ' pī', + '䯰' => ' jiè', + '䭣' => ' cí', + '䭡' => ' èn', + '䱂' => ' yǒu', + '䫈' => ' chěn', + '䫒' => ' mén', + '䫑' => ' yī', + '䫐' => ' lín', + '䫏' => ' qī', + '䫎' => ' zhuō', + '䫍' => ' fǔ', + '䫌' => ' pǐ', + '䫋' => ' wēi', + '䫊' => ' péi', + '䫉' => ' mào', + '䫇' => ' rán', + '䫔' => ' qì', + '䫆' => ' chéng', + '䫅' => ' sàn', + '䫄' => ' chuà', + '䫃' => ' chún', + '䫂' => ' duǒ', + '䫁' => ' bì', + '䫀' => ' gěn', + '䪿' => ' shěng', + '䪾' => ' zhěn', + '䪽' => ' hào', + '䫓' => ' wú', + '䫕' => ' dié', + '䪻' => ' biàn', + '䫢' => ' sī', + '䫭' => ' huì', + '䫬' => ' zhěn', + '䫫' => ' lóu', + '䫪' => ' shuǎng', + '䫩' => ' sǎn', + '䫨' => ' ào', + '䫦' => ' gé', + '䫥' => ' kuǐ', + '䫤' => ' míng', + '䫣' => ' xí', + '䫡' => ' yán', + '䫖' => ' chěn', + '䫠' => ' pī', + '䫟' => ' hùn', + '䫞' => ' qiāo', + '䫝' => ' fǔ', + '䫜' => ' āo', + '䫛' => ' hóu', + '䫚' => ' guā', + '䫙' => ' sǎng', + '䫘' => ' hé', + '䫗' => ' xiá', + '䪼' => ' zhuō', + '䪺' => ' gé', + '䫰' => ' lìn', + '䪎' => ' suī', + '䪘' => ' wěi', + '䪗' => ' xiá', + '䪖' => ' pò', + '䪕' => ' jú', + '䪔' => ' bǔ', + '䪓' => ' diē', + '䪒' => ' zhù', + '䪑' => ' tuó', + '䪐' => ' bì', + '䪏' => ' nà', + '䪍' => ' lán', + '䪚' => ' dā', + '䪌' => ' zhàn', + '䪋' => ' wèi', + '䪊' => ' lóng', + '䪉' => ' liè', + '䪈' => ' qiàn', + '䪇' => ' bó', + '䪆' => ' tǐ', + '䪅' => ' dú', + '䪄' => ' huáng', + '䪃' => ' dūn', + '䪙' => ' pò', + '䪛' => ' fān', + '䪹' => ' bāi', + '䪮' => ' chǒu', + '䪸' => ' mín', + '䪷' => ' hòu', + '䪶' => ' jū', + '䪵' => ' yǎ', + '䪴' => ' zhěn', + '䪳' => ' yǔn', + '䪲' => ' kūn', + '䪱' => ' gǎi', + '䪰' => ' yī', + '䪯' => ' yīng', + '䪭' => ' ruǎn', + '䪜' => ' chān', + '䪬' => ' bó', + '䪫' => ' jīng', + '䪩' => ' yín', + '䪨' => ' báo', + '䪧' => ' chí', + '䪦' => ' hóng', + '䪥' => ' xiè', + '䪤' => ' fán', + '䪞' => ' zá', + '䪝' => ' hù', + '䫮' => ' chán', + '䫱' => ' ná', + '䭠' => ' jiǎn', + '䬹' => ' zhì', + '䭃' => ' rěn', + '䭂' => ' yì', + '䭁' => ' zhì', + '䭀' => ' xùn', + '䬿' => ' wěi', + '䬾' => ' tí', + '䬽' => ' shuì', + '䬼' => ' yuàn', + '䬻' => ' jiàn', + '䬺' => ' yàng', + '䬷' => ' shě', + '䭅' => ' hú', + '䬶' => ' èn', + '䬵' => ' gāi', + '䬴' => ' mò', + '䬳' => ' bǎn', + '䬲' => ' gōu', + '䬱' => ' bèn', + '䬰' => ' shào', + '䬯' => ' nián', + '䬮' => ' yí', + '䬬' => ' yǐng', + '䭄' => ' shì', + '䭆' => ' nè', + '䬪' => ' bó', + '䭔' => ' duī', + '䭟' => ' yè', + '䭞' => ' yì', + '䭝' => ' kuài', + '䭛' => ' dàn', + '䭚' => ' chuáng', + '䭙' => ' jǐn', + '䭘' => ' yǐng', + '䭗' => ' yǐng', + '䭖' => ' zhù', + '䭕' => ' zǎn', + '䭓' => ' èn', + '䭇' => ' yē', + '䭒' => ' xī', + '䭑' => ' lián', + '䭐' => ' yàng', + '䭎' => ' yè', + '䭍' => ' hú', + '䭌' => ' hú', + '䭋' => ' bǎo', + '䭊' => ' yǐng', + '䭉' => ' suǐ', + '䭈' => ' jiàn', + '䬫' => ' tí', + '䬨' => ' jiù', + '䫲' => ' hàn', + '䫾' => ' bī', + '䬈' => ' duì', + '䬇' => ' yuàn', + '䬆' => ' lì', + '䬅' => ' lì', + '䬄' => ' xù', + '䬃' => ' sà', + '䬂' => ' xuè', + '䬁' => ' yí', + '䬀' => ' yǒu', + '䫿' => ' chāo', + '䫽' => ' pāo', + '䬊' => ' shà', + '䫼' => ' xuè', + '䫻' => ' yù', + '䫺' => ' hóng', + '䫹' => ' hóng', + '䫸' => ' chāo', + '䫷' => ' è', + '䫶' => ' fán', + '䫵' => ' mián', + '䫴' => ' jìn', + '䫳' => ' dú', + '䬉' => ' huò', + '䬋' => ' léng', + '䬧' => ' yuán', + '䬘' => ' xiāo', + '䬦' => ' dòu', + '䬥' => ' yì', + '䬤' => ' zàn', + '䬣' => ' xì', + '䬡' => ' zhù', + '䬟' => ' liú', + '䬞' => ' táo', + '䬝' => ' héng', + '䬛' => ' bì', + '䬙' => ' yáo', + '䬗' => ' yáng', + '䬌' => ' pōu', + '䬖' => ' héng', + '䬕' => ' xiāng', + '䬔' => ' yú', + '䬓' => ' àn', + '䬒' => ' sōu', + '䬑' => ' wèi', + '䬐' => ' ruí', + '䬏' => ' bù', + '䬎' => ' guó', + '䬍' => ' hū', + '䱁' => ' qiáo', + '䱃' => ' zhì', + '久' => ' jiǔ', + '䵪' => ' gùn', + '䵴' => ' yìng', + '䵳' => ' wèi', + '䵲' => ' qiāo', + '䵱' => ' xì', + '䵰' => ' shāng', + '䵯' => ' tuǎn', + '䵮' => ' yàng', + '䵭' => ' zhè', + '䵬' => ' tà', + '䵫' => ' xūn', + '䵩' => ' lí', + '䵶' => ' qú', + '䵨' => ' máng', + '䵧' => ' zài', + '䵦' => ' xiè', + '䵥' => ' yù', + '䵤' => ' jiǎn', + '䵣' => ' dá', + '䵢' => ' mèi', + '䵡' => ' jiǎn', + '䵠' => ' dí', + '䵟' => ' gǎn', + '䵵' => ' chuā', + '䵷' => ' wā', + '䵝' => ' yì', + '䶅' => ' hé', + '䶏' => ' tì', + '䶎' => ' hē', + '䶍' => ' dì', + '䶌' => ' pào', + '䶋' => ' xī', + '䶊' => ' nǜ', + '䶉' => ' liú', + '䶈' => ' bó', + '䶇' => ' zhòu', + '䶆' => ' zhuī', + '䶄' => ' píng', + '䵹' => ' zhī', + '䶃' => ' hán', + '䶂' => ' zhuó', + '䶁' => ' tà', + '䶀' => ' tà', + '䵿' => ' tiè', + '䵾' => ' fú', + '䵽' => ' cà', + '䵼' => ' shāng', + '䵻' => ' gǔ', + '䵺' => ' tǐng', + '䵞' => ' jìng', + '䵜' => ' nǒng', + '䶑' => ' tì', + '䴵' => ' bǐng', + '䴿' => ' méng', + '䴾' => ' cuó', + '䴽' => ' pí', + '䴼' => ' chàn', + '䴻' => ' lí', + '䴺' => ' bù', + '䴹' => ' guǒ', + '䴸' => ' fū', + '䴷' => ' hún', + '䴶' => ' péng', + '䴴' => ' tiè', + '䵁' => ' qiàng', + '䴳' => ' huá', + '䴲' => ' mò', + '䴱' => ' tuō', + '䴯' => ' hū', + '䴮' => ' shàn', + '䴭' => ' cái', + '䴬' => ' yì', + '䴫' => ' líng', + '䴪' => ' lù', + '䴩' => ' piáo', + '䵀' => ' suǒ', + '䵂' => ' zhí', + '䵛' => ' qiàn', + '䵐' => ' chǎn', + '䵚' => ' tǎo', + '䵙' => ' zhā', + '䵘' => ' shài', + '䵗' => ' bó', + '䵖' => ' qiàn', + '䵕' => ' jù', + '䵔' => ' dǒng', + '䵓' => ' lí', + '䵒' => ' nì', + '䵑' => ' nì', + '䵏' => ' lǎo', + '䵃' => ' kuàng', + '䵎' => ' tuān', + '䵌' => ' xiān', + '䵋' => ' wěi', + '䵊' => ' tuān', + '䵉' => ' tóu', + '䵈' => ' kù', + '䵇' => ' xiàn', + '䵆' => ' méng', + '䵅' => ' áo', + '䵄' => ' bí', + '䶐' => ' wài', + '䶒' => ' qí', + '䴦' => ' yín', + '丠' => ' qiū', + '个' => ' gè', + '丩' => ' jiū', + '丨' => ' gǔn', + '丧' => ' sàng', + '並' => ' bìng', + '严' => ' yán', + '两' => ' liǎng', + '丣' => ' yǒu', + '丢' => ' diū', + '両' => ' liǎng', + '丟' => ' diū', + '丬' => ' qiáng', + '丞' => ' chéng', + '丝' => ' sī', + '东' => ' dōng', + '丛' => ' cóng', + '业' => ' yè', + '丙' => ' bǐng', + '丘' => ' qiū', + '丗' => ' shì', + '世' => ' shì', + '丕' => ' pī', + '丫' => ' yā', + '中' => ' zhōng', + '专' => ' zhuān', + '为' => ' wèi', + '乄' => ' wu', + '乃' => ' nǎi', + '乂' => ' yì', + '乁' => ' yí', + '乀' => ' fú', + '丿' => ' piě', + '举' => ' jǔ', + '丽' => ' lì', + '丼' => ' jǐng', + '主' => ' zhǔ', + '丹' => ' dān', + '丮' => ' jǐ', + '丸' => ' wán', + '丷' => ' ha', + '丶' => ' zhǔ', + '丵' => ' zhuó', + '临' => ' lín', + '丳' => ' chǎn', + '串' => ' chuàn', + '丱' => ' guàn', + '丰' => ' fēng', + '丯' => ' jiè', + '且' => ' qiě', + '丒' => ' chǒu', + '䶓' => ' jì', + '䶟' => ' xiàn', + '䶩' => ' jì', + '䶨' => ' zhān', + '䶧' => ' yǎo', + '䶦' => ' zé', + '䶥' => ' zhā', + '䶤' => ' huá', + '䶣' => ' ái', + '䶢' => ' jiān', + '䶡' => ' shí', + '䶠' => ' jiǎn', + '䶞' => ' qí', + '䶫' => ' yàn', + '䶝' => ' qià', + '䶜' => ' gǔ', + '䶛' => ' là', + '䶚' => ' qǔ', + '䶙' => ' jù', + '䶘' => ' lì', + '䶗' => ' kè', + '䶖' => ' jìn', + '䶕' => ' bà', + '䶔' => ' chí', + '䶪' => ' chà', + '䶬' => ' jiān', + '丑' => ' chǒu', + '丆' => ' hǎn', + '丐' => ' gài', + '丏' => ' miǎn', + '与' => ' yǔ', + '不' => ' bù', + '丌' => ' jī', + '下' => ' xià', + '上' => ' shàng', + '三' => ' sān', + '丈' => ' zhàng', + '万' => ' wàn', + '丅' => ' xià', + '䶮' => ' yǎn', + '丄' => ' shàng', + '七' => ' qī', + '丂' => ' kǎo', + '丁' => ' dīng', + '一' => ' yī', + '䶵' => ' chí', + '䶳' => ' yuè', + '䶲' => ' nán', + '䶱' => ' tóng', + '䶰' => ' jiāo', + '䴧' => ' wēi', + '䴥' => ' jiā', + '䱄' => ' jiè', + '䲋' => ' cén', + '䲕' => ' pū', + '䲔' => ' qíng', + '䲓' => ' yǎn', + '䲒' => ' xiè', + '䲑' => ' yí', + '䲐' => ' lǔ', + '䲏' => ' láo', + '䲎' => ' něi', + '䲍' => ' téng', + '䲌' => ' kuǎn', + '䲊' => ' duò', + '䲗' => ' xián', + '䲉' => ' sī', + '䲆' => ' sū', + '䲅' => ' guī', + '䲄' => ' mín', + '䲃' => ' zǎo', + '䲂' => ' xuán', + '䲁' => ' wèi', + '䲀' => ' zhì', + '䱿' => ' chàn', + '䱾' => ' lóu', + '䲖' => ' chóu', + '䲘' => ' guǎn', + '䱻' => ' huá', + '䲨' => ' hóng', + '䲵' => ' què', + '䲴' => ' zhèn', + '䲳' => ' háng', + '䲲' => ' gōng', + '䲱' => ' fǎng', + '䲰' => ' yùn', + '䲯' => ' jí', + '䲮' => ' yuán', + '䲬' => ' qí', + '䲪' => ' xì', + '䲧' => ' dù', + '䲙' => ' jié', + '䲦' => ' dài', + '䲣' => ' yú', + '䲢' => ' téng', + '䲡' => ' qiū', + '䲠' => ' chūn', + '䲟' => ' yìn', + '䲞' => ' lì', + '䲜' => ' yè', + '䲛' => ' méng', + '䲚' => ' lài', + '䱼' => ' zhǎn', + '䱺' => ' hái', + '䲹' => ' pí', + '䱐' => ' fū', + '䱚' => ' lù', + '䱙' => ' shū', + '䱘' => ' lí', + '䱗' => ' cān', + '䱖' => ' liú', + '䱕' => ' móu', + '䱔' => ' tiáo', + '䱓' => ' tíng', + '䱒' => ' yè', + '䱑' => ' xué', + '䱏' => ' tǒu', + '䱜' => ' cuò', + '䱎' => ' gèng', + '䱍' => ' gèng', + '䱌' => ' yí', + '䱋' => ' gǒng', + '䱊' => ' mǐ', + '䱉' => ' shàn', + '䱈' => ' qí', + '䱇' => ' shàn', + '䱆' => ' shéng', + '䱅' => ' mò', + '䱛' => ' huò', + '䱝' => ' pái', + '䱹' => ' zhǎ', + '䱮' => ' é', + '䱸' => ' sōu', + '䱷' => ' yú', + '䱶' => ' láng', + '䱵' => ' wēng', + '䱴' => ' gèng', + '䱳' => ' zhān', + '䱲' => ' yuán', + '䱱' => ' tí', + '䱰' => ' zhòng', + '䱯' => ' mú', + '䱭' => ' gèng', + '䱞' => ' liú', + '䱬' => ' xū', + '䱫' => ' là', + '䱨' => ' là', + '䱥' => ' zhì', + '䱤' => ' xiàn', + '䱣' => ' zú', + '䱢' => ' zhēng', + '䱡' => ' jú', + '䱠' => ' zhàn', + '䱟' => ' jù', + '䲸' => ' jiè', + '䲺' => ' gàn', + '䴤' => ' shēng', + '䳿' => ' rán', + '䴉' => ' huán', + '䴈' => ' ǎo', + '䴇' => ' líng', + '䴆' => ' pú', + '䴅' => ' fén', + '䴄' => ' shùn', + '䴃' => ' náo', + '䴂' => ' xiàng', + '䴁' => ' yù', + '䴀' => ' tóng', + '䳾' => ' dēng', + '䴋' => ' huán', + '䳽' => ' hè', + '䳻' => ' cán', + '䳺' => ' yàn', + '䳹' => ' kòu', + '䳸' => ' má', + '䳷' => ' cóng', + '䳶' => ' xī', + '䳵' => ' chì', + '䳴' => ' tà', + '䳳' => ' xù', + '䴊' => ' yí', + '䴌' => ' méng', + '䳱' => ' wù', + '䴙' => ' pì', + '䴣' => ' hū', + '䴢' => ' mí', + '䴡' => ' lì', + '䴠' => ' yǎo', + '䴟' => ' huán', + '䴞' => ' dí', + '䴝' => ' chuài', + '䴜' => ' wāi', + '䴛' => ' xiāo', + '䴚' => ' gǎng', + '䴘' => ' tī', + '䴍' => ' yīng', + '䴗' => ' jú', + '䴖' => ' jīng', + '䴕' => ' liè', + '䴔' => ' jiāo', + '䴓' => ' shī', + '䴒' => ' líng', + '䴑' => ' dié', + '䴐' => ' bǎo', + '䴏' => ' yàn', +); \ No newline at end of file diff --git a/vendor/overtrue/pinyin/data/words_4 b/vendor/overtrue/pinyin/data/words_4 new file mode 100644 index 0000000..69f7e3e --- /dev/null +++ b/vendor/overtrue/pinyin/data/words_4 @@ -0,0 +1,8003 @@ + ' lěi', + '䳲' => ' zhèn', + '䳰' => ' bǎo', + '䲻' => ' xuán', + '䳇' => ' wǔ', + '䳒' => ' yuán', + '䳑' => ' yǒu', + '䳐' => ' cì', + '䳏' => ' guì', + '䳎' => ' jiù', + '䳍' => ' gōng', + '䳋' => ' tóng', + '䳊' => ' bá', + '䳉' => ' dàn', + '䳈' => ' bǎo', + '䳆' => ' bái', + '䳔' => ' jú', + '䳅' => ' zhǐ', + '䳄' => ' cí', + '䳃' => ' wǎn', + '䳂' => ' diāo', + '䳁' => ' bó', + '䳀' => ' dié', + '䲿' => ' cí', + '䲾' => ' qiǎo', + '䲽' => ' shí', + '䲼' => ' shēng', + '䳓' => ' lǎo', + '䳕' => ' fú', + '䳯' => ' chóng', + '䳣' => ' yuán', + '䳮' => ' mò', + '䳭' => ' jí', + '䳬' => ' è', + '䳫' => ' kuí', + '䳪' => ' juàn', + '䳩' => ' yāo', + '䳨' => ' huáng', + '䳧' => ' hóu', + '䳦' => ' xuān', + '䳤' => ' biē', + '䳢' => ' qí', + '䳖' => ' niè', + '䳡' => ' yàn', + '䳠' => ' shuì', + '䳟' => ' míng', + '䳞' => ' běng', + '䳝' => ' pǒu', + '䳜' => ' tú', + '䳛' => ' yàn', + '䳚' => ' kàn', + '䳙' => ' xǐng', + '䳘' => ' é', + '䳗' => ' é', + '䍖' => ' fú', + '䍔' => ' gōng', + '冉' => ' rǎn', + '㥍' => ' jì', + '㥛' => ' jí', + '㥚' => ' yú', + '㥖' => ' cōng', + '㥕' => ' lián', + '㥔' => ' yù', + '㥓' => ' qī', + '㥒' => ' cǎi', + '㥐' => ' yuàn', + '㥏' => ' tiǎn', + '㥎' => ' lái', + '㥌' => ' jú', + '㥝' => ' mǐ', + '㥋' => ' yī', + '㥊' => ' péng', + '㥉' => ' chè', + '㥈' => ' dié', + '㥇' => ' cán', + '㥆' => ' tuì', + '㥅' => ' shòu', + '㥄' => ' líng', + '㥃' => ' mèn', + '㥂' => ' tè', + '㥜' => ' wèi', + '㥞' => ' suì', + '㥀' => ' zhí', + '㥭' => ' tài', + '㥷' => ' yè', + '㥶' => ' sè', + '㥵' => ' hùn', + '㥴' => ' yí', + '㥳' => ' yuán', + '㥲' => ' chēn', + '㥱' => ' fěi', + '㥰' => ' sāo', + '㥯' => ' yǐn', + '㥮' => ' zhòu', + '㥬' => ' páng', + '㥟' => ' xié', + '㥪' => ' lóu', + '㥩' => ' duǒ', + '㥨' => ' shuì', + '㥧' => ' shùn', + '㥦' => ' qiè', + '㥥' => ' yú', + '㥣' => ' huì', + '㥢' => ' qiú', + '㥡' => ' chì', + '㥠' => ' xū', + '㥁' => ' dé', + '㤿' => ' yān', + '㥹' => ' fěn', + '㤓' => ' bèn', + '㤟' => ' kǒng', + '㤞' => ' chà', + '㤝' => ' chōng', + '㤜' => ' jué', + '㤛' => ' nín', + '㤚' => ' háng', + '㤘' => ' zhòu', + '㤖' => ' zhù', + '㤕' => ' chù', + '㤔' => ' fù', + '㤒' => ' gāo', + '㤡' => ' lì', + '㤑' => ' yòu', + '㤐' => ' chān', + '㤏' => ' tóng', + '㤎' => ' gē', + '㤍' => ' qiāo', + '㤌' => ' gān', + '㤋' => ' fēn', + '㤊' => ' xiào', + '㤉' => ' qiā', + '㤈' => ' qìn', + '㤠' => ' liè', + '㤢' => ' yù', + '㤾' => ' sàn', + '㤱' => ' fù', + '㤽' => ' chóu', + '㤺' => ' huǎng', + '㤹' => ' qiú', + '㤸' => ' xì', + '㤷' => ' hān', + '㤶' => ' máng', + '㤵' => ' cí', + '㤴' => ' chè', + '㤳' => ' běi', + '㤲' => ' qiè', + '㤰' => ' zuò', + '㤤' => ' yú', + '㤯' => ' jiǒng', + '㤮' => ' guàng', + '㤬' => ' huì', + '㤫' => ' dé', + '㤪' => ' yuàn', + '㤩' => ' kè', + '㤨' => ' gǒng', + '㤧' => ' hóu', + '㤦' => ' lì', + '㤥' => ' hài', + '㥸' => ' mǐn', + '㥺' => ' hé', + '㤆' => ' fàn', + '㧁' => ' qù', + '㧋' => ' xuǎn', + '㧊' => ' pō', + '㧉' => ' gài', + '㧈' => ' yǐn', + '㧇' => ' mǎo', + '㧆' => ' jīn', + '㧅' => ' mù', + '㧄' => ' qiǎn', + '㧃' => ' shōu', + '㧂' => ' tián', + '㧀' => ' jí', + '㧍' => ' fǎng', + '㦿' => ' qiǎn', + '㦾' => ' yí', + '㦽' => ' yù', + '㦼' => ' shuàng', + '㦻' => ' xí', + '㦺' => ' rù', + '㦹' => ' yáng', + '㦸' => ' jǐ', + '㦷' => ' yǒng', + '㦶' => ' dié', + '㧌' => ' mào', + '㧎' => ' yá', + '㦴' => ' gé', + '㧜' => ' liè', + '㧪' => ' guǐ', + '㧩' => ' pèi', + '㧨' => ' qiú', + '㧦' => ' xuàn', + '㧥' => ' xiǎn', + '㧤' => ' chòng', + '㧣' => ' zhū', + '㧢' => ' yīn', + '㧡' => ' hài', + '㧟' => ' kuǎi', + '㧚' => ' wǎ', + '㧏' => ' gāng', + '㧙' => ' bì', + '㧘' => ' zì', + '㧗' => ' zǐ', + '㧖' => ' è', + '㧕' => ' liǔ', + '㧔' => ' guài', + '㧓' => ' guā', + '㧒' => ' yù', + '㧑' => ' huī', + '㧐' => ' sǒng', + '㦵' => ' zhū', + '㦱' => ' wǒ', + '㥼' => ' yìn', + '㦉' => ' yì', + '㦓' => ' xiàn', + '㦒' => ' lí', + '㦑' => ' xiàn', + '㦐' => ' nì', + '㦏' => ' sǔn', + '㦎' => ' huò', + '㦍' => ' è', + '㦌' => ' hū', + '㦋' => ' zhā', + '㦊' => ' huá', + '㦇' => ' lù', + '㦕' => ' lóng', + '㦆' => ' hū', + '㦅' => ' diē', + '㦄' => ' má', + '㦃' => ' chǎn', + '㦂' => ' cháng', + '㦁' => ' lián', + '㦀' => ' féng', + '㥿' => ' ào', + '㥾' => ' nì', + '㥽' => ' cè', + '㦔' => ' yàn', + '㦖' => ' mèn', + '㦰' => ' jiān', + '㦥' => ' xuān', + '㦯' => ' huò', + '㦮' => ' qián', + '㦭' => ' líng', + '㦬' => ' luǒ', + '㦫' => ' zā', + '㦪' => ' xiè', + '㦩' => ' yǐn', + '㦨' => ' lán', + '㦧' => ' cǎn', + '㦦' => ' xì', + '㦤' => ' yì', + '㦗' => ' jīn', + '㦣' => ' wèi', + '㦢' => ' jié', + '㦡' => ' lè', + '㦟' => ' mái', + '㦞' => ' chóu', + '㦝' => ' miǎo', + '㦜' => ' huò', + '㦛' => ' yǔ', + '㦚' => ' biǎn', + '㦘' => ' jī', + '㤇' => ' ǎo', + '㤅' => ' ài', + '㧬' => ' gǒng', + '㡕' => ' yīng', + '㡟' => ' zāo', + '㡞' => ' lóu', + '㡝' => ' fèng', + '㡜' => ' xiè', + '㡛' => ' máng', + '㡚' => ' gōu', + '㡙' => ' bī', + '㡘' => ' lián', + '㡗' => ' tí', + '㡖' => ' chuáng', + '㡔' => ' wù', + '㡡' => ' chú', + '㡓' => ' kūn', + '㡒' => ' zhūn', + '㡑' => ' qiāo', + '㡏' => ' shù', + '㡌' => ' mào', + '㡋' => ' yé', + '㡊' => ' qià', + '㡉' => ' xián', + '㡈' => ' wèn', + '㡇' => ' zhé', + '㡠' => ' zhèng', + '㡢' => ' màn', + '㡄' => ' xún', + '㡱' => ' jiǔ', + '㡻' => ' liào', + '㡺' => ' dàn', + '㡹' => ' jū', + '㡸' => ' zhà', + '㡷' => ' běn', + '㡶' => ' zhǐ', + '㡵' => ' líng', + '㡴' => ' lā', + '㡳' => ' zhǐ', + '㡲' => ' huán', + '㡰' => ' yǔ', + '㡣' => ' lóng', + '㡯' => ' zhái', + '㡮' => ' jí', + '㡭' => ' jì', + '㡫' => ' yì', + '㡪' => ' nié', + '㡩' => ' luán', + '㡨' => ' jiān', + '㡧' => ' zhèng', + '㡦' => ' pīn', + '㡥' => ' yìn', + '㡅' => ' nuǒ', + '㡃' => ' huāng', + '㡽' => ' zhào', + '㠕' => ' wěi', + '㠠' => ' lú', + '㠟' => ' lí', + '㠝' => ' cuán', + '㠜' => ' nì', + '㠛' => ' wò', + '㠚' => ' duì', + '㠙' => ' háo', + '㠘' => ' yǔ', + '㠗' => ' ào', + '㠖' => ' yǐ', + '㠔' => ' bài', + '㠢' => ' huái', + '㠓' => ' méng', + '㠒' => ' biǎo', + '㠑' => ' zuì', + '㠐' => ' jiào', + '㠏' => ' huá', + '㠎' => ' jí', + '㠍' => ' jí', + '㠌' => ' qī', + '㠋' => ' è', + '㠊' => ' qū', + '㠡' => ' niǎo', + '㠣' => ' lì', + '㡂' => ' lì', + '㠶' => ' fán', + '㡁' => ' kuǎ', + '㡀' => ' bì', + '㠿' => ' zī', + '㠾' => ' yuān', + '㠽' => ' tóng', + '㠼' => ' sī', + '㠺' => ' shā', + '㠹' => ' jiè', + '㠸' => ' kù', + '㠷' => ' gé', + '㠵' => ' huāng', + '㠥' => ' lǜ', + '㠴' => ' rèn', + '㠲' => ' bǐ', + '㠱' => ' jì', + '㠯' => ' yǐ', + '㠮' => ' pēng', + '㠭' => ' zhǎn', + '㠪' => ' jù', + '㠨' => ' yù', + '㠧' => ' mǐ', + '㠦' => ' fēng', + '㡼' => ' yì', + '㡾' => ' xiàn', + '㤄' => ' pèi', + '㣑' => ' róng', + '㣟' => ' xì', + '㣞' => ' duó', + '㣝' => ' sōng', + '㣜' => ' xíng', + '㣛' => ' tà', + '㣚' => ' tǒng', + '㣙' => ' dí', + '㣔' => ' dīng', + '㣓' => ' càn', + '㣒' => ' cèng', + '㣐' => ' biàn', + '㣢' => ' tí', + '㣏' => ' jìng', + '㣎' => ' mù', + '㣍' => ' tái', + '㣌' => ' shàn', + '㣈' => ' sì', + '㣇' => ' yì', + '㣆' => ' mí', + '㣅' => ' zàn', + '㣄' => ' qú', + '㣃' => ' yǔ', + '㣠' => ' tāo', + '㣣' => ' shàn', + '㣁' => ' bèi', + '㣵' => ' tà', + '㤃' => ' fáng', + '㤂' => ' jí', + '㤁' => ' miǎn', + '㣿' => ' zhuó', + '㣾' => ' chà', + '㣽' => ' shù', + '㣼' => ' rěn', + '㣻' => ' yì', + '㣷' => ' nìng', + '㣶' => ' zhān', + '㣲' => ' wéi', + '㣤' => ' jiàn', + '㣱' => ' zé', + '㣰' => ' xiè', + '㣯' => ' xiè', + '㣭' => ' zōng', + '㣬' => ' qì', + '㣫' => ' zhǒng', + '㣪' => ' huǎn', + '㣧' => ' yìn', + '㣦' => ' wēi', + '㣥' => ' zhì', + '㣂' => ' è', + '㣀' => ' zhèn', + '㡿' => ' chì', + '㢍' => ' yǐng', + '㢙' => ' qín', + '㢘' => ' lián', + '㢗' => ' qiào', + '㢖' => ' pān', + '㢔' => ' zǒng', + '㢓' => ' yǎo', + '㢒' => ' chá', + '㢑' => ' tuí', + '㢏' => ' tóu', + '㢎' => ' zhé', + '㢋' => ' chǐ', + '㢛' => ' yàn', + '㢊' => ' ǎi', + '㢉' => ' chá', + '㢈' => ' tuí', + '㢆' => ' chán', + '㢅' => ' lòng', + '㢄' => ' dòu', + '㢃' => ' láng', + '㢂' => ' yǎn', + '㢁' => ' chǐ', + '㢀' => ' cì', + '㢚' => ' lǔ', + '㢜' => ' kàng', + '㢾' => ' juàn', + '㢯' => ' diāo', + '㢽' => ' ěr', + '㢼' => ' biè', + '㢻' => ' ruì', + '㢺' => ' xián', + '㢸' => ' bì', + '㢶' => ' bì', + '㢵' => ' xié', + '㢳' => ' lú', + '㢲' => ' xùn', + '㢰' => ' bì', + '㢮' => ' chí', + '㢝' => ' sū', + '㢬' => ' hóng', + '㢩' => ' dì', + '㢨' => ' hàn', + '㢧' => ' juàn', + '㢥' => ' dòng', + '㢣' => ' jìng', + '㢡' => ' jiǎng', + '㢠' => ' jiǒng', + '㢟' => ' chān', + '㢞' => ' yì', + '㧫' => ' ér', + '㧭' => ' qióng', + '㠈' => ' rùn', + '㬭' => ' jiào', + '㬸' => ' tiāo', + '㬶' => ' hào', + '㬵' => ' xiáo', + '㬴' => ' hǒng', + '㬳' => ' wǔ', + '㬲' => ' tiān', + '㬱' => ' tì', + '㬰' => ' yú', + '㬯' => ' liè', + '㬮' => ' nàn', + '㬬' => ' jù', + '㬻' => ' huāng', + '㬫' => ' yàn', + '㬪' => ' dié', + '㬩' => ' huì', + '㬧' => ' bó', + '㬦' => ' yuè', + '㬥' => ' pù', + '㬤' => ' qī', + '㬣' => ' duì', + '㬡' => ' líng', + '㬠' => ' shài', + '㬹' => ' zhēng', + '㬼' => ' fù', + '㬞' => ' hè', + '㭒' => ' sì', + '㭞' => ' liè', + '㭝' => ' qiú', + '㭜' => ' róng', + '㭛' => ' pài', + '㭚' => ' lǚ', + '㭙' => ' zhé', + '㭘' => ' gé', + '㭕' => ' qū', + '㭔' => ' lú', + '㭓' => ' biàn', + '㭑' => ' mèi', + '㬿' => ' tūn', + '㭏' => ' wěi', + '㭎' => ' gāng', + '㭌' => ' móu', + '㭋' => ' bàng', + '㭉' => ' huá', + '㭈' => ' jué', + '㭇' => ' yuàn', + '㭄' => ' xìn', + '㭂' => ' jiǎo', + '㭁' => ' réng', + '㬟' => ' fēn', + '㬝' => ' céng', + '㭠' => ' xiǎn', + '㫯' => ' mào', + '㫻' => ' kùn', + '㫹' => ' shù', + '㫸' => ' jiē', + '㫷' => ' jī', + '㫶' => ' zhǒu', + '㫵' => ' fēi', + '㫳' => ' chén', + '㫲' => ' bèi', + '㫱' => ' nǎn', + '㫰' => ' làng', + '㫫' => ' xiǎn', + '㫽' => ' lù', + '㫨' => ' ān', + '㫧' => ' bào', + '㫦' => ' tāo', + '㫥' => ' mǐng', + '㫤' => ' chǎng', + '㫢' => ' qù', + '㫟' => ' yán', + '㫝' => ' dī', + '㫜' => ' tǎn', + '㫛' => ' guàng', + '㫼' => ' dié', + '㬂' => ' yú', + '㬜' => ' jìn', + '㬏' => ' lì', + '㬛' => ' xī', + '㬚' => ' chè', + '㬙' => ' wěi', + '㬘' => ' sù', + '㬗' => ' xiàn', + '㬕' => ' yáng', + '㬔' => ' háo', + '㬓' => ' piào', + '㬒' => ' mǎng', + '㬐' => ' jìn', + '㬎' => ' xiǎn', + '㬃' => ' tái', + '㬍' => ' bó', + '㬌' => ' jìng', + '㬋' => ' hóu', + '㬊' => ' huàn', + '㬉' => ' nuǎn', + '㬈' => ' wēn', + '㬇' => ' huàn', + '㬆' => ' mǐn', + '㬅' => ' màn', + '㬄' => ' chàn', + '㭟' => ' gǒng', + '㭡' => ' xì', + '㫙' => ' fú', + '㮰' => ' pí', + '㮾' => ' lǎng', + '㮺' => ' běn', + '㮹' => ' zhǐ', + '㮸' => ' sòng', + '㮷' => ' jī', + '㮶' => ' shuò', + '㮵' => ' zhān', + '㮳' => ' zhèn', + '㮲' => ' chōu', + '㮱' => ' shēn', + '㮯' => ' hún', + '㯀' => ' xuàn', + '㮮' => ' jié', + '㮭' => ' xián', + '㮬' => ' wēng', + '㮫' => ' hé', + '㮪' => ' qióng', + '㮩' => ' xī', + '㮨' => ' jì', + '㮧' => ' wū', + '㮦' => ' suǒ', + '㮥' => ' bèn', + '㮿' => ' bì', + '㯁' => ' péi', + '㮢' => ' hóu', + '㯏' => ' gǔ', + '㯛' => ' hú', + '㯚' => ' tā', + '㯙' => ' zhé', + '㯘' => ' kuǎn', + '㯗' => ' xián', + '㯖' => ' dé', + '㯕' => ' xī', + '㯔' => ' cuì', + '㯓' => ' tà', + '㯐' => ' fǎng', + '㯎' => ' nèn', + '㯂' => ' dài', + '㯍' => ' jiǎng', + '㯌' => ' chuán', + '㯋' => ' jiǒng', + '㯊' => ' hén', + '㯉' => ' huò', + '㯈' => ' sù', + '㯇' => ' bì', + '㯆' => ' chǎn', + '㯅' => ' pí', + '㯄' => ' zhī', + '㮣' => ' gài', + '㮡' => ' guàn', + '㭢' => ' xīn', + '㭲' => ' jí', + '㮀' => ' hán', + '㭿' => ' áng', + '㭾' => ' jué', + '㭽' => ' dǐ', + '㭼' => ' tà', + '㭻' => ' táng', + '㭺' => ' yǎn', + '㭹' => ' xián', + '㭸' => ' tú', + '㭴' => ' jiān', + '㭱' => ' hé', + '㮂' => ' jú', + '㭰' => ' zuī', + '㭯' => ' zhé', + '㭮' => ' zuò', + '㭭' => ' bā', + '㭬' => ' zhuó', + '㭫' => ' cuó', + '㭪' => ' fū', + '㭩' => ' liè', + '㭨' => ' xié', + '㭤' => ' niǎo', + '㮁' => ' xiáo', + '㮃' => ' wēi', + '㮟' => ' niǎn', + '㮔' => ' chòng', + '㮞' => ' jié', + '㮝' => ' hé', + '㮜' => ' zāng', + '㮛' => ' chí', + '㮚' => ' lì', + '㮙' => ' è', + '㮘' => ' máo', + '㮗' => ' qín', + '㮖' => ' jiá', + '㮕' => ' ruǎn', + '㮓' => ' gèn', + '㮄' => ' bǎng', + '㮒' => ' yān', + '㮑' => ' chā', + '㮐' => ' shěng', + '㮏' => ' nài', + '㮌' => ' mián', + '㮋' => ' yǒu', + '㮈' => ' nài', + '㮇' => ' tiàn', + '㮆' => ' niè', + '㮅' => ' zhuī', + '㫚' => ' hū', + '㫘' => ' mì', + '㧮' => ' hū', + '㨱' => ' yáo', + '㨻' => ' cán', + '㨺' => ' miǎn', + '㨹' => ' huì', + '㨸' => ' mà', + '㨷' => ' jìn', + '㨶' => ' dǎo', + '㨵' => ' jiǎn', + '㨴' => ' jiàn', + '㨳' => ' guǐ', + '㨲' => ' cè', + '㨰' => ' gǔn', + '㨽' => ' pì', + '㨯' => ' huò', + '㨮' => ' dōu', + '㨭' => ' hù', + '㨫' => ' lǎn', + '㨪' => ' huàng', + '㨩' => ' jié', + '㨨' => ' chōu', + '㨧' => ' bèn', + '㨦' => ' sǒng', + '㨥' => ' nà', + '㨼' => ' lüè', + '㨾' => ' yàng', + '㨢' => ' pī', + '㩍' => ' cè', + '㩚' => ' méng', + '㩙' => ' sāi', + '㩘' => ' nǐ', + '㩗' => ' xí', + '㩖' => ' qiáng', + '㩔' => ' tún', + '㩓' => ' huǐ', + '㩒' => ' qín', + '㩐' => ' dèn', + '㩎' => ' yè', + '㩌' => ' fèi', + '㨿' => ' jù', + '㩋' => ' xiāo', + '㩊' => ' xuān', + '㩉' => ' dá', + '㩈' => ' yǔn', + '㩇' => ' huò', + '㩆' => ' jiù', + '㩄' => ' shāi', + '㩃' => ' qiān', + '㩁' => ' què', + '㩀' => ' jù', + '㨤' => ' huì', + '㨡' => ' hún', + '㩜' => ' lǎn', + '㧺' => ' tà', + '㨅' => ' nèi', + '㨄' => ' zhōu', + '㨃' => ' duǐ', + '㨂' => ' dǒng', + '㨁' => ' zhì', + '㨀' => ' bǐng', + '㧾' => ' hū', + '㧽' => ' gù', + '㧼' => ' biào', + '㧻' => ' zhuó', + '㧹' => ' tè', + '㨇' => ' pó', + '㧸' => ' pēng', + '㧷' => ' tùn', + '㧶' => ' kēng', + '㧵' => ' póu', + '㧴' => ' wǒ', + '㧳' => ' zhuò', + '㧲' => ' sǎn', + '㧱' => ' chèn', + '㧰' => ' lì', + '㧯' => ' lǎo', + '㨆' => ' lǐn', + '㨈' => ' jǐ', + '㨠' => ' mì', + '㨕' => ' yíng', + '㨟' => ' hāi', + '㨞' => ' sù', + '㨝' => ' xiē', + '㨜' => ' qián', + '㨛' => ' shǎn', + '㨚' => ' xún', + '㨙' => ' xié', + '㨘' => ' xǐng', + '㨗' => ' jié', + '㨖' => ' zhì', + '㨔' => ' hàn', + '㨉' => ' mín', + '㨓' => ' láo', + '㨒' => ' kuī', + '㨑' => ' zōng', + '㨐' => ' bǔ', + '㨏' => ' tān', + '㨎' => ' rú', + '㨍' => ' bāng', + '㨌' => ' gòu', + '㨋' => ' chě', + '㨊' => ' wěi', + '㩛' => ' tuán', + '㩝' => ' háo', + '㫗' => ' hòu', + '㪦' => ' yìn', + '㪱' => ' huàn', + '㪯' => ' chá', + '㪮' => ' líng', + '㪭' => ' lú', + '㪬' => ' chuō', + '㪫' => ' ruì', + '㪪' => ' sà', + '㪩' => ' sù', + '㪨' => ' shàn', + '㪧' => ' xī', + '㪥' => ' zhā', + '㪵' => ' bàn', + '㪤' => ' bì', + '㪣' => ' xiāo', + '㪢' => ' shǎo', + '㪡' => ' kài', + '㪠' => ' qiān', + '㪞' => ' mào', + '㪝' => ' liàn', + '㪜' => ' chuǎi', + '㪛' => ' zhěn', + '㪚' => ' sàn', + '㪴' => ' jiá', + '㪶' => ' hú', + '㪘' => ' liǎn', + '㫄' => ' páng', + '㫔' => ' gèn', + '㫓' => ' qǐ', + '㫒' => ' gǒng', + '㫑' => ' zhǐ', + '㫐' => ' yǎo', + '㫏' => ' yǎo', + '㫎' => ' huī', + '㫍' => ' yóu', + '㫊' => ' yǐ', + '㫅' => ' chá', + '㫃' => ' yǎn', + '㪷' => ' dǒu', + '㫂' => ' zhù', + '㫁' => ' duàn', + '㫀' => ' dǐng', + '㪿' => ' zhé', + '㪾' => ' luò', + '㪽' => ' suǒ', + '㪼' => ' kě', + '㪻' => ' juàn', + '㪺' => ' jū', + '㪹' => ' lǒu', + '㪙' => ' kě', + '㪗' => ' tǒu', + '㩞' => ' cì', + '㩮' => ' qián', + '㩸' => ' dié', + '㩷' => ' qiān', + '㩶' => ' niè', + '㩵' => ' è', + '㩴' => ' jú', + '㩳' => ' sǒng', + '㩲' => ' kǔn', + '㩱' => ' jué', + '㩰' => ' jiǎo', + '㩯' => ' pó', + '㩭' => ' bó', + '㩻' => ' qī', + '㩪' => ' xié', + '㩩' => ' qǐng', + '㩨' => ' huì', + '㩧' => ' bó', + '㩦' => ' xié', + '㩤' => ' fū', + '㩢' => ' miè', + '㩡' => ' luǒ', + '㩠' => ' āo', + '㩟' => ' zhài', + '㩹' => ' dié', + '㩼' => ' zhī', + '㪖' => ' lù', + '㪋' => ' hàn', + '㪕' => ' diào', + '㪔' => ' sàn', + '㪓' => ' chuái', + '㪒' => ' ní', + '㪑' => ' yè', + '㪐' => ' lù', + '㪏' => ' bǐ', + '㪎' => ' shǎn', + '㪍' => ' bó', + '㪌' => ' tǒng', + '㪊' => ' qún', + '㩽' => ' qí', + '㪉' => ' hé', + '㪈' => ' guì', + '㪇' => ' xiàn', + '㪆' => ' dǐ', + '㪄' => ' fú', + '㪃' => ' hé', + '㪂' => ' kū', + '㪁' => ' qín', + '㪀' => ' yú', + '㩿' => ' kū', + '㩾' => ' zhuì', + '㠉' => ' tóng', + '㠇' => ' jiù', + '㯝' => ' lù', + '㕯' => ' nè', + '㕺' => ' xiāo', + '㕹' => ' fá', + '㕸' => ' lì', + '㕷' => ' huà', + '㕶' => ' ň', + '㕵' => ' guó', + '㕴' => ' chén', + '㕲' => ' huá', + '㕱' => ' yóu', + '㕰' => ' xuè', + '㕮' => ' fǔ', + '㕽' => ' sì', + '㕭' => ' āo', + '㕬' => ' gōng', + '㕫' => ' fǎng', + '㕪' => ' dǎn', + '㕩' => ' pàng', + '㕨' => ' fàn', + '㕧' => ' xī', + '㕦' => ' huà', + '㕥' => ' shēn', + '㕤' => ' qiú', + '㕻' => ' pǒu', + '㖀' => ' lè', + '㕢' => ' gài', + '㖔' => ' tǔn', + '㖣' => ' tòu', + '㖢' => ' qí', + '㖡' => ' yè', + '㖠' => ' nuò', + '㖟' => ' shòu', + '㖞' => ' wāi', + '㖘' => ' zhēn', + '㖗' => ' yín', + '㖖' => ' niè', + '㖕' => ' niè', + '㖓' => ' hóng', + '㖁' => ' lìn', + '㖒' => ' tí', + '㖑' => ' xiè', + '㖐' => ' wěi', + '㖏' => ' niè', + '㖊' => ' xún', + '㖇' => ' ér', + '㖆' => ' qú', + '㖅' => ' xù', + '㖃' => ' hǒu', + '㖂' => ' yì', + '㕣' => ' yǎn', + '㕡' => ' hé', + '㖥' => ' jùn', + '㔴' => ' yì', + '㔿' => ' zòu', + '㔾' => ' xiān', + '㔽' => ' yào', + '㔼' => ' sǔn', + '㔺' => ' shì', + '㔹' => ' lè', + '㔸' => ' tī', + '㔷' => ' lòu', + '㔶' => ' gòng', + '㔵' => ' xuǎn', + '㔳' => ' guǐ', + '㕂' => ' yín', + '㔱' => ' yǔ', + '㔰' => ' héng', + '㔯' => ' xuán', + '㔮' => ' nuó', + '㔭' => ' chè', + '㔪' => ' lǔ', + '㔩' => ' è', + '㔨' => ' bèi', + '㔧' => ' lǜ', + '㔦' => ' yǎng', + '㕁' => ' què', + '㕃' => ' xī', + '㕠' => ' shuāng', + '㕑' => ' chú', + '㕟' => ' kuì', + '㕞' => ' shuā', + '㕜' => ' shǐ', + '㕛' => ' xù', + '㕚' => ' zhǎo', + '㕙' => ' jùn', + '㕗' => ' yòu', + '㕖' => ' sù', + '㕕' => ' huàn', + '㕒' => ' wēi', + '㕐' => ' xiě', + '㕄' => ' zhǐ', + '㕏' => ' chú', + '㕎' => ' kè', + '㕌' => ' ài', + '㕋' => ' qín', + '㕊' => ' fū', + '㕉' => ' kè', + '㕈' => ' yǐ', + '㕇' => ' lā', + '㕆' => ' hù', + '㕅' => ' jiá', + '㖤' => ' hán', + '㖦' => ' dǒng', + '㔤' => ' xìn', + '㗼' => ' yè', + '㘇' => ' róng', + '㘆' => ' tǎi', + '㘅' => ' xián', + '㘄' => ' lēng', + '㘂' => ' cí', + '㘁' => ' yì', + '㘀' => ' zuò', + '㗿' => ' hé', + '㗾' => ' xuē', + '㗽' => ' xiǎng', + '㗻' => ' guō', + '㘉' => ' zhì', + '㗷' => ' sǎi', + '㗶' => ' pō', + '㗵' => ' xù', + '㗴' => ' yán', + '㗳' => ' tǎ', + '㗲' => ' huò', + '㗱' => ' jí', + '㗰' => ' cóng', + '㗭' => ' xī', + '㗬' => ' zhā', + '㘈' => ' yì', + '㘊' => ' xī', + '㗩' => ' xī', + '㘚' => ' hǎn', + '㘥' => ' yóu', + '㘤' => ' wān', + '㘣' => ' xuán', + '㘢' => ' wěng', + '㘡' => ' xiá', + '㘠' => ' bì', + '㘞' => ' huò', + '㘝' => ' niè', + '㘜' => ' chǐ', + '㘛' => ' kān', + '㘙' => ' yán', + '㘋' => ' xián', + '㘗' => ' qū', + '㘖' => ' yán', + '㘕' => ' hǎn', + '㘔' => ' sǎi', + '㘓' => ' lán', + '㘑' => ' lì', + '㘐' => ' pào', + '㘎' => ' hǎn', + '㘍' => ' jí', + '㘌' => ' jù', + '㗫' => ' qiè', + '㗨' => ' xiē', + '㖧' => ' hūn', + '㖺' => ' chǔn', + '㗄' => ' kū', + '㗃' => ' wěn', + '㗂' => ' shěng', + '㗁' => ' è', + '㗀' => ' yōu', + '㖿' => ' xié', + '㖾' => ' è', + '㖽' => ' zé', + '㖼' => ' duǒ', + '㖻' => ' niú', + '㖹' => ' pēn', + '㗆' => ' gé', + '㖸' => ' xué', + '㖷' => ' shí', + '㖶' => ' yān', + '㖵' => ' gé', + '㖮' => ' lún', + '㖭' => ' tiǎn', + '㖫' => ' líng', + '㖪' => ' huò', + '㖩' => ' jū', + '㖨' => ' lù', + '㗅' => ' hú', + '㗇' => ' xiá', + '㗧' => ' zhì', + '㗗' => ' bēi', + '㗦' => ' láo', + '㗥' => ' kuò', + '㗤' => ' cù', + '㗣' => ' tì', + '㗢' => ' hóng', + '㗜' => ' xiù', + '㗛' => ' xiào', + '㗚' => ' lì', + '㗙' => ' chū', + '㗘' => ' bó', + '㗖' => ' dàn', + '㗈' => ' màn', + '㗕' => ' gòu', + '㗔' => ' qiān', + '㗓' => ' zhuī', + '㗒' => ' ài', + '㗑' => ' bai', + '㗏' => ' wāi', + '㗌' => ' zhì', + '㗋' => ' hóu', + '㗊' => ' jí', + '㗉' => ' lüè', + '㔥' => ' bài', + '㔣' => ' léi', + '㘧' => ' xù', + '㑯' => ' xǔ', + '㑻' => ' mà', + '㑺' => ' jùn', + '㑹' => ' huì', + '㑸' => ' yáo', + '㑷' => ' tài', + '㑶' => ' xì', + '㑵' => ' hàn', + '㑳' => ' zhòu', + '㑱' => ' rǎo', + '㑰' => ' huì', + '㑮' => ' hún', + '㑽' => ' táng', + '㑪' => ' chái', + '㑩' => ' luó', + '㑨' => ' yǔ', + '㑧' => ' jì', + '㑦' => ' lì', + '㑥' => ' yì', + '㑤' => ' miáo', + '㑣' => ' lán', + '㑟' => ' běng', + '㑞' => ' yìng', + '㑼' => ' lüè', + '㑾' => ' yáo', + '㑜' => ' yì', + '㒌' => ' qióng', + '㒙' => ' mián', + '㒗' => ' tái', + '㒖' => ' ǒu', + '㒕' => ' yāng', + '㒔' => ' shú', + '㒓' => ' tà', + '㒒' => ' pú', + '㒑' => ' kuǐ', + '㒎' => ' sà', + '㒍' => ' léi', + '㒊' => ' sè', + '㑿' => ' zhào', + '㒉' => ' tǎng', + '㒈' => ' hàn', + '㒇' => ' wǔ', + '㒆' => ' chì', + '㒅' => ' qǐ', + '㒄' => ' rǎn', + '㒃' => ' èr', + '㒂' => ' zhuó', + '㒁' => ' yǔ', + '㒀' => ' zhāi', + '㑝' => ' lòng', + '㑛' => ' sù', + '㒛' => ' diào', + '㐫' => ' xiōng', + '㐵' => ' rú', + '㐴' => ' pān', + '㐳' => ' wù', + '㐲' => ' dài', + '㐱' => ' zhěn', + '㐰' => ' xìn', + '㐯' => ' yōng', + '㐮' => ' xiāng', + '㐭' => ' lǐn', + '㐬' => ' liú', + '㐩' => ' xíng', + '㐸' => ' qiàn', + '㐨' => ' xù', + '㐤' => ' dān', + '㐡' => ' nuò', + '㐜' => ' chóu', + '㐖' => ' xié', + '㐌' => ' yí', + '㐆' => ' yǐn', + '㐅' => ' wǔ', + '㐄' => ' kuà', + '㐁' => ' tiàn', + '㐷' => ' mǎ', + '㐹' => ' yì', + '㑚' => ' nuó', + '㑊' => ' yì', + '㑙' => ' dié', + '㑘' => ' jiè', + '㑗' => ' shēn', + '㑔' => ' xǔ', + '㑐' => ' shū', + '㑏' => ' zhù', + '㑎' => ' nǎo', + '㑍' => ' lèi', + '㑌' => ' kuāng', + '㑋' => ' qióng', + '㑉' => ' sù', + '㐺' => ' yín', + '㑈' => ' dòng', + '㑇' => ' zhòu', + '㑅' => ' zuò', + '㑄' => ' wǔ', + '㑃' => ' ǎo', + '㑂' => ' fǎng', + '㑁' => ' zhuō', + '㐽' => ' fēng', + '㐼' => ' chèng', + '㐻' => ' nèi', + '㒚' => ' yìn', + '㒜' => ' yǔ', + '㔢' => ' jué', + '㓴' => ' ruǎn', + '㔀' => ' lüè', + '㓾' => ' xī', + '㓽' => ' chóng', + '㓼' => ' chì', + '㓺' => ' jiān', + '㓹' => ' ruì', + '㓸' => ' zhuō', + '㓷' => ' yì', + '㓶' => ' qiè', + '㓵' => ' è', + '㓳' => ' dī', + '㔂' => ' lín', + '㓲' => ' piān', + '㓱' => ' tóu', + '㓰' => ' huà', + '㓯' => ' lí', + '㓭' => ' shé', + '㓪' => ' lǎng', + '㓩' => ' xuān', + '㓨' => ' cí', + '㓧' => ' gān', + '㓦' => ' bāi', + '㔁' => ' dēng', + '㔃' => ' jué', + '㓤' => ' qià', + '㔓' => ' jiǎn', + '㔡' => ' biē', + '㔠' => ' xiā', + '㔞' => ' kuài', + '㔝' => ' liǎng', + '㔜' => ' bá', + '㔚' => ' jié', + '㔙' => ' bēng', + '㔘' => ' chù', + '㔗' => ' fèi', + '㔕' => ' jí', + '㔒' => ' xí', + '㔄' => ' sù', + '㔑' => ' chì', + '㔏' => ' lì', + '㔎' => ' xiè', + '㔍' => ' chuā', + '㔌' => ' zòu', + '㔋' => ' jiān', + '㔊' => ' zhǎn', + '㔉' => ' zhǔ', + '㔆' => ' zàn', + '㔅' => ' xiào', + '㓥' => ' tāng', + '㓣' => ' qià', + '㒝' => ' miè', + '㒲' => ' cái', + '㒿' => ' lí', + '㒾' => ' shì', + '㒽' => ' quān', + '㒼' => ' mán', + '㒻' => ' mào', + '㒸' => ' suì', + '㒷' => ' guǎ', + '㒵' => ' mào', + '㒴' => ' gǔ', + '㒳' => ' liǎng', + '㒰' => ' quán', + '㓂' => ' kòu', + '㒫' => ' jì', + '㒩' => ' luǒ', + '㒧' => ' lì', + '㒦' => ' lěi', + '㒥' => ' fēng', + '㒤' => ' chè', + '㒡' => ' yóu', + '㒠' => ' xiè', + '㒟' => ' niǎo', + '㒞' => ' jùn', + '㓁' => ' wǎng', + '㓃' => ' dù', + '㓢' => ' luò', + '㓔' => ' xiè', + '㓡' => ' fǔ', + '㓠' => ' diàn', + '㓟' => ' pí', + '㓞' => ' qià', + '㓝' => ' xíng', + '㓜' => ' yòu', + '㓚' => ' gōng', + '㓘' => ' sù', + '㓗' => ' jié', + '㓖' => ' bì', + '㓓' => ' nán', + '㓄' => ' zhèn', + '㓑' => ' xìng', + '㓐' => ' lù', + '㓏' => ' jiǒng', + '㓎' => ' qīn', + '㓌' => ' chēng', + '㓋' => ' gòng', + '㓊' => ' dòng', + '㓉' => ' huò', + '㓈' => ' bìng', + '㓅' => ' tīng', + '㘦' => ' qín', + '㘨' => ' niè', + '㠆' => ' dān', + '㝦' => ' jùn', + '㝲' => ' qǐn', + '㝱' => ' mèng', + '㝰' => ' mián', + '㝯' => ' qiáo', + '㝮' => ' cuì', + '㝭' => ' xǐng', + '㝫' => ' lóng', + '㝪' => ' diàn', + '㝩' => ' kāng', + '㝧' => ' wěn', + '㝥' => ' mǐ', + '㝵' => ' dé', + '㝤' => ' gòu', + '㝣' => ' yì', + '㝢' => ' yǔ', + '㝡' => ' zuì', + '㝠' => ' míng', + '㝟' => ' máo', + '㝞' => ' yā', + '㝝' => ' lín', + '㝛' => ' sù', + '㝚' => ' yán', + '㝴' => ' wán', + '㝶' => ' ài', + '㝘' => ' yóng', + '㞄' => ' ān', + '㞏' => ' zhé', + '㞎' => ' bǎ', + '㞌' => ' yùn', + '㞋' => ' niǎn', + '㞊' => ' xù', + '㞉' => ' zā', + '㞈' => ' bō', + '㞇' => ' wěi', + '㞆' => ' jǐ', + '㞅' => ' luò', + '㞃' => ' jì', + '㝸' => ' biàn', + '㞂' => ' tuǐ', + '㞁' => ' yào', + '㞀' => ' huī', + '㝿' => ' bǒ', + '㝾' => ' zuǒ', + '㝽' => ' chuí', + '㝼' => ' yū', + '㝻' => ' jǐn', + '㝺' => ' lián', + '㝹' => ' nóu', + '㝙' => ' yín', + '㝗' => ' láng', + '㞑' => ' wěi', + '㜯' => ' xī', + '㜹' => ' qú', + '㜸' => ' niè', + '㜷' => ' mí', + '㜶' => ' wǎn', + '㜵' => ' niǎo', + '㜴' => ' mèng', + '㜳' => ' huái', + '㜲' => ' wěng', + '㜱' => ' zhì', + '㜰' => ' yuè', + '㜮' => ' làn', + '㜻' => ' liàn', + '㜫' => ' mà', + '㜪' => ' shēn', + '㜩' => ' ào', + '㜨' => ' nái', + '㜧' => ' yuè', + '㜦' => ' xín', + '㜥' => ' mái', + '㜤' => ' niǎn', + '㜣' => ' rán', + '㜡' => ' cōng', + '㜺' => ' zàn', + '㜼' => ' zhí', + '㝖' => ' yí', + '㝊' => ' shǒu', + '㝕' => ' níng', + '㝔' => ' yǎo', + '㝓' => ' kè', + '㝑' => ' máng', + '㝐' => ' róng', + '㝏' => ' jiè', + '㝎' => ' dìng', + '㝍' => ' xiě', + '㝌' => ' jiù', + '㝋' => ' liǎo', + '㝉' => ' zhù', + '㜽' => ' zǐ', + '㝈' => ' luán', + '㝇' => ' chún', + '㝅' => ' gòu', + '㝄' => ' chún', + '㝃' => ' miǎn', + '㝂' => ' zhì', + '㝁' => ' xuān', + '㝀' => ' hào', + '㜿' => ' xù', + '㜾' => ' hái', + '㞐' => ' jū', + '㞒' => ' xiè', + '㜞' => ' qiàn', + '㟚' => ' qí', + '㟧' => ' è', + '㟦' => ' yùn', + '㟤' => ' lù', + '㟢' => ' qí', + '㟠' => ' gǎng', + '㟟' => ' xiàng', + '㟞' => ' zhàn', + '㟝' => ' péi', + '㟜' => ' mí', + '㟛' => ' zàn', + '㟙' => ' zé', + '㟩' => ' mín', + '㟘' => ' tiáo', + '㟖' => ' lòng', + '㟔' => ' hán', + '㟓' => ' qí', + '㟒' => ' qūn', + '㟑' => ' bó', + '㟐' => ' mǎng', + '㟏' => ' hán', + '㟎' => ' tuǒ', + '㟍' => ' láng', + '㟨' => ' duān', + '㟪' => ' wēi', + '㟋' => ' duì', + '㟹' => ' láo', + '㠅' => ' fú', + '㠄' => ' xí', + '㠂' => ' áo', + '㠀' => ' dǎo', + '㟿' => ' mǎng', + '㟾' => ' yōng', + '㟽' => ' biǎo', + '㟼' => ' áo', + '㟻' => ' qiàn', + '㟺' => ' lóu', + '㟷' => ' da', + '㟫' => ' quán', + '㟶' => ' yuán', + '㟵' => ' gǎng', + '㟴' => ' kuài', + '㟳' => ' lì', + '㟲' => ' jué', + '㟱' => ' yǎo', + '㟰' => ' mǐng', + '㟮' => ' tū', + '㟭' => ' mín', + '㟬' => ' sǒu', + '㟌' => ' máng', + '㟊' => ' fú', + '㞓' => ' qì', + '㞠' => ' láo', + '㞬' => ' qìn', + '㞫' => ' jù', + '㞪' => ' náo', + '㞩' => ' lán', + '㞨' => ' zǐ', + '㞧' => ' huì', + '㞦' => ' jǐ', + '㞥' => ' cén', + '㞤' => ' yín', + '㞡' => ' zhǎn', + '㞟' => ' diàn', + '㞯' => ' jié', + '㞞' => ' sóng', + '㞜' => ' tuī', + '㞛' => ' jǐ', + '㞚' => ' qì', + '㞙' => ' niào', + '㞘' => ' dū', + '㞗' => ' qiú', + '㞖' => ' cí', + '㞕' => ' xiè', + '㞔' => ' yí', + '㞭' => ' dài', + '㞰' => ' xǔ', + '㟉' => ' láo', + '㞾' => ' ní', + '㟈' => ' qiú', + '㟇' => ' cuò', + '㟆' => ' huá', + '㟅' => ' yáng', + '㟄' => ' xiáng', + '㟃' => ' sī', + '㟂' => ' mǔ', + '㟁' => ' àn', + '㟀' => ' lián', + '㞿' => ' chì', + '㞽' => ' xuè', + '㞱' => ' cōng', + '㞼' => ' chéng', + '㞻' => ' hào', + '㞺' => ' zú', + '㞹' => ' kě', + '㞸' => ' suì', + '㞷' => ' huáng', + '㞶' => ' mǐn', + '㞴' => ' chí', + '㞳' => ' dǒu', + '㞲' => ' yòng', + '㜠' => ' cuī', + '㜝' => ' ǎn', + '㘩' => ' bì', + '㙶' => ' táng', + '㚂' => ' nàng', + '㚁' => ' qiào', + '㚀' => ' xī', + '㙾' => ' xì', + '㙼' => ' lěi', + '㙻' => ' chán', + '㙺' => ' kuí', + '㙹' => ' méng', + '㙸' => ' bào', + '㙷' => ' zhí', + '㙵' => ' tái', + '㚅' => ' lóng', + '㙴' => ' chàn', + '㙳' => ' kǎn', + '㙲' => ' yǒng', + '㙱' => ' hào', + '㙰' => ' xiè', + '㙯' => ' yì', + '㙮' => ' dā', + '㙬' => ' yín', + '㙫' => ' jí', + '㙪' => ' yì', + '㚃' => ' yūn', + '㚆' => ' fù', + '㙨' => ' jì', + '㚖' => ' gǎo', + '㚠' => ' xiāo', + '㚟' => ' chuò', + '㚞' => ' shěn', + '㚝' => ' zhuī', + '㚜' => ' yù', + '㚛' => ' xì', + '㚚' => ' kōng', + '㚙' => ' jiā', + '㚘' => ' bàn', + '㚗' => ' qié', + '㚕' => ' fú', + '㚇' => ' zōng', + '㚔' => ' niè', + '㚓' => ' lǎi', + '㚒' => ' shǎn', + '㚐' => ' tào', + '㚏' => ' gǎo', + '㚍' => ' kuǐ', + '㚌' => ' huà', + '㚋' => ' diāo', + '㚊' => ' kāi', + '㚉' => ' gǔ', + '㙩' => ' liáo', + '㙦' => ' xié', + '㚢' => ' nú', + '㘸' => ' zàng', + '㙇' => ' chù', + '㙅' => ' bǎo', + '㙄' => ' bì', + '㙃' => ' bāng', + '㙂' => ' duì', + '㙁' => ' méi', + '㙀' => ' liù', + '㘿' => ' niè', + '㘺' => ' fá', + '㘹' => ' cí', + '㘷' => ' ài', + '㙉' => ' tiǎn', + '㘶' => ' jié', + '㘵' => ' bù', + '㘴' => ' zuò', + '㘲' => ' jú', + '㘱' => ' tān', + '㘰' => ' zhēn', + '㘭' => ' ào', + '㘬' => ' ào', + '㘫' => ' jǐng', + '㘪' => ' hào', + '㙈' => ' xià', + '㙊' => ' cháng', + '㙥' => ' gùn', + '㙚' => ' xīng', + '㙤' => ' xià', + '㙣' => ' zhàng', + '㙢' => ' mán', + '㙡' => ' zōng', + '㙠' => ' yī', + '㙟' => ' lǎng', + '㙞' => ' niè', + '㙝' => ' xié', + '㙜' => ' chí', + '㙛' => ' bǔ', + '㙙' => ' lǒng', + '㙍' => ' duō', + '㙘' => ' yāo', + '㙗' => ' wēi', + '㙕' => ' kuài', + '㙔' => ' wěi', + '㙓' => ' kuí', + '㙒' => ' yě', + '㙑' => ' yǔ', + '㙐' => ' duǒ', + '㙏' => ' fù', + '㙎' => ' wēi', + '㚡' => ' jǐ', + '㚣' => ' xiáo', + '㜜' => ' ào', + '㛳' => ' yì', + '㛾' => ' xián', + '㛼' => ' chā', + '㛻' => ' kuā', + '㛺' => ' ān', + '㛹' => ' pián', + '㛸' => ' shù', + '㛷' => ' jī', + '㛶' => ' tàn', + '㛵' => ' chēng', + '㛴' => ' nǎo', + '㛲' => ' fà', + '㜂' => ' fēng', + '㛱' => ' wěi', + '㛯' => ' fàn', + '㛮' => ' sǎo', + '㛭' => ' xī', + '㛬' => ' lù', + '㛫' => ' xī', + '㛪' => ' yàn', + '㛩' => ' zhóu', + '㛨' => ' qiǎng', + '㛧' => ' mǎn', + '㛿' => ' zhì', + '㜃' => ' liàn', + '㛥' => ' tà', + '㜐' => ' shù', + '㜛' => ' nèn', + '㜚' => ' sù', + '㜙' => ' lù', + '㜘' => ' jù', + '㜗' => ' cān', + '㜖' => ' cáo', + '㜕' => ' lián', + '㜓' => ' dá', + '㜒' => ' yì', + '㜑' => ' fú', + '㜏' => ' yún', + '㜄' => ' xún', + '㜎' => ' xī', + '㜍' => ' táng', + '㜌' => ' nǒu', + '㜋' => ' yì', + '㜊' => ' zhǎn', + '㜉' => ' yōng', + '㜈' => ' mù', + '㜇' => ' huì', + '㜆' => ' mì', + '㜅' => ' xù', + '㛦' => ' lǎn', + '㛤' => ' lí', + '㚤' => ' yì', + '㚱' => ' qiū', + '㚾' => ' xiōng', + '㚽' => ' qiǎo', + '㚻' => ' jī', + '㚺' => ' yùn', + '㚹' => ' mǎo', + '㚷' => ' ěr', + '㚶' => ' sì', + '㚵' => ' dōng', + '㚴' => ' bù', + '㚲' => ' chān', + '㚰' => ' pēi', + '㛀' => ' chú', + '㚯' => ' xín', + '㚭' => ' yóu', + '㚬' => ' jūn', + '㚫' => ' sà', + '㚪' => ' hào', + '㚩' => ' rǎn', + '㚨' => ' shěn', + '㚧' => ' yǎn', + '㚦' => ' yí', + '㚥' => ' yú', + '㚿' => ' páo', + '㛁' => ' pēng', + '㛣' => ' yún', + '㛔' => ' fēng', + '㛞' => ' lòng', + '㛝' => ' bèi', + '㛜' => ' yóu', + '㛛' => ' xìn', + '㛚' => ' tǒng', + '㛙' => ' xīn', + '㛘' => ' pò', + '㛗' => ' qiē', + '㛖' => ' suō', + '㛕' => ' yì', + '㛓' => ' xī', + '㛂' => ' nuǒ', + '㛒' => ' dòu', + '㛑' => ' càn', + '㛐' => ' sǒu', + '㛏' => ' qiú', + '㛎' => ' lǚ', + '㛍' => ' qiè', + '㛊' => ' duǒ', + '㛆' => ' duò', + '㛅' => ' èr', + '㛄' => ' yī', + '㛃' => ' jié', + '㯜' => ' cuì', + '㯞' => ' juàn', + '䍓' => ' hù', + '䃫' => ' dǎn', + '䃵' => ' niǎo', + '䃴' => ' zhū', + '䃳' => ' là', + '䃲' => ' pán', + '䃱' => ' xiàn', + '䃰' => ' cā', + '䃯' => ' lì', + '䃮' => ' dá', + '䃭' => ' gǎn', + '䃬' => ' wěi', + '䃪' => ' tán', + '䃷' => ' yíng', + '䃩' => ' náo', + '䃧' => ' lóng', + '䃦' => ' dūn', + '䃥' => ' chuáng', + '䃤' => ' sù', + '䃣' => ' huǐ', + '䃢' => ' qīn', + '䃡' => ' jīn', + '䃠' => ' xuàn', + '䃞' => ' shú', + '䃶' => ' huái', + '䃸' => ' xiàn', + '䃜' => ' yī', + '䄇' => ' chéng', + '䄒' => ' niè', + '䄑' => ' huó', + '䄐' => ' quàn', + '䄏' => ' yú', + '䄎' => ' qǐ', + '䄍' => ' zhà', + '䄌' => ' zhuì', + '䄋' => ' yǎn', + '䄉' => ' é', + '䄈' => ' dòu', + '䄆' => ' huó', + '䃹' => ' làn', + '䄅' => ' juàn', + '䄄' => ' yīn', + '䄂' => ' liù', + '䄁' => ' yì', + '䄀' => ' huò', + '䃿' => ' fū', + '䃾' => ' bǐ', + '䃽' => ' guǐ', + '䃻' => ' bà', + '䃺' => ' mó', + '䃝' => ' qiào', + '䃛' => ' lián', + '䄔' => ' jǔ', + '䂱' => ' ruì', + '䂻' => ' zhèng', + '䂺' => ' wǒ', + '䂹' => ' suǒ', + '䂸' => ' guó', + '䂷' => ' guāi', + '䂶' => ' jié', + '䂵' => ' gōng', + '䂴' => ' yán', + '䂳' => ' cuǒ', + '䂲' => ' fǎ', + '䂰' => ' là', + '䂽' => ' diào', + '䂯' => ' guài', + '䂮' => ' lüè', + '䂭' => ' qiāo', + '䂬' => ' gǒng', + '䂫' => ' hāng', + '䂪' => ' qiǎo', + '䂩' => ' yàn', + '䂧' => ' zhěn', + '䂦' => ' zhēn', + '䂥' => ' mín', + '䂼' => ' niè', + '䂾' => ' lǎi', + '䃚' => ' cù', + '䃎' => ' zhà', + '䃙' => ' lù', + '䃘' => ' kēng', + '䃗' => ' bō', + '䃖' => ' wù', + '䃕' => ' láo', + '䃔' => ' hóng', + '䃓' => ' gòu', + '䃒' => ' hé', + '䃑' => ' bān', + '䃏' => ' xīng', + '䃍' => ' zhào', + '䂿' => ' tà', + '䃌' => ' zhēn', + '䃋' => ' yú', + '䃊' => ' jǔ', + '䃉' => ' mín', + '䃈' => ' jiē', + '䃇' => ' mián', + '䃅' => ' dī', + '䃂' => ' gǔn', + '䃁' => ' yā', + '䃀' => ' cuì', + '䄓' => ' huáng', + '䄕' => ' shè', + '䂣' => ' cí', + '䅜' => ' duò', + '䅦' => ' máo', + '䅥' => ' jié', + '䅤' => ' qì', + '䅣' => ' huáng', + '䅢' => ' jiù', + '䅡' => ' xǔ', + '䅠' => ' tí', + '䅟' => ' cǎn', + '䅞' => ' nè', + '䅝' => ' kōng', + '䅛' => ' chāng', + '䅩' => ' zhǐ', + '䅚' => ' quǎn', + '䅙' => ' hùn', + '䅘' => ' lái', + '䅗' => ' suì', + '䅖' => ' ān', + '䅕' => ' jū', + '䅔' => ' zī', + '䅓' => ' jǔ', + '䅒' => ' máng', + '䅑' => ' ruí', + '䅧' => ' yān', + '䅪' => ' tuí', + '䅏' => ' huì', + '䅸' => ' shēn', + '䆂' => ' lián', + '䆁' => ' gāo', + '䆀' => ' měi', + '䅿' => ' huáng', + '䅾' => ' cén', + '䅽' => ' xīn', + '䅼' => ' mán', + '䅻' => ' lí', + '䅺' => ' biāo', + '䅹' => ' lǒu', + '䅷' => ' tú', + '䅬' => ' ài', + '䅶' => ' nòu', + '䅵' => ' zhuó', + '䅴' => ' suǒ', + '䅳' => ' chú', + '䅲' => ' qí', + '䅱' => ' hùn', + '䅰' => ' ěn', + '䅯' => ' táng', + '䅮' => ' càng', + '䅭' => ' páng', + '䅐' => ' jiǎn', + '䅎' => ' yǒu', + '䄘' => ' péng', + '䄦' => ' liǎo', + '䄰' => ' yá', + '䄯' => ' jiǎn', + '䄮' => ' fū', + '䄭' => ' nián', + '䄬' => ' yí', + '䄫' => ' qǐ', + '䄪' => ' diǎo', + '䄩' => ' yì', + '䄨' => ' yú', + '䄧' => ' réng', + '䄥' => ' líng', + '䄲' => ' ruì', + '䄤' => ' lài', + '䄢' => ' qí', + '䄡' => ' dān', + '䄠' => ' shàn', + '䄟' => ' cuī', + '䄝' => ' chuāng', + '䄜' => ' lí', + '䄛' => ' lóu', + '䄚' => ' cáo', + '䄙' => ' míng', + '䄱' => ' fāng', + '䄳' => ' xiān', + '䅍' => ' tǐng', + '䅂' => ' hé', + '䅌' => ' juān', + '䅋' => ' wǎn', + '䅊' => ' chá', + '䅉' => ' yà', + '䅈' => ' yuàn', + '䅇' => ' sù', + '䅆' => ' zì', + '䅅' => ' guī', + '䅄' => ' lì', + '䅃' => ' qióng', + '䅁' => ' àn', + '䄶' => ' bì', + '䅀' => ' liè', + '䄿' => ' yì', + '䄾' => ' rù', + '䄽' => ' tiǎn', + '䄼' => ' tiǎn', + '䄻' => ' táo', + '䄺' => ' zhì', + '䄹' => ' nián', + '䄸' => ' pò', + '䄷' => ' shí', + '䂤' => ' fù', + '䂢' => ' dǒng', + '䆄' => ' zhǎn', + '䀎' => ' miǎn', + '䀘' => ' xié', + '䀗' => ' jué', + '䀖' => ' tiān', + '䀕' => ' zhèn', + '䀔' => ' rèn', + '䀓' => ' huàn', + '䀒' => ' qiān', + '䀑' => ' wò', + '䀐' => ' shān', + '䀏' => ' xuàn', + '䀍' => ' kàn', + '䀚' => ' áng', + '䀌' => ' xì', + '䀋' => ' yán', + '䀊' => ' jiǎo', + '䀉' => ' qiáo', + '䀈' => ' jì', + '䀇' => ' gǔ', + '䀆' => ' jìn', + '䀅' => ' shè', + '䀄' => ' méng', + '䀃' => ' tuí', + '䀙' => ' qì', + '䀛' => ' mèi', + '䀁' => ' yòu', + '䀩' => ' luò', + '䀳' => ' ài', + '䀲' => ' mù', + '䀱' => ' fēng', + '䀰' => ' gǔ', + '䀯' => ' bǔ', + '䀮' => ' huāng', + '䀭' => ' gāi', + '䀬' => ' quán', + '䀫' => ' jiá', + '䀪' => ' háng', + '䀨' => ' huà', + '䀜' => ' gǔ', + '䀧' => ' hǒng', + '䀦' => ' gǔ', + '䀥' => ' shuò', + '䀤' => ' mào', + '䀣' => ' bì', + '䀢' => ' shùn', + '䀡' => ' chàn', + '䀠' => ' jù', + '䀟' => ' fán', + '䀞' => ' tāo', + '䀂' => ' ān', + '䀀' => ' fàn', + '䀵' => ' shùn', + '㿗' => ' tuí', + '㿣' => ' hē', + '㿢' => ' yào', + '㿠' => ' huàng', + '㿟' => ' bái', + '㿞' => ' mào', + '㿜' => ' biě', + '㿛' => ' lì', + '㿚' => ' luò', + '㿙' => ' pì', + '㿘' => ' yíng', + '㿖' => ' lú', + '㿥' => ' hé', + '㿕' => ' yán', + '㿔' => ' lěi', + '㿓' => ' jiá', + '㿒' => ' dǎo', + '㿑' => ' yào', + '㿏' => ' qún', + '㿎' => ' fèn', + '㿍' => ' jiè', + '㿌' => ' xiān', + '㿋' => ' sào', + '㿤' => ' chǔn', + '㿦' => ' nìng', + '㿿' => ' yǎ', + '㿴' => ' dā', + '㿾' => ' zhù', + '㿽' => ' xī', + '㿼' => ' yán', + '㿻' => ' gān', + '㿺' => ' báo', + '㿹' => ' tà', + '㿸' => ' wǎn', + '㿷' => ' cuó', + '㿶' => ' bāng', + '㿵' => ' rǎn', + '㿳' => ' zī', + '㿧' => ' chóu', + '㿱' => ' xué', + '㿰' => ' áo', + '㿯' => ' dá', + '㿮' => ' yàng', + '㿭' => ' chè', + '㿬' => ' bā', + '㿫' => ' bì', + '㿪' => ' huán', + '㿩' => ' tǎng', + '㿨' => ' lì', + '䀴' => ' yǐng', + '䀶' => ' liàng', + '䂡' => ' dǐ', + '䁹' => ' bì', + '䂃' => ' jiào', + '䂂' => ' qú', + '䂁' => ' chán', + '䂀' => ' xī', + '䁿' => ' mò', + '䁾' => ' miè', + '䁽' => ' liè', + '䁼' => ' mò', + '䁻' => ' shuò', + '䁺' => ' shì', + '䁸' => ' náng', + '䂅' => ' xiān', + '䁷' => ' jué', + '䁶' => ' jī', + '䁵' => ' biǎn', + '䁴' => ' zhǎn', + '䁳' => ' mǎng', + '䁲' => ' mái', + '䁱' => ' kōu', + '䁰' => ' jiǎng', + '䁯' => ' xī', + '䁮' => ' qián', + '䂄' => ' huò', + '䂆' => ' xù', + '䁬' => ' céng', + '䂕' => ' huì', + '䂠' => ' shǐ', + '䂟' => ' jiā', + '䂞' => ' zhè', + '䂝' => ' jié', + '䂜' => ' bàng', + '䂛' => ' yú', + '䂚' => ' yáo', + '䂙' => ' duī', + '䂗' => ' kū', + '䂖' => ' shí', + '䂔' => ' xìng', + '䂇' => ' niǔ', + '䂒' => ' qià', + '䂑' => ' jī', + '䂐' => ' zhuō', + '䂏' => ' diāo', + '䂎' => ' zuǎn', + '䂍' => ' bó', + '䂌' => ' chōng', + '䂊' => ' yù', + '䂉' => ' hóu', + '䂈' => ' tóng', + '䁭' => ' biāo', + '䁫' => ' hú', + '䀷' => ' jié', + '䁄' => ' xìng', + '䁎' => ' chéng', + '䁍' => ' qià', + '䁌' => ' yù', + '䁋' => ' xiè', + '䁊' => ' wò', + '䁉' => ' qì', + '䁈' => ' qì', + '䁇' => ' mì', + '䁆' => ' yè', + '䁅' => ' měng', + '䁃' => ' biāo', + '䁐' => ' yīng', + '䁂' => ' xiàn', + '䁁' => ' liàng', + '䀿' => ' dì', + '䀾' => ' dǔ', + '䀽' => ' yán', + '䀼' => ' chēn', + '䀻' => ' pìng', + '䀺' => ' chōu', + '䀹' => ' jié', + '䀸' => ' chì', + '䁏' => ' yǎo', + '䁑' => ' yáng', + '䁪' => ' zhǎn', + '䁞' => ' shěng', + '䁩' => ' yú', + '䁨' => ' huò', + '䁧' => ' miáo', + '䁦' => ' qù', + '䁥' => ' nì', + '䁤' => ' chè', + '䁣' => ' chuán', + '䁢' => ' xuán', + '䁠' => ' lián', + '䁟' => ' chá', + '䁝' => ' yíng', + '䁒' => ' jí', + '䁜' => ' huàng', + '䁛' => ' guì', + '䁚' => ' sǔn', + '䁙' => ' yǎn', + '䁘' => ' yǎo', + '䁗' => ' kǎi', + '䁖' => ' lōu', + '䁕' => ' mín', + '䁔' => ' xuān', + '䁓' => ' zōng', + '䆃' => ' dào', + '䆅' => ' zī', + '㿉' => ' tuǐ', + '䊶' => ' zhèn', + '䋃' => ' mào', + '䋂' => ' yáo', + '䋁' => ' gěng', + '䋀' => ' huà', + '䊿' => ' bó', + '䊾' => ' mù', + '䊽' => ' guài', + '䊼' => ' chǐ', + '䊻' => ' gān', + '䊺' => ' hù', + '䊵' => ' qiú', + '䋈' => ' rú', + '䊴' => ' qiàn', + '䊳' => ' mí', + '䊲' => ' chàn', + '䊱' => ' xiān', + '䊰' => ' chú', + '䊯' => ' kuàng', + '䊮' => ' dí', + '䊭' => ' dào', + '䊬' => ' chuā', + '䊫' => ' cuǐ', + '䋄' => ' wǎng', + '䋉' => ' xué', + '䊩' => ' fán', + '䋙' => ' ěr', + '䋥' => ' lí', + '䋤' => ' yuè', + '䋣' => ' fán', + '䋢' => ' biē', + '䋠' => ' bǔ', + '䋟' => ' jì', + '䋞' => ' wǎng', + '䋜' => ' qìng', + '䋛' => ' mǐ', + '䋚' => ' yì', + '䋘' => ' chuò', + '䋊' => ' zhēng', + '䋖' => ' yù', + '䋕' => ' rèn', + '䋔' => ' bì', + '䋓' => ' zhòu', + '䋑' => ' liè', + '䋐' => ' yuè', + '䋏' => ' zuó', + '䋎' => ' zhàn', + '䋌' => ' jiǎng', + '䋋' => ' mín', + '䊪' => ' lì', + '䊨' => ' luó', + '䋧' => ' qú', + '䉺' => ' hóng', + '䊅' => ' míng', + '䊄' => ' qióng', + '䊃' => ' pèi', + '䊂' => ' cè', + '䊀' => ' hú', + '䉿' => ' hú', + '䉾' => ' bì', + '䉽' => ' bǎn', + '䉼' => ' liào', + '䉻' => ' qí', + '䉹' => ' líng', + '䊇' => ' bù', + '䉸' => ' biàn', + '䉷' => ' yán', + '䉶' => ' shuāng', + '䉵' => ' zhuàn', + '䉴' => ' ráng', + '䉳' => ' xiān', + '䉲' => ' mí', + '䉱' => ' ōu', + '䉰' => ' xiào', + '䉯' => ' xiàn', + '䊆' => ' jiù', + '䊈' => ' méi', + '䊧' => ' bì', + '䊛' => ' sà', + '䊦' => ' yè', + '䊥' => ' xiào', + '䊤' => ' tán', + '䊣' => ' huáng', + '䊡' => ' mán', + '䊠' => ' xì', + '䊟' => ' mén', + '䊞' => ' zhé', + '䊝' => ' xiè', + '䊜' => ' tuán', + '䊙' => ' yān', + '䊉' => ' sǎn', + '䊘' => ' jiù', + '䊗' => ' huáng', + '䊖' => ' nǎn', + '䊔' => ' yíng', + '䊓' => ' shì', + '䊑' => ' xiǎng', + '䊐' => ' hún', + '䊎' => ' quǎn', + '䊍' => ' lí', + '䊊' => ' wèi', + '䋦' => ' fán', + '䋨' => ' fǔ', + '䉭' => ' liè', + '䌫' => ' lǎn', + '䌷' => ' chōu', + '䌵' => ' zhú', + '䌴' => ' luò', + '䌳' => ' shī', + '䌱' => ' luò', + '䌰' => ' niè', + '䌯' => ' quān', + '䌮' => ' shuāng', + '䌭' => ' jiāo', + '䌬' => ' chóng', + '䌪' => ' yǎn', + '䌹' => ' jiǒng', + '䌩' => ' miè', + '䌨' => ' cā', + '䌧' => ' chóu', + '䌦' => ' dào', + '䌥' => ' yǐn', + '䌤' => ' shī', + '䌣' => ' zuǎn', + '䌢' => ' líng', + '䌡' => ' tǐ', + '䌠' => ' jiān', + '䌸' => ' juàn', + '䌺' => ' ěr', + '䌞' => ' liǎn', + '䍇' => ' tà', + '䍒' => ' mǒu', + '䍑' => ' hǎn', + '䍐' => ' hǎn', + '䍎' => ' cùn', + '䍍' => ' kòu', + '䍌' => ' bù', + '䍋' => ' chuí', + '䍊' => ' jiāo', + '䍉' => ' zhǎi', + '䍈' => ' píng', + '䍆' => ' zhù', + '䌻' => ' yì', + '䍅' => ' líng', + '䍄' => ' diǎn', + '䍃' => ' yóu', + '䍂' => ' yú', + '䍁' => ' suì', + '䍀' => ' lán', + '䌿' => ' fú', + '䌾' => ' rén', + '䌽' => ' cǎi', + '䌼' => ' ruì', + '䌟' => ' bó', + '䌝' => ' jīn', + '䋩' => ' ér', + '䋵' => ' yì', + '䌀' => ' kē', + '䋿' => ' wēi', + '䋾' => ' zhǎ', + '䋽' => ' běng', + '䋼' => ' tīng', + '䋻' => ' nín', + '䋹' => ' fú', + '䋸' => ' xún', + '䋷' => ' móu', + '䋶' => ' xǔ', + '䋴' => ' niù', + '䌂' => ' òu', + '䋳' => ' běi', + '䋲' => ' chě', + '䋱' => ' lái', + '䋰' => ' jú', + '䋯' => ' qǐ', + '䋮' => ' jìn', + '䋭' => ' yù', + '䋬' => ' tiān', + '䋫' => ' zhēng', + '䋪' => ' ē', + '䌁' => ' yāo', + '䌃' => ' xiāo', + '䌜' => ' niè', + '䌑' => ' jiān', + '䌛' => ' yáo', + '䌚' => ' sū', + '䌙' => ' huàng', + '䌘' => ' biè', + '䌗' => ' fǔ', + '䌖' => ' jié', + '䌕' => ' mí', + '䌔' => ' ōu', + '䌓' => ' fán', + '䌒' => ' lù', + '䌐' => ' mì', + '䌄' => ' gěng', + '䌏' => ' mì', + '䌎' => ' lüè', + '䌍' => ' jǐn', + '䌌' => ' qì', + '䌋' => ' dā', + '䌊' => ' yáo', + '䌈' => ' tā', + '䌇' => ' huì', + '䌆' => ' guì', + '䌅' => ' táng', + '䉮' => ' lìn', + '䉬' => ' fèi', + '䆈' => ' zhì', + '䇒' => ' jiè', + '䇟' => ' jiàn', + '䇞' => ' gān', + '䇜' => ' qiǎn', + '䇛' => ' zhǐ', + '䇙' => ' yǐn', + '䇘' => ' hù', + '䇗' => ' zhōng', + '䇖' => ' yǔn', + '䇔' => ' luò', + '䇓' => ' xū', + '䇑' => ' bà', + '䇡' => ' zhù', + '䇐' => ' lì', + '䇏' => ' duì', + '䇎' => ' què', + '䇍' => ' chù', + '䇌' => ' qiào', + '䇋' => ' hài', + '䇈' => ' huà', + '䇇' => ' méng', + '䇅' => ' fá', + '䇃' => ' sì', + '䇠' => ' zhù', + '䇢' => ' kǔ', + '䇁' => ' sī', + '䇯' => ' róng', + '䇻' => ' wěi', + '䇺' => ' dòu', + '䇹' => ' jùn', + '䇸' => ' chéng', + '䇷' => ' bié', + '䇶' => ' jué', + '䇵' => ' yí', + '䇲' => ' cè', + '䇱' => ' nà', + '䇰' => ' zhēng', + '䇮' => ' rèn', + '䇣' => ' niè', + '䇭' => ' lǎo', + '䇬' => ' zhū', + '䇫' => ' jī', + '䇪' => ' chī', + '䇩' => ' yì', + '䇨' => ' gòng', + '䇧' => ' zhì', + '䇦' => ' ǎng', + '䇥' => ' zé', + '䇤' => ' ruì', + '䇂' => ' qiān', + '䇀' => ' róng', + '䇽' => ' zhé', + '䆕' => ' yuè', + '䆟' => ' kè', + '䆞' => ' yǎo', + '䆝' => ' xuè', + '䆜' => ' yòu', + '䆛' => ' zhà', + '䆚' => ' tóng', + '䆙' => ' yáo', + '䆘' => ' yā', + '䆗' => ' yǎo', + '䆖' => ' hóng', + '䆔' => ' chōng', + '䆡' => ' láng', + '䆓' => ' è', + '䆒' => ' jiù', + '䆑' => ' chéng', + '䆐' => ' guó', + '䆏' => ' fèi', + '䆎' => ' xiān', + '䆍' => ' lóng', + '䆋' => ' qiū', + '䆊' => ' cuì', + '䆉' => ' bà', + '䆠' => ' huàn', + '䆢' => ' yuè', + '䆿' => ' yì', + '䆲' => ' kāng', + '䆾' => ' lán', + '䆽' => ' qú', + '䆻' => ' qiào', + '䆺' => ' pān', + '䆹' => ' chōng', + '䆸' => ' zhēng', + '䆷' => ' xuè', + '䆶' => ' jiū', + '䆵' => ' chéng', + '䆳' => ' qióng', + '䆱' => ' tān', + '䆣' => ' chén', + '䆰' => ' yū', + '䆯' => ' zhuó', + '䆮' => ' jìn', + '䆭' => ' xuān', + '䆬' => ' yǔn', + '䆫' => ' chuāng', + '䆪' => ' hōng', + '䆩' => ' míng', + '䆨' => ' níng', + '䆦' => ' shèn', + '䇼' => ' yì', + '䇾' => ' yán', + '䉫' => ' lí', + '䉄' => ' léng', + '䉏' => ' xiè', + '䉎' => ' táng', + '䉍' => ' jiǎn', + '䉌' => ' suì', + '䉋' => ' mèi', + '䉊' => ' fàn', + '䉉' => ' gū', + '䉈' => ' sǎn', + '䉆' => ' diǎo', + '䉅' => ' zhì', + '䉃' => ' jiǎng', + '䉑' => ' wú', + '䉂' => ' lěi', + '䉁' => ' líng', + '䉀' => ' shù', + '䈿' => ' mì', + '䈾' => ' shāo', + '䈽' => ' jīn', + '䈼' => ' miè', + '䈻' => ' pú', + '䈺' => ' zhōng', + '䈹' => ' sǒu', + '䉐' => ' kū', + '䉒' => ' fán', + '䈷' => ' luò', + '䉟' => ' huò', + '䉪' => ' lèi', + '䉨' => ' yì', + '䉧' => ' liú', + '䉦' => ' qiān', + '䉥' => ' sǒng', + '䉤' => ' sǒu', + '䉣' => ' xiè', + '䉢' => ' sè', + '䉡' => ' tán', + '䉠' => ' wéi', + '䉞' => ' dǎn', + '䉓' => ' luò', + '䉝' => ' yǐ', + '䉜' => ' zhì', + '䉛' => ' yù', + '䉚' => ' méng', + '䉙' => ' yún', + '䉘' => ' cóng', + '䉗' => ' yī', + '䉖' => ' líng', + '䉕' => ' céng', + '䉔' => ' cān', + '䈸' => ' hú', + '䈶' => ' róng', + '䈀' => ' sān', + '䈌' => ' qū', + '䈗' => ' suǒ', + '䈖' => ' kē', + '䈕' => ' shí', + '䈓' => ' gé', + '䈒' => ' nǎn', + '䈑' => ' guǎ', + '䈐' => ' guì', + '䈏' => ' báo', + '䈎' => ' yè', + '䈍' => ' máng', + '䈋' => ' tà', + '䈙' => ' zhòu', + '䈊' => ' líng', + '䈉' => ' shà', + '䈈' => ' féi', + '䈇' => ' zhào', + '䈆' => ' dài', + '䈅' => ' yù', + '䈄' => ' hán', + '䈃' => ' zhǎo', + '䈂' => ' píng', + '䈁' => ' lún', + '䈘' => ' cí', + '䈚' => ' tái', + '䈵' => ' wēng', + '䈪' => ' xì', + '䈴' => ' qiàn', + '䈳' => ' tà', + '䈲' => ' bān', + '䈱' => ' tāo', + '䈰' => ' shāo', + '䈯' => ' zhēn', + '䈮' => ' jù', + '䈭' => ' sōu', + '䈬' => ' pú', + '䈫' => ' nà', + '䈧' => ' wěi', + '䈛' => ' kuài', + '䈦' => ' zōng', + '䈥' => ' jīn', + '䈤' => ' qián', + '䈣' => ' zhèng', + '䈢' => ' sǎi', + '䈡' => ' cōng', + '䈠' => ' huǎn', + '䈟' => ' cè', + '䈞' => ' dǔ', + '䈝' => ' xū', + '䈜' => ' qìn', + '㿊' => ' cǎn', + '㿇' => ' xí', + '㯟' => ' lù', + '㴥' => ' qiào', + '㴯' => ' zhī', + '㴮' => ' xiè', + '㴭' => ' yǎo', + '㴬' => ' xiè', + '㴫' => ' jùn', + '㴪' => ' niè', + '㴩' => ' yōng', + '㴨' => ' zhèn', + '㴧' => ' xī', + '㴦' => ' guàn', + '㴝' => ' lí', + '㴲' => ' sī', + '㴜' => ' biàn', + '㴛' => ' zhì', + '㴚' => ' suì', + '㴙' => ' zhǎ', + '㴘' => ' mào', + '㴗' => ' yōu', + '㴖' => ' luò', + '㴕' => ' jí', + '㴔' => ' xī', + '㴓' => ' shāi', + '㴰' => ' néng', + '㴳' => ' lǒng', + '㴑' => ' sù', + '㵄' => ' hàn', + '㵑' => ' hè', + '㵐' => ' jué', + '㵏' => ' cuǐ', + '㵎' => ' jiàn', + '㵍' => ' mèn', + '㵌' => ' xún', + '㵋' => ' mì', + '㵊' => ' nà', + '㵆' => ' gǎo', + '㵅' => ' tān', + '㵃' => ' shé', + '㴴' => ' chén', + '㵂' => ' shù', + '㵀' => ' zú', + '㴿' => ' dǐng', + '㴾' => ' bó', + '㴽' => ' xiè', + '㴼' => ' sù', + '㴸' => ' shǎn', + '㴷' => ' dān', + '㴶' => ' què', + '㴵' => ' mì', + '㴒' => ' yì', + '㴐' => ' miàn', + '㵓' => ' shí', + '㳚' => ' xù', + '㳪' => ' pì', + '㳩' => ' tān', + '㳨' => ' jiǎn', + '㳧' => ' chè', + '㳦' => ' xiè', + '㳥' => ' lòng', + '㳡' => ' guō', + '㳠' => ' tà', + '㳜' => ' xiū', + '㳛' => ' yú', + '㳙' => ' xuàn', + '㳬' => ' xuán', + '㳘' => ' chōng', + '㳗' => ' chài', + '㳖' => ' yī', + '㳕' => ' lán', + '㳔' => ' duì', + '㳒' => ' biàn', + '㳑' => ' yì', + '㳐' => ' zhá', + '㳏' => ' shì', + '㳎' => ' biàn', + '㳫' => ' zǎn', + '㳭' => ' xián', + '㴏' => ' wǎng', + '㳿' => ' xiè', + '㴋' => ' sù', + '㴉' => ' jǐ', + '㴈' => ' yìn', + '㴇' => ' shè', + '㴆' => ' jìn', + '㴄' => ' yǒng', + '㴃' => ' lèi', + '㴂' => ' tān', + '㴁' => ' yì', + '㴀' => ' fàn', + '㳾' => ' qiāng', + '㳮' => ' niào', + '㳽' => ' mǐ', + '㳼' => ' bì', + '㳻' => ' zé', + '㳺' => ' yóu', + '㳹' => ' wǎng', + '㳸' => ' huā', + '㳷' => ' hū', + '㳶' => ' nǒu', + '㳵' => ' jì', + '㳴' => ' mì', + '㵒' => ' fèi', + '㵔' => ' chě', + '㳌' => ' yā', + '㶤' => ' chǎo', + '㶳' => ' jìn', + '㶲' => ' yòng', + '㶰' => ' hán', + '㶯' => ' liù', + '㶭' => ' yǒu', + '㶬' => ' mò', + '㶫' => ' liǎo', + '㶧' => ' nèn', + '㶦' => ' jìn', + '㶥' => ' gān', + '㶣' => ' chán', + '㶵' => ' rèn', + '㶡' => ' dài', + '㶠' => ' yì', + '㶟' => ' lěi', + '㶞' => ' nǎng', + '㶛' => ' yú', + '㶚' => ' bà', + '㶘' => ' diàn', + '㶗' => ' fàn', + '㶖' => ' shù', + '㶕' => ' jiǎn', + '㶴' => ' chǐ', + '㶶' => ' nóng', + '㶓' => ' cáng', + '㷇' => ' huī', + '㷓' => ' zǒng', + '㷒' => ' yú', + '㷐' => ' huǐ', + '㷏' => ' yǒng', + '㷎' => ' hè', + '㷍' => ' lún', + '㷋' => ' tán', + '㷊' => ' fén', + '㷉' => ' wèi', + '㷈' => ' è', + '㷆' => ' fù', + '㶹' => ' hòng', + '㷅' => ' chǎo', + '㷄' => ' huǐ', + '㷃' => ' chuǐ', + '㷂' => ' shù', + '㷀' => ' qióng', + '㶿' => ' bó', + '㶾' => ' biāo', + '㶽' => ' guā', + '㶼' => ' āi', + '㶺' => ' tiàn', + '㶔' => ' bèi', + '㶒' => ' shǎn', + '㵕' => ' shèn', + '㵦' => ' suí', + '㵰' => ' xù', + '㵯' => ' féng', + '㵮' => ' chún', + '㵭' => ' zhǔ', + '㵬' => ' yíng', + '㵫' => ' nì', + '㵪' => ' xián', + '㵩' => ' yì', + '㵨' => ' pì', + '㵧' => ' gé', + '㵥' => ' bì', + '㵲' => ' wǔ', + '㵤' => ' shà', + '㵣' => ' kě', + '㵢' => ' léi', + '㵡' => ' báo', + '㵠' => ' kū', + '㵞' => ' chóu', + '㵝' => ' yì', + '㵘' => ' màn', + '㵗' => ' píng', + '㵖' => ' nǜ', + '㵱' => ' piǎo', + '㵳' => ' liáo', + '㶑' => ' liàn', + '㶁' => ' guó', + '㶏' => ' yīn', + '㶎' => ' huán', + '㶍' => ' xiǎn', + '㶌' => ' lián', + '㶉' => ' xī', + '㶈' => ' yíng', + '㶇' => ' héng', + '㶆' => ' zhū', + '㶅' => ' xué', + '㶄' => ' yán', + '㶀' => ' jiāo', + '㵴' => ' cáng', + '㵿' => ' xiào', + '㵾' => ' qìng', + '㵽' => ' lěi', + '㵻' => ' xiū', + '㵺' => ' pài', + '㵹' => ' huán', + '㵸' => ' yào', + '㵷' => ' biàn', + '㵶' => ' zuō', + '㵵' => ' zòu', + '㳍' => ' bù', + '㳋' => ' qiū', + '㷕' => ' qiú', + '㰬' => ' xún', + '㰶' => ' yǒu', + '㰵' => ' zú', + '㰴' => ' pǒu', + '㰳' => ' yà', + '㰲' => ' yū', + '㰱' => ' shà', + '㰰' => ' xiā', + '㰯' => ' kòu', + '㰮' => ' shèn', + '㰭' => ' xū', + '㰫' => ' chān', + '㰸' => ' liǎn', + '㰪' => ' guī', + '㰩' => ' hāi', + '㰨' => ' xiā', + '㰧' => ' hāi', + '㰦' => ' qù', + '㰥' => ' xì', + '㰤' => ' hē', + '㰣' => ' zī', + '㰢' => ' kēng', + '㰡' => ' xiè', + '㰷' => ' zì', + '㰹' => ' xiān', + '㰟' => ' jì', + '㱆' => ' xī', + '㱔' => ' xiē', + '㱏' => ' zhèng', + '㱎' => ' kūn', + '㱍' => ' luán', + '㱌' => ' yé', + '㱋' => ' què', + '㱊' => ' yōu', + '㱉' => ' yè', + '㱈' => ' jìn', + '㱇' => ' sè', + '㱅' => ' yì', + '㰺' => ' xià', + '㱄' => ' hēi', + '㱃' => ' yǐn', + '㱂' => ' kāng', + '㱁' => ' shì', + '㱀' => ' chǐ', + '㰿' => ' xī', + '㰾' => ' jiào', + '㰽' => ' yàn', + '㰼' => ' shà', + '㰻' => ' yǐ', + '㰠' => ' hāng', + '㰞' => ' chī', + '㱗' => ' xiū', + '㯮' => ' shú', + '㯺' => ' jiàn', + '㯹' => ' biǎo', + '㯸' => ' jǐn', + '㯷' => ' pú', + '㯶' => ' zōng', + '㯳' => ' qíng', + '㯲' => ' jìn', + '㯱' => ' pāo', + '㯰' => ' zhé', + '㯯' => ' gòng', + '㯭' => ' lǔ', + '㯾' => ' zāo', + '㯬' => ' liǎn', + '㯫' => ' qú', + '㯪' => ' líng', + '㯩' => ' tì', + '㯦' => ' qí', + '㯥' => ' cáo', + '㯤' => ' lì', + '㯢' => ' zhèn', + '㯡' => ' pào', + '㯠' => ' qiàn', + '㯻' => ' gǔn', + '㯿' => ' liè', + '㰝' => ' yǐ', + '㰎' => ' zuì', + '㰛' => ' yuè', + '㰚' => ' lí', + '㰙' => ' nuó', + '㰘' => ' yí', + '㰗' => ' qí', + '㰖' => ' lǎn', + '㰔' => ' xiè', + '㰒' => ' xué', + '㰑' => ' shān', + '㰐' => ' jué', + '㰍' => ' lóng', + '㰀' => ' lí', + '㰌' => ' què', + '㰋' => ' pín', + '㰊' => ' xián', + '㰈' => ' liǎn', + '㰆' => ' bèi', + '㰅' => ' dí', + '㰄' => ' jiàn', + '㰃' => ' mián', + '㰂' => ' shěn', + '㰁' => ' luǒ', + '㱖' => ' cuì', + '㱘' => ' àn', + '㳊' => ' yōu', + '㲠' => ' máo', + '㲬' => ' jiāo', + '㲫' => ' pú', + '㲪' => ' dēng', + '㲨' => ' róng', + '㲧' => ' sào', + '㲦' => ' hàn', + '㲥' => ' táng', + '㲤' => ' shuāi', + '㲢' => ' biàn', + '㲡' => ' nài', + '㲟' => ' yǐng', + '㲯' => ' rán', + '㲞' => ' sū', + '㲝' => ' rǒng', + '㲜' => ' tán', + '㲛' => ' zhǐ', + '㲚' => ' shā', + '㲘' => ' qú', + '㲗' => ' fū', + '㲖' => ' xiāo', + '㲕' => ' lèi', + '㲔' => ' xiān', + '㲭' => ' tǎn', + '㲰' => ' níng', + '㲒' => ' bào', + '㲾' => ' yǔ', + '㳈' => ' pèi', + '㳇' => ' fù', + '㳆' => ' tǒu', + '㳅' => ' jiǎo', + '㳄' => ' xián', + '㳃' => ' cuì', + '㳂' => ' yán', + '㳁' => ' zè', + '㳀' => ' guò', + '㲿' => ' wǎng', + '㲽' => ' niàn', + '㲱' => ' liè', + '㲼' => ' yì', + '㲻' => ' nì', + '㲺' => ' jí', + '㲹' => ' guǐ', + '㲸' => ' xī', + '㲷' => ' dàn', + '㲶' => ' lǜ', + '㲴' => ' zhòng', + '㲳' => ' dié', + '㲲' => ' dié', + '㲓' => ' róng', + '㲏' => ' pāo', + '㱙' => ' xiǔ', + '㱦' => ' qī', + '㱰' => ' zǎi', + '㱯' => ' ái', + '㱮' => ' kuài', + '㱭' => ' duàn', + '㱬' => ' wěi', + '㱫' => ' làn', + '㱪' => ' mèn', + '㱩' => ' dú', + '㱨' => ' liàn', + '㱧' => ' wò', + '㱥' => ' líng', + '㱲' => ' yì', + '㱤' => ' xī', + '㱣' => ' tuǐ', + '㱢' => ' láng', + '㱡' => ' shēng', + '㱠' => ' kū', + '㱟' => ' pī', + '㱞' => ' yì', + '㱜' => ' zhá', + '㱛' => ' chuǎn', + '㱚' => ' cán', + '㱱' => ' huì', + '㱳' => ' mò', + '㲎' => ' lóu', + '㲂' => ' chéng', + '㲍' => ' zhī', + '㲌' => ' nèng', + '㲋' => ' chuò', + '㲊' => ' ruì', + '㲉' => ' què', + '㲈' => ' sháo', + '㲆' => ' líng', + '㲅' => ' jì', + '㲄' => ' jué', + '㲃' => ' jiù', + '㲁' => ' kōng', + '㱴' => ' zì', + '㲀' => ' zhēn', + '㱿' => ' què', + '㱾' => ' gāi', + '㱽' => ' zhěn', + '㱼' => ' hāi', + '㱻' => ' luò', + '㱺' => ' lú', + '㱹' => ' lì', + '㱸' => ' bì', + '㱶' => ' péng', + '㱵' => ' fèn', + '㷔' => ' yàn', + '㷖' => ' zhào', + '㿆' => ' guō', + '㼧' => ' tóng', + '㼱' => ' ruǎn', + '㼰' => ' pí', + '㼯' => ' dòng', + '㼮' => ' chāi', + '㼭' => ' diàn', + '㼬' => ' xìng', + '㼫' => ' huàn', + '㼪' => ' jié', + '㼩' => ' chéng', + '㼨' => ' hán', + '㼦' => ' gǒng', + '㼳' => ' shěng', + '㼥' => ' tǒu', + '㼤' => ' qiè', + '㼣' => ' bó', + '㼢' => ' yí', + '㼡' => ' shū', + '㼠' => ' tuó', + '㼟' => ' bó', + '㼞' => ' pèng', + '㼝' => ' fàn', + '㼜' => ' àng', + '㼲' => ' liè', + '㼴' => ' ǒu', + '㼚' => ' gāng', + '㽁' => ' lì', + '㽌' => ' liù', + '㽋' => ' luó', + '㽊' => ' xiè', + '㽉' => ' xiàn', + '㽈' => ' yì', + '㽇' => ' hú', + '㽆' => ' dāng', + '㽄' => ' sī', + '㽃' => ' pān', + '㽂' => ' sà', + '㽀' => ' zhèng', + '㼵' => ' dì', + '㼿' => ' tóng', + '㼾' => ' lù', + '㼽' => ' chuǎng', + '㼼' => ' piáo', + '㼻' => ' cóng', + '㼺' => ' táng', + '㼹' => ' kāng', + '㼸' => ' róng', + '㼷' => ' chuán', + '㼶' => ' yú', + '㼛' => ' xíng', + '㼙' => ' zhòu', + '㽏' => ' gàn', + '㻧' => ' duò', + '㻵' => ' zhàn', + '㻲' => ' lǚ', + '㻱' => ' jīn', + '㻰' => ' miǎn', + '㻯' => ' tú', + '㻮' => ' càn', + '㻭' => ' sè', + '㻬' => ' tū', + '㻫' => ' bì', + '㻪' => ' suì', + '㻡' => ' xuē', + '㻷' => ' jí', + '㻠' => ' tú', + '㻟' => ' suí', + '㻞' => ' bīn', + '㻝' => ' là', + '㻗' => ' bèi', + '㻖' => ' dài', + '㻕' => ' jué', + '㻔' => ' duǒ', + '㻓' => ' zōu', + '㻒' => ' jùn', + '㻶' => ' bǐ', + '㻸' => ' zēn', + '㼘' => ' wǎ', + '㼍' => ' lěi', + '㼗' => ' tíng', + '㼖' => ' lì', + '㼕' => ' dāng', + '㼔' => ' wēn', + '㼓' => ' lián', + '㼒' => ' tǎng', + '㼑' => ' liàn', + '㼐' => ' pián', + '㼏' => ' něi', + '㼎' => ' bó', + '㼌' => ' yǔ', + '㻹' => ' xuān', + '㼋' => ' gū', + '㼊' => ' tún', + '㼉' => ' zhèn', + '㼈' => ' luó', + '㼇' => ' qióng', + '㼂' => ' é', + '㻿' => ' shǔ', + '㻾' => ' yōng', + '㻽' => ' suì', + '㻺' => ' lì', + '㽎' => ' tán', + '㽑' => ' tán', + '㻏' => ' líng', + '㾛' => ' qǐn', + '㾥' => ' chù', + '㾤' => ' qiāng', + '㾣' => ' qīn', + '㾢' => ' ài', + '㾡' => ' què', + '㾠' => ' kuáng', + '㾟' => ' bù', + '㾞' => ' yóu', + '㾝' => ' chè', + '㾜' => ' qiè', + '㾚' => ' xiē', + '㾧' => ' kuò', + '㾙' => ' xìn', + '㾘' => ' gěng', + '㾗' => ' liàng', + '㾖' => ' lǐ', + '㾕' => ' shěn', + '㾔' => ' lǚ', + '㾓' => ' yuān', + '㾒' => ' rú', + '㾑' => ' dá', + '㾐' => ' lì', + '㾦' => ' pèi', + '㾨' => ' yī', + '㾎' => ' yā', + '㾸' => ' gǎo', + '㿅' => ' xiǎn', + '㿄' => ' ài', + '㿃' => ' zhì', + '㾿' => ' láng', + '㾾' => ' xiān', + '㾽' => ' zhuì', + '㾼' => ' tuǐ', + '㾻' => ' zhù', + '㾺' => ' mà', + '㾹' => ' chái', + '㾷' => ' xī', + '㾩' => ' guāi', + '㾶' => ' gǔ', + '㾵' => ' jì', + '㾴' => ' zhā', + '㾱' => ' bèi', + '㾰' => ' hú', + '㾯' => ' huī', + '㾮' => ' huáng', + '㾭' => ' zhòu', + '㾫' => ' piān', + '㾪' => ' shěng', + '㾏' => ' jiè', + '㾍' => ' nái', + '㽕' => ' yóu', + '㽢' => ' ǎn', + '㽬' => ' fù', + '㽫' => ' yōng', + '㽪' => ' zěng', + '㽩' => ' càn', + '㽨' => ' cuó', + '㽧' => ' zī', + '㽦' => ' xún', + '㽥' => ' róu', + '㽤' => ' jú', + '㽣' => ' yù', + '㽡' => ' bēi', + '㽯' => ' xí', + '㽠' => ' xiá', + '㽟' => ' liè', + '㽞' => ' liú', + '㽝' => ' lì', + '㽜' => ' wǎn', + '㽛' => ' gōu', + '㽚' => ' chì', + '㽙' => ' jùn', + '㽘' => ' gǎng', + '㽖' => ' nán', + '㽭' => ' ruǎn', + '㽰' => ' shù', + '㾌' => ' xuǎn', + '㾁' => ' shù', + '㾋' => ' xiū', + '㾊' => ' jí', + '㾉' => ' líng', + '㾈' => ' fù', + '㾇' => ' mù', + '㾆' => ' rán', + '㾅' => ' zǐ', + '㾄' => ' dú', + '㾃' => ' tuó', + '㾂' => ' hāi', + '㾀' => ' qiè', + '㽱' => ' jiǎo', + '㽾' => ' wù', + '㽽' => ' gù', + '㽻' => ' zhī', + '㽺' => ' jí', + '㽹' => ' fǎn', + '㽸' => ' chén', + '㽷' => ' shuì', + '㽴' => ' zhàng', + '㽳' => ' xū', + '㽲' => ' jiǎo', + '㻑' => ' jì', + '㻎' => ' lì', + '㷗' => ' jiǒng', + '㸪' => ' chún', + '㸵' => ' guǐ', + '㸳' => ' líng', + '㸲' => ' zuó', + '㸱' => ' tuó', + '㸰' => ' tuó', + '㸯' => ' kē', + '㸮' => ' fén', + '㸭' => ' bā', + '㸬' => ' bèi', + '㸫' => ' qián', + '㸩' => ' ān', + '㸷' => ' shì', + '㸨' => ' jiū', + '㸧' => ' kèn', + '㸦' => ' hù', + '㸥' => ' chàn', + '㸤' => ' piàn', + '㸣' => ' yè', + '㸢' => ' bèi', + '㸡' => ' shū', + '㸠' => ' tiǎo', + '㸟' => ' zhī', + '㸶' => ' yān', + '㸸' => ' hǒu', + '㸝' => ' xiān', + '㹆' => ' huī', + '㹐' => ' chóng', + '㹏' => ' jǐn', + '㹎' => ' léi', + '㹍' => ' dí', + '㹌' => ' chǎn', + '㹋' => ' xiū', + '㹊' => ' yuè', + '㹉' => ' yuán', + '㹈' => ' lí', + '㹇' => ' hé', + '㹅' => ' zǒng', + '㸹' => ' liè', + '㹄' => ' jì', + '㹃' => ' fèi', + '㹂' => ' qiǎn', + '㹁' => ' liáng', + '㹀' => ' bó', + '㸿' => ' dú', + '㸾' => ' rèn', + '㸽' => ' bèi', + '㸻' => ' sì', + '㸺' => ' shā', + '㸞' => ' zhé', + '㸜' => ' kòng', + '㹒' => ' pǔ', + '㷮' => ' zāo', + '㷻' => ' wú', + '㷹' => ' zhào', + '㷸' => ' dié', + '㷷' => ' juǎn', + '㷶' => ' bèi', + '㷵' => ' mò', + '㷴' => ' gé', + '㷳' => ' yàn', + '㷰' => ' chī', + '㷯' => ' bèng', + '㷬' => ' mò', + '㷾' => ' jué', + '㷫' => ' qǐng', + '㷪' => ' cuì', + '㷦' => ' xù', + '㷤' => ' hù', + '㷣' => ' xīng', + '㷢' => ' zhǎ', + '㷡' => ' jiǒng', + '㷠' => ' lín', + '㷟' => ' tuì', + '㷘' => ' tái', + '㷼' => ' yàn', + '㷿' => ' xiān', + '㸛' => ' sháo', + '㸏' => ' mí', + '㸚' => ' lǐ', + '㸙' => ' zhē', + '㸘' => ' wàn', + '㸗' => ' tóng', + '㸖' => ' qū', + '㸕' => ' jué', + '㸓' => ' mì', + '㸒' => ' yín', + '㸑' => ' cuàn', + '㸐' => ' rán', + '㸎' => ' niè', + '㸀' => ' tái', + '㸍' => ' xì', + '㸌' => ' huò', + '㸋' => ' fán', + '㸊' => ' lài', + '㸉' => ' xiè', + '㸇' => ' zuǎn', + '㸅' => ' jié', + '㸄' => ' jì', + '㸃' => ' diǎn', + '㸁' => ' hǎn', + '㹑' => ' sì', + '㹓' => ' yǎo', + '㻍' => ' wú', + '㺜' => ' nóng', + '㺦' => ' lián', + '㺥' => ' chán', + '㺤' => ' xiān', + '㺣' => ' xī', + '㺢' => ' huò', + '㺡' => ' lì', + '㺠' => ' yòu', + '㺟' => ' zhuó', + '㺞' => ' yú', + '㺝' => ' hàn', + '㺛' => ' zhù', + '㺩' => ' jiù', + '㺚' => ' tǎ', + '㺘' => ' zhàn', + '㺗' => ' chān', + '㺖' => ' hǎn', + '㺕' => ' fán', + '㺔' => ' hài', + '㺓' => ' zé', + '㺒' => ' xiāo', + '㺑' => ' shān', + '㺐' => ' lǎo', + '㺨' => ' sī', + '㺪' => ' pú', + '㺎' => ' róng', + '㺺' => ' mào', + '㻌' => ' tú', + '㻋' => ' là', + '㻊' => ' máng', + '㻉' => ' bù', + '㻅' => ' huì', + '㻄' => ' bǎo', + '㻃' => ' qū', + '㻂' => ' píng', + '㻀' => ' yú', + '㺿' => ' yí', + '㺹' => ' biàn', + '㺫' => ' qiú', + '㺸' => ' píng', + '㺷' => ' xù', + '㺵' => ' jiú', + '㺴' => ' bā', + '㺳' => ' méi', + '㺲' => ' niǔ', + '㺱' => ' réng', + '㺮' => ' yú', + '㺭' => ' zǐ', + '㺬' => ' gǒng', + '㺏' => ' lóu', + '㺍' => ' pín', + '㹔' => ' jiāng', + '㹠' => ' tún', + '㹭' => ' yì', + '㹬' => ' shǐ', + '㹫' => ' yí', + '㹨' => ' yòu', + '㹦' => ' diāo', + '㹥' => ' zhù', + '㹤' => ' qiè', + '㹣' => ' zhōng', + '㹢' => ' jiā', + '㹡' => ' xuán', + '㹟' => ' jué', + '㹱' => ' què', + '㹞' => ' yín', + '㹝' => ' shì', + '㹜' => ' yín', + '㹛' => ' ráo', + '㹚' => ' yíng', + '㹙' => ' wěng', + '㹘' => ' rù', + '㹗' => ' tāo', + '㹖' => ' huàn', + '㹕' => ' huān', + '㹮' => ' mò', + '㹲' => ' xiāo', + '㺌' => ' xiàn', + '㺀' => ' hū', + '㺋' => ' wēng', + '㺊' => ' yàng', + '㺉' => ' hù', + '㺈' => ' chī', + '㺇' => ' sī', + '㺅' => ' hóu', + '㺄' => ' yǔ', + '㺃' => ' gǒu', + '㺂' => ' yán', + '㺁' => ' nǎo', + '㹿' => ' zhuó', + '㹳' => ' wú', + '㹾' => ' piǎo', + '㹽' => ' chǎn', + '㹼' => ' jú', + '㹻' => ' wō', + '㹺' => ' tà', + '㹹' => ' gēng', + '㹸' => ' ní', + '㹷' => ' shǐ', + '㹶' => ' tíng', + '㹵' => ' yǐng', + '㹴' => ' gēng', + '冈' => ' gāng', + '冊' => ' cè', + '殔' => ' yì', + '拢' => ' lǒng', + '括' => ' kuò', + '拫' => ' hén', + '拪' => ' qiān', + '择' => ' zé', + '拨' => ' bō', + '拧' => ' níng', + '拦' => ' lán', + '拥' => ' yōng', + '拤' => ' qiá', + '拣' => ' jiǎn', + '拡' => ' kuò', + '拮' => ' jié', + '拠' => ' jù', + '拟' => ' nǐ', + '拞' => ' dǐ', + '拝' => ' bài', + '拜' => ' bài', + '招' => ' zhāo', + '拚' => ' pàn', + '拙' => ' zhuō', + '拘' => ' jū', + '拗' => ' ǎo', + '拭' => ' shì', + '拯' => ' zhěng', + '拕' => ' tuō', + '拼' => ' pīn', + '挆' => ' duǒ', + '挅' => ' duǒ', + '挄' => ' kuò', + '挃' => ' zhì', + '挂' => ' guà', + '持' => ' chí', + '挀' => ' bāi', + '拿' => ' ná', + '拾' => ' shí', + '拽' => ' zhuāi', + '拻' => ' huī', + '拰' => ' nǐn', + '拺' => ' cè', + '拹' => ' xié', + '拸' => ' yí', + '拷' => ' kǎo', + '拶' => ' zā', + '拵' => ' cún', + '拴' => ' shuān', + '拳' => ' quán', + '拲' => ' gǒng', + '拱' => ' gǒng', + '拖' => ' tuō', + '拔' => ' bá', + '挈' => ' qiè', + '抮' => ' zhěn', + '抸' => ' jiā', + '抷' => ' pī', + '抶' => ' chì', + '抵' => ' dǐ', + '抴' => ' yè', + '抳' => ' nǐ', + '抲' => ' hē', + '抱' => ' bào', + '抰' => ' yāng', + '抯' => ' zhā', + '抭' => ' yǎo', + '抺' => ' mèi', + '抬' => ' tái', + '披' => ' pī', + '抪' => ' bù', + '抩' => ' tān', + '抨' => ' pēng', + '抧' => ' zhǐ', + '抦' => ' bǐng', + '报' => ' bào', + '护' => ' hù', + '抣' => ' yun', + '抹' => ' mǒ', + '抻' => ' chēn', + '拓' => ' tà', + '拈' => ' niān', + '拒' => ' jù', + '拑' => ' qián', + '拐' => ' guǎi', + '拏' => ' ná', + '拎' => ' līn', + '拍' => ' pāi', + '拌' => ' bàn', + '拋' => ' pāo', + '拊' => ' fǔ', + '拉' => ' lā', + '拇' => ' mǔ', + '押' => ' yā', + '拆' => ' chāi', + '担' => ' dān', + '拄' => ' zhǔ', + '拃' => ' zhǎ', + '拂' => ' fú', + '拁' => ' jiā', + '拀' => ' chù', + '抿' => ' mǐn', + '抾' => ' qū', + '抽' => ' chōu', + '指' => ' zhǐ', + '按' => ' àn', + '抡' => ' lūn', + '捊' => ' póu', + '捔' => ' jué', + '捓' => ' yé', + '捒' => ' shù', + '捑' => ' zè', + '捐' => ' juān', + '捏' => ' niē', + '捎' => ' shāo', + '捍' => ' hàn', + '捌' => ' bā', + '捋' => ' lǚ', + '捉' => ' zhuō', + '捖' => ' wán', + '捈' => ' tú', + '捇' => ' huò', + '捆' => ' kǔn', + '捅' => ' tǒng', + '捄' => ' jiù', + '捃' => ' jùn', + '捂' => ' wǔ', + '捁' => ' jiǎo', + '捀' => ' féng', + '挿' => ' chā', + '捕' => ' bǔ', + '捗' => ' bù', + '挽' => ' wǎn', + '捤' => ' wei', + '据' => ' jù', + '捭' => ' bǎi', + '捬' => ' fǔ', + '捫' => ' mén', + '捪' => ' mín', + '捩' => ' liè', + '捨' => ' shě', + '捧' => ' pěng', + '捦' => ' qín', + '捥' => ' wàn', + '捣' => ' dǎo', + '捘' => ' zùn', + '换' => ' huàn', + '捡' => ' jiǎn', + '捠' => ' bāng', + '损' => ' sǔn', + '捞' => ' lāo', + '捝' => ' tuō', + '捜' => ' sōu', + '捛' => ' lǚ', + '捚' => ' zhāi', + '捙' => ' yì', + '挾' => ' xié', + '挼' => ' ruá', + '挊' => ' nòng', + '挖' => ' wā', + '挠' => ' náo', + '挟' => ' xié', + '挞' => ' tà', + '挝' => ' wō', + '挜' => ' yà', + '挛' => ' luán', + '挚' => ' zhì', + '挙' => ' jǔ', + '挘' => ' lie', + '挗' => ' jué', + '挕' => ' dié', + '挢' => ' jiǎo', + '挔' => ' lǚ', + '挓' => ' zhā', + '挒' => ' liè', + '挑' => ' tiāo', + '挐' => ' ná', + '挏' => ' dòng', + '挎' => ' kuà', + '挍' => ' jiào', + '挌' => ' gé', + '挋' => ' zhèn', + '挡' => ' dǎng', + '挣' => ' zhēng', + '挻' => ' shān', + '挰' => ' chéng', + '挺' => ' tǐng', + '挹' => ' yì', + '挸' => ' jiǎn', + '挷' => ' péng', + '挶' => ' jū', + '挵' => ' lòng', + '挴' => ' měi', + '挳' => ' kēng', + '挲' => ' sā', + '挱' => ' sā', + '振' => ' zhèn', + '挤' => ' jǐ', + '挮' => ' tǐ', + '挭' => ' gěng', + '挬' => ' bó', + '挫' => ' cuò', + '挪' => ' nuó', + '挩' => ' tuō', + '挨' => ' āi', + '挧' => ' yu', + '挦' => ' xián', + '挥' => ' huī', + '抢' => ' qiǎng', + '抠' => ' kōu', + '捰' => ' wǒ', + '戒' => ' jiè', + '戜' => ' dié', + '戛' => ' jiá', + '戚' => ' qī', + '戙' => ' dòng', + '战' => ' zhàn', + '戗' => ' qiāng', + '或' => ' huò', + '戕' => ' qiāng', + '戔' => ' jiān', + '戓' => ' gē', + '我' => ' wǒ', + '戞' => ' jiá', + '成' => ' chéng', + '戏' => ' xì', + '戎' => ' róng', + '戍' => ' shù', + '戌' => ' xū', + '戋' => ' jiān', + '戊' => ' wù', + '戉' => ' yuè', + '戈' => ' gē', + '戇' => ' zhuàng', + '戝' => ' zéi', + '戟' => ' jǐ', + '戅' => ' gàng', + '戬' => ' jiǎn', + '戶' => ' hù', + '戵' => ' qú', + '戴' => ' dài', + '戳' => ' chuō', + '戲' => ' xì', + '戱' => ' xì', + '戰' => ' zhàn', + '戯' => ' hū', + '戮' => ' lù', + '戭' => ' yǎn', + '戫' => ' yù', + '戠' => ' zhī', + '截' => ' jié', + '戩' => ' jiǎn', + '戨' => ' gē', + '戧' => ' qiāng', + '戦' => ' zhàn', + '戥' => ' děng', + '戤' => ' gài', + '戣' => ' kuí', + '戢' => ' jí', + '戡' => ' kān', + '戆' => ' gàng', + '戄' => ' jué', + '戸' => ' hù', + '懞' => ' méng', + '懨' => ' yān', + '懧' => ' nuò', + '懦' => ' nuò', + '懥' => ' zhì', + '懤' => ' chóu', + '懣' => ' mèn', + '懢' => ' lán', + '懡' => ' mǒ', + '懠' => ' qí', + '懟' => ' duì', + '懝' => ' ài', + '懪' => ' bó', + '懜' => ' měng', + '懛' => ' dāi', + '懚' => ' yìn', + '懙' => ' yǔ', + '懘' => ' chì', + '懗' => ' xià', + '懖' => ' kuò', + '懕' => ' yān', + '懔' => ' lǐn', + '懓' => ' ài', + '懩' => ' yǎng', + '懫' => ' zhì', + '戃' => ' tǎng', + '懸' => ' xuán', + '戂' => ' mí', + '戁' => ' nǎn', + '戀' => ' liàn', + '懿' => ' yì', + '懾' => ' shè', + '懽' => ' huān', + '懼' => ' jù', + '懻' => ' jì', + '懺' => ' chàn', + '懹' => ' ràng', + '懷' => ' huái', + '懬' => ' kuàng', + '懶' => ' lǎn', + '懵' => ' měng', + '懴' => ' chàn', + '懳' => ' hui', + '懲' => ' chéng', + '懱' => ' miè', + '懰' => ' liú', + '懯' => ' fū', + '懮' => ' yǒu', + '懭' => ' kuǎng', + '户' => ' hù', + '戹' => ' è', + '抟' => ' tuán', + '扺' => ' zhǐ', + '抄' => ' chāo', + '抃' => ' biàn', + '抂' => ' kuáng', + '抁' => ' yǎn', + '技' => ' jì', + '承' => ' chéng', + '找' => ' zhǎo', + '扽' => ' dèn', + '扼' => ' è', + '扻' => ' zhì', + '批' => ' pī', + '抆' => ' wěn', + '扸' => ' xī', + '扷' => ' ào', + '扶' => ' fú', + '扵' => ' yú', + '扴' => ' jiá', + '扳' => ' bān', + '扲' => ' qián', + '扱' => ' xī', + '扰' => ' rǎo', + '扯' => ' chě', + '抅' => ' jū', + '抇' => ' hú', + '扭' => ' niǔ', + '抔' => ' póu', + '択' => ' zé', + '抝' => ' ǎo', + '抜' => ' bá', + '抛' => ' pāo', + '抚' => ' fǔ', + '抙' => ' póu', + '折' => ' zhé', + '抗' => ' kàng', + '抖' => ' dǒu', + '投' => ' tóu', + '抓' => ' zhuā', + '抈' => ' yuè', + '抒' => ' shū', + '抑' => ' yì', + '抐' => ' nè', + '抏' => ' wán', + '抎' => ' yǔn', + '抍' => ' zhěng', + '抌' => ' dǎn', + '抋' => ' qìn', + '把' => ' bǎ', + '抉' => ' jué', + '扮' => ' bàn', + '扬' => ' yáng', + '戺' => ' shì', + '扆' => ' yǐ', + '扐' => ' lè', + '扏' => ' qiú', + '扎' => ' zhā', + '才' => ' cái', + '扌' => ' shou', + '手' => ' shǒu', + '扊' => ' yǎn', + '扉' => ' fēi', + '扈' => ' hù', + '扇' => ' shàn', + '扅' => ' yí', + '扒' => ' bā', + '扄' => ' shǎng', + '扃' => ' jiōng', + '扂' => ' diàn', + '扁' => ' biǎn', + '所' => ' suǒ', + '房' => ' fáng', + '戾' => ' lì', + '戽' => ' hù', + '戼' => ' mǎo', + '戻' => ' tì', + '扑' => ' pū', + '打' => ' dǎ', + '扫' => ' sǎo', + '扠' => ' chā', + '扪' => ' mén', + '扩' => ' kuò', + '扨' => ' rèn', + '执' => ' zhí', + '扦' => ' qiān', + '扥' => ' dèn', + '扤' => ' wù', + '扣' => ' kòu', + '扢' => ' gǔ', + '扡' => ' tuō', + '扟' => ' shēn', + '扔' => ' rēng', + '扞' => ' gǎn', + '扝' => ' kū', + '扜' => ' yū', + '扛' => ' káng', + '扚' => ' diǎo', + '扙' => ' zhàng', + '托' => ' tuō', + '扗' => ' zài', + '扖' => ' ru', + '払' => ' fǎn', + '捯' => ' dáo', + '捱' => ' ái', + '懑' => ' mèn', + '撃' => ' jí', + '撍' => ' zǎn', + '撌' => ' guì', + '撋' => ' ruán', + '撊' => ' xiàn', + '撉' => ' dūn', + '撈' => ' lāo', + '撇' => ' piē', + '撆' => ' piē', + '撅' => ' juē', + '撄' => ' yīng', + '撂' => ' liào', + '撏' => ' xián', + '撁' => ' qiān', + '撀' => ' gòu', + '摿' => ' yīn', + '摾' => ' jiàng', + '摽' => ' biāo', + '摼' => ' kēng', + '摻' => ' càn', + '摺' => ' zhé', + '摹' => ' mó', + '摸' => ' mō', + '撎' => ' yì', + '撐' => ' chēng', + '摶' => ' tuán', + '撝' => ' huī', + '撧' => ' juē', + '撦' => ' chě', + '撥' => ' bō', + '撤' => ' chè', + '撣' => ' dǎn', + '撢' => ' dǎn', + '撡' => ' cāo', + '撠' => ' jǐ', + '撟' => ' jiǎo', + '撞' => ' zhuàng', + '撜' => ' zhěng', + '撑' => ' chēng', + '撛' => ' lǐn', + '撚' => ' niǎn', + '撙' => ' zǔn', + '撘' => ' dā', + '撗' => ' guàng', + '撖' => ' hàn', + '撕' => ' sī', + '撔' => ' hòng', + '撓' => ' náo', + '撒' => ' sā', + '摷' => ' jiǎo', + '摵' => ' shè', + '撩' => ' liāo', + '摏' => ' chōng', + '摙' => ' liǎn', + '摘' => ' zhāi', + '摗' => ' sōu', + '摖' => ' qì', + '摕' => ' dì', + '摔' => ' shuāi', + '摓' => ' féng', + '摒' => ' bǐng', + '摑' => ' guāi', + '摐' => ' chuāng', + '摎' => ' jiū', + '摛' => ' chī', + '摍' => ' suō', + '摌' => ' chǎn', + '摋' => ' sà', + '摊' => ' tān', + '摉' => ' sōu', + '摈' => ' bìn', + '摇' => ' yáo', + '摆' => ' bǎi', + '摅' => ' shū', + '摄' => ' shè', + '摚' => ' chēng', + '摜' => ' guàn', + '摴' => ' chū', + '摩' => ' mó', + '摳' => ' kōu', + '摲' => ' chàn', + '摱' => ' màn', + '摰' => ' niè', + '摯' => ' zhì', + '摮' => ' áo', + '摭' => ' zhí', + '摬' => ' yǐng', + '摫' => ' guī', + '摪' => ' jiāng', + '摨' => ' nái', + '摝' => ' lù', + '摧' => ' cuī', + '摦' => ' huà', + '摥' => ' tàng', + '摤' => ' chuǎng', + '摣' => ' zhā', + '摢' => ' hù', + '摡' => ' gài', + '摠' => ' zǒng', + '摟' => ' lǒu', + '摞' => ' luò', + '撨' => ' fǔ', + '撪' => ' bèn', + '摂' => ' shè', + '擫' => ' yè', + '擵' => ' mó', + '擴' => ' kuò', + '擳' => ' zhì', + '擲' => ' zhì', + '擱' => ' gē', + '擰' => ' níng', + '擯' => ' bìn', + '擮' => ' jí', + '擭' => ' wò', + '擬' => ' nǐ', + '擪' => ' yè', + '擷' => ' xié', + '擩' => ' rǔ', + '擨' => ' yé', + '擧' => ' jǔ', + '擦' => ' cā', + '擥' => ' lǎn', + '擤' => ' xǐng', + '擣' => ' dǎo', + '擢' => ' zhuó', + '擡' => ' tái', + '擠' => ' jǐ', + '擶' => ' jiàn', + '擸' => ' liè', + '擞' => ' sǒu', + '攅' => ' zǎn', + '攏' => ' lǒng', + '攎' => ' lú', + '攍' => ' yíng', + '攌' => ' huǎn', + '攋' => ' là', + '攊' => ' lì', + '攉' => ' huō', + '攈' => ' jùn', + '攇' => ' xiǎn', + '攆' => ' niǎn', + '攄' => ' shū', + '擹' => ' tān', + '攃' => ' cā', + '攂' => ' lèi', + '攁' => ' yǎng', + '攀' => ' pān', + '擿' => ' tī', + '擾' => ' rǎo', + '擽' => ' lüè', + '擼' => ' lǔ', + '擻' => ' sǒu', + '擺' => ' bǎi', + '擟' => ' mí', + '擝' => ' meng', + '撫' => ' fǔ', + '撷' => ' xié', + '擁' => ' yōng', + '擀' => ' gǎn', + '撿' => ' jiǎn', + '撾' => ' wō', + '撽' => ' qiào', + '撼' => ' hàn', + '撻' => ' tà', + '撺' => ' cuān', + '撹' => ' jiǎo', + '撸' => ' lū', + '撶' => ' huá', + '擃' => ' nǎng', + '撵' => ' niǎn', + '撴' => ' dūn', + '撳' => ' qìn', + '撲' => ' pū', + '撱' => ' wěi', + '撰' => ' zhuàn', + '撯' => ' zhuó', + '撮' => ' cuō', + '播' => ' bō', + '撬' => ' qiào', + '擂' => ' léi', + '擄' => ' lǔ', + '擜' => ' e', + '擑' => ' jiē', + '擛' => ' yè', + '據' => ' jù', + '擙' => ' ào', + '擘' => ' bāi', + '擗' => ' pǐ', + '擖' => ' kā', + '擕' => ' xié', + '擔' => ' dān', + '擓' => ' kuǎi', + '擒' => ' qín', + '擐' => ' huàn', + '擅' => ' shàn', + '擏' => ' qíng', + '擎' => ' qíng', + '操' => ' cāo', + '擌' => ' sè', + '擋' => ' dǎng', + '擊' => ' jī', + '擉' => ' chuò', + '擈' => ' pū', + '擇' => ' zé', + '擆' => ' zhuó', + '摃' => ' káng', + '摁' => ' èn', + '捲' => ' juǎn', + '掲' => ' jiē', + '掼' => ' guàn', + '掻' => ' sāo', + '掺' => ' càn', + '掹' => ' meng', + '掸' => ' dǎn', + '掷' => ' zhì', + '掶' => ' geng', + '掵' => ' ming', + '掴' => ' guāi', + '掳' => ' lǔ', + '掱' => ' pá', + '掾' => ' yuàn', + '掰' => ' bāi', + '掯' => ' kèn', + '掮' => ' qián', + '掭' => ' tiàn', + '掬' => ' jū', + '掫' => ' zhōu', + '措' => ' cuò', + '掩' => ' yǎn', + '推' => ' tuī', + '控' => ' kòng', + '掽' => ' pèng', + '掿' => ' nuò', + '接' => ' jiē', + '揌' => ' sāi', + '揖' => ' yī', + '揕' => ' zhèn', + '揔' => ' zǒng', + '揓' => ' shì', + '插' => ' chā', + '揑' => ' niē', + '提' => ' tí', + '描' => ' miáo', + '揎' => ' xuān', + '揍' => ' zòu', + '揋' => ' wēi', + '揀' => ' jiǎn', + '揊' => ' pì', + '揉' => ' róu', + '揈' => ' hōng', + '揇' => ' nǎn', + '揆' => ' kuí', + '揅' => ' yán', + '揄' => ' yú', + '揃' => ' jiǎn', + '揂' => ' jiū', + '揁' => ' zhēng', + '掦' => ' tì', + '掤' => ' bīng', + '揘' => ' yóng', + '捾' => ' wò', + '授' => ' shòu', + '掇' => ' duō', + '掆' => ' gāng', + '掅' => ' qìng', + '掄' => ' lūn', + '掃' => ' sǎo', + '掂' => ' diān', + '掁' => ' chéng', + '掀' => ' xiān', + '捿' => ' xī', + '捽' => ' zuó', + '掊' => ' póu', + '捼' => ' ruó', + '捻' => ' niǎn', + '捺' => ' nà', + '捹' => ' bèn', + '捸' => ' tū', + '捷' => ' jié', + '捶' => ' chuí', + '捵' => ' chēn', + '捴' => ' zǒng', + '捳' => ' yuè', + '掉' => ' diào', + '掋' => ' dǐ', + '掣' => ' chè', + '掘' => ' jué', + '探' => ' tàn', + '採' => ' cǎi', + '掠' => ' lüè', + '掟' => ' zhěng', + '掞' => ' shàn', + '掝' => ' huò', + '掜' => ' yì', + '掛' => ' guà', + '掚' => ' liǎng', + '掙' => ' zhēng', + '掗' => ' yà', + '掌' => ' zhǎng', + '掖' => ' yē', + '掕' => ' líng', + '掔' => ' qiān', + '掓' => ' shū', + '排' => ' pái', + '掑' => ' qí', + '掐' => ' qiā', + '掏' => ' tāo', + '掎' => ' jǐ', + '掍' => ' hùn', + '揗' => ' xún', + '揙' => ' biān', + '摀' => ' wǔ', + '搛' => ' jiān', + '搥' => ' chuí', + '搤' => ' è', + '搣' => ' miè', + '搢' => ' jìn', + '搡' => ' sǎng', + '搠' => ' shuò', + '搟' => ' xiǎn', + '搞' => ' gǎo', + '搝' => ' qiǔ', + '搜' => ' sōu', + '搚' => ' lā', + '搧' => ' shān', + '搙' => ' nù', + '搘' => ' zhī', + '搗' => ' dǎo', + '搖' => ' yáo', + '搕' => ' kē', + '搔' => ' sāo', + '搓' => ' cuō', + '搒' => ' bàng', + '搑' => ' róng', + '搐' => ' chù', + '搦' => ' nuò', + '搨' => ' tà', + '搎' => ' sūn', + '搵' => ' wèn', + '搿' => ' gé', + '搾' => ' zhà', + '搽' => ' chá', + '搼' => ' quán', + '搻' => ' nuò', + '携' => ' xié', + '搹' => ' è', + '搸' => ' zhēn', + '搷' => ' tián', + '搶' => ' qiǎng', + '搴' => ' qiān', + '搩' => ' zhǎ', + '搳' => ' huá', + '搲' => ' wā', + '搱' => ' zhì', + '搰' => ' hú', + '搯' => ' tāo', + '搮' => ' lì', + '搭' => ' dā', + '搬' => ' bān', + '搫' => ' pán', + '搪' => ' táng', + '搏' => ' bó', + '損' => ' sǔn', + '揚' => ' yáng', + '揦' => ' lá', + '揰' => ' chòng', + '揯' => ' gèn', + '揮' => ' huī', + '揭' => ' jiē', + '揬' => ' tú', + '揫' => ' jiū', + '揪' => ' jiū', + '揩' => ' kāi', + '揨' => ' chén', + '揧' => ' là', + '揥' => ' tì', + '揲' => ' dié', + '揤' => ' jí', + '揣' => ' chuāi', + '揢' => ' ké', + '握' => ' wò', + '揠' => ' yà', + '揟' => ' xū', + '揞' => ' ǎn', + '揝' => ' zǎn', + '揜' => ' yǎn', + '換' => ' huàn', + '揱' => ' xiāo', + '揳' => ' xiē', + '搌' => ' zhǎn', + '搁' => ' gē', + '搋' => ' chuāi', + '搊' => ' chōu', + '搉' => ' què', + '搈' => ' róng', + '搇' => ' qìn', + '搆' => ' gòu', + '搅' => ' jiǎo', + '搄' => ' gēng', + '搃' => ' zǒng', + '搂' => ' lǒu', + '搀' => ' chān', + '援' => ' yuán', + '揿' => ' qìn', + '揾' => ' wèn', + '揽' => ' lǎn', + '揼' => ' beng', + '揻' => ' wēi', + '揺' => ' yáo', + '揹' => ' bēi', + '揸' => ' zhā', + '揷' => ' chā', + '揶' => ' yé', + '揵' => ' qián', + '懒' => ' lǎn', + '懐' => ' huái', + '攑' => ' qiān', + '御' => ' yù', + '徫' => ' wěi', + '循' => ' xún', + '復' => ' fù', + '徨' => ' huáng', + '徧' => ' biàn', + '徦' => ' jiǎ', + '徥' => ' shì', + '徤' => ' jiàn', + '徣' => ' jiè', + '徢' => ' xiè', + '徠' => ' lái', + '徭' => ' yáo', + '徟' => ' zhōu', + '從' => ' cóng', + '徝' => ' zhì', + '徜' => ' cháng', + '徛' => ' jì', + '徚' => ' dōng', + '徙' => ' xǐ', + '徘' => ' pái', + '得' => ' dé', + '徖' => ' cóng', + '徬' => ' páng', + '微' => ' wēi', + '徔' => ' zhi', + '徻' => ' huì', + '必' => ' bì', + '忄' => ' xin', + '心' => ' xīn', + '忂' => ' qú', + '忁' => ' bào', + '忀' => ' xiāng', + '徿' => ' lòng', + '徾' => ' méi', + '徽' => ' huī', + '徼' => ' jiǎo', + '徺' => ' jiǎo', + '徯' => ' xī', + '徹' => ' chè', + '徸' => ' chōng', + '德' => ' dé', + '徶' => ' bié', + '徵' => ' zhēng', + '徴' => ' zhēng', + '徳' => ' dé', + '徲' => ' tí', + '徱' => ' piào', + '徰' => ' zhēng', + '徕' => ' lái', + '従' => ' cóng', + '忇' => ' lè', + '彭' => ' péng', + '彷' => ' fǎng', + '彶' => ' jí', + '彵' => ' tuǒ', + '彴' => ' zhuó', + '彳' => ' chì', + '彲' => ' chī', + '影' => ' yǐng', + '彰' => ' zhāng', + '彯' => ' piǎo', + '彮' => ' yǒng', + '彬' => ' bīn', + '役' => ' yì', + '彫' => ' diāo', + '彪' => ' biāo', + '彩' => ' cǎi', + '彨' => ' chī', + '彧' => ' yù', + '彦' => ' yàn', + '彥' => ' yàn', + '彤' => ' tóng', + '彣' => ' wén', + '形' => ' xíng', + '彸' => ' zhōng', + '彺' => ' wáng', + '徒' => ' tú', + '徇' => ' xùn', + '徑' => ' jìng', + '徐' => ' xú', + '徏' => ' zhì', + '徎' => ' chěng', + '徍' => ' wǎng', + '後' => ' hòu', + '律' => ' lǜ', + '徊' => ' huái', + '徉' => ' yáng', + '很' => ' hěn', + '徆' => ' xī', + '彻' => ' chè', + '待' => ' dài', + '径' => ' jìng', + '徃' => ' wǎng', + '徂' => ' cú', + '征' => ' zhēng', + '往' => ' wǎng', + '彿' => ' fú', + '彾' => ' líng', + '彽' => ' dī', + '彼' => ' bǐ', + '忆' => ' yì', + '忈' => ' rén', + '彠' => ' yuē', + '怉' => ' bǎo', + '怓' => ' náo', + '怒' => ' nù', + '怑' => ' bàn', + '怐' => ' jù', + '怏' => ' yàng', + '怎' => ' zěn', + '怍' => ' zuò', + '怌' => ' pēi', + '怋' => ' mín', + '怊' => ' chāo', + '怈' => ' yì', + '怕' => ' pà', + '怇' => ' jù', + '怆' => ' chuàng', + '怅' => ' chàng', + '怄' => ' òu', + '怃' => ' wǔ', + '怂' => ' sǒng', + '态' => ' tài', + '怀' => ' huái', + '忿' => ' fèn', + '忾' => ' kài', + '怔' => ' zhēng', + '怖' => ' bù', + '忼' => ' kāng', + '怣' => ' yóu', + '怭' => ' bì', + '怬' => ' xì', + '怫' => ' fú', + '怪' => ' guài', + '怩' => ' ní', + '怨' => ' yuàn', + '性' => ' xìng', + '怦' => ' pēng', + '急' => ' jí', + '怤' => ' fū', + '怢' => ' tū', + '怗' => ' tiē', + '怡' => ' yí', + '怠' => ' dài', + '怟' => ' dì', + '怞' => ' chóu', + '思' => ' sī', + '怜' => ' lián', + '怛' => ' dá', + '怚' => ' jù', + '怙' => ' hù', + '怘' => ' hù', + '忽' => ' hū', + '忻' => ' xīn', + '忉' => ' dāo', + '忕' => ' shì', + '忟' => ' wěn', + '忞' => ' mín', + '忝' => ' tiǎn', + '応' => ' yīng', + '忛' => ' fán', + '忚' => ' xī', + '忙' => ' máng', + '忘' => ' wàng', + '志' => ' zhì', + '忖' => ' cǔn', + '忔' => ' qì', + '忡' => ' chōng', + '忓' => ' gān', + '忒' => ' tè', + '忑' => ' tè', + '忐' => ' tǎn', + '忏' => ' chàn', + '忎' => ' rén', + '忍' => ' rěn', + '忌' => ' jì', + '忋' => ' gǎi', + '忊' => ' dìng', + '忠' => ' zhōng', + '忢' => ' wù', + '忺' => ' xiān', + '忯' => ' qí', + '忹' => ' kuáng', + '忸' => ' niǔ', + '忷' => ' xiōng', + '忶' => ' hún', + '念' => ' niàn', + '忴' => ' qián', + '忳' => ' tún', + '忲' => ' tài', + '忱' => ' chén', + '忰' => ' cuì', + '忮' => ' zhì', + '忣' => ' jí', + '忭' => ' biàn', + '忬' => ' yù', + '快' => ' kuài', + '忪' => ' sōng', + '忩' => ' cōng', + '忨' => ' wàn', + '忧' => ' yōu', + '忦' => ' jiá', + '忥' => ' xì', + '忤' => ' wǔ', + '彡' => ' shān', + '彟' => ' yuē', + '怯' => ' qiè', + '廑' => ' jǐn', + '廛' => ' chán', + '廚' => ' chú', + '廙' => ' yì', + '廘' => ' lù', + '廗' => ' dài', + '廖' => ' liào', + '廕' => ' yìn', + '廔' => ' lóu', + '廓' => ' kuò', + '廒' => ' áo', + '廐' => ' jiù', + '廝' => ' sī', + '廏' => ' jiù', + '廎' => ' qǐng', + '廍' => ' pǒu', + '廌' => ' zhì', + '廋' => ' sōu', + '廊' => ' láng', + '廉' => ' lián', + '廈' => ' shà', + '廇' => ' liù', + '廆' => ' guī', + '廜' => ' tú', + '廞' => ' xīn', + '廄' => ' jiù', + '廫' => ' liáo', + '廵' => ' xún', + '廴' => ' yǐn', + '廳' => ' tīng', + '廲' => ' lí', + '廱' => ' yōng', + '廰' => ' tīng', + '廯' => ' xiān', + '廮' => ' yǐng', + '廭' => ' ji', + '廬' => ' lú', + '廪' => ' lǐn', + '廟' => ' miào', + '廩' => ' lǐn', + '廨' => ' xiè', + '廧' => ' qiáng', + '廦' => ' bì', + '廥' => ' kuài', + '廤' => ' kù', + '廣' => ' guǎng', + '廢' => ' fèi', + '廡' => ' wǔ', + '廠' => ' chǎng', + '廅' => ' è', + '廃' => ' fèi', + '廷' => ' tíng', + '庝' => ' tóng', + '座' => ' zuò', + '度' => ' dù', + '庥' => ' xiū', + '庤' => ' zhì', + '庣' => ' tiāo', + '庢' => ' zhì', + '庡' => ' yǐ', + '庠' => ' xiáng', + '废' => ' fèi', + '庞' => ' páng', + '府' => ' fǔ', + '庩' => ' tú', + '庛' => ' cì', + '庚' => ' gēng', + '庙' => ' miào', + '庘' => ' yā', + '店' => ' diàn', + '庖' => ' páo', + '底' => ' dǐ', + '应' => ' yīng', + '库' => ' kù', + '庒' => ' zhuang', + '庨' => ' xiāo', + '庪' => ' guǐ', + '廂' => ' xiāng', + '康' => ' kāng', + '廁' => ' cè', + '廀' => ' sōu', + '庿' => ' miào', + '庾' => ' yǔ', + '庽' => ' yù', + '庼' => ' qǐng', + '庻' => ' shù', + '庺' => ' sōng', + '庹' => ' tuǒ', + '庸' => ' yōng', + '庶' => ' shù', + '庫' => ' kù', + '庵' => ' ān', + '庴' => ' jí', + '庳' => ' bì', + '庲' => ' lái', + '庱' => ' chěng', + '庰' => ' bìng', + '庯' => ' bū', + '庮' => ' yǒu', + '庭' => ' tíng', + '庬' => ' máng', + '延' => ' yán', + '廸' => ' dí', + '彞' => ' yí', + '弹' => ' dàn', + '彃' => ' bì', + '彂' => ' fā', + '彁' => ' ge', + '彀' => ' gòu', + '弿' => ' jiǎn', + '弾' => ' dàn', + '弽' => ' shè', + '弼' => ' bì', + '弻' => ' bì', + '强' => ' qiáng', + '弸' => ' péng', + '彅' => ' jian', + '強' => ' qiáng', + '弶' => ' jiàng', + '張' => ' zhāng', + '弴' => ' diāo', + '弳' => ' jìng', + '弲' => ' xuān', + '弱' => ' ruò', + '弰' => ' shāo', + '弯' => ' wān', + '弮' => ' quān', + '彄' => ' kōu', + '彆' => ' biè', + '弬' => ' yí', + '当' => ' dāng', + '彝' => ' yí', + '彜' => ' yí', + '彛' => ' yí', + '彚' => ' huì', + '彙' => ' huì', + '彘' => ' zhì', + '彗' => ' huì', + '彖' => ' tuàn', + '录' => ' lù', + '彔' => ' lù', + '归' => ' guī', + '彇' => ' xiāo', + '彑' => ' jì', + '彐' => ' jì', + '彏' => ' jué', + '彎' => ' wān', + '彍' => ' guō', + '彌' => ' mí', + '彋' => ' hóng', + '彊' => ' jiàng', + '彉' => ' guō', + '彈' => ' dàn', + '弭' => ' mǐ', + '弫' => ' zhěn', + '廹' => ' pǎi', + '弅' => ' fèn', + '式' => ' shì', + '弎' => ' sān', + '弍' => ' èr', + '弌' => ' yī', + '弋' => ' yì', + '弊' => ' bì', + '弉' => ' zàng', + '弈' => ' yì', + '弇' => ' yǎn', + '弆' => ' jǔ', + '弄' => ' nòng', + '弑' => ' shì', + '弃' => ' qì', + '异' => ' yì', + '弁' => ' biàn', + '开' => ' kāi', + '廿' => ' niàn', + '廾' => ' gǒng', + '廽' => ' huí', + '廼' => ' nǎi', + '廻' => ' huí', + '建' => ' jiàn', + '弐' => ' èr', + '弒' => ' shì', + '弪' => ' jìng', + '弟' => ' dì', + '弩' => ' nǔ', + '弨' => ' chāo', + '弧' => ' hú', + '弦' => ' xián', + '弥' => ' mí', + '弤' => ' dǐ', + '弣' => ' fǔ', + '弢' => ' tāo', + '弡' => ' jué', + '张' => ' zhāng', + '弞' => ' shěn', + '弓' => ' gōng', + '弝' => ' bà', + '弜' => ' jiàng', + '弛' => ' chí', + '弚' => ' tuí', + '弙' => ' wū', + '弘' => ' hóng', + '弗' => ' fú', + '弖' => ' hù', + '引' => ' yǐn', + '弔' => ' diào', + '怮' => ' yōu', + '怰' => ' xuàn', + '懏' => ' jùn', + '慂' => ' yǒng', + '慌' => ' huāng', + '態' => ' tài', + '慊' => ' qiàn', + '慉' => ' xù', + '慈' => ' cí', + '慇' => ' yīn', + '慆' => ' tāo', + '慅' => ' sāo', + '慄' => ' lì', + '慃' => ' yǎng', + '慁' => ' hùn', + '慎' => ' shèn', + '慀' => ' xì', + '愿' => ' yuàn', + '愾' => ' kài', + '愽' => ' bó', + '愼' => ' shèn', + '愻' => ' xùn', + '愺' => ' cǎo', + '愹' => ' yǒng', + '愸' => ' zhěng', + '愷' => ' kǎi', + '慍' => ' yùn', + '慏' => ' mǐng', + '愵' => ' nì', + '慜' => ' mǐn', + '慦' => ' jiù', + '慥' => ' zào', + '慤' => ' què', + '慣' => ' guàn', + '慢' => ' màn', + '慡' => ' shuǎng', + '慠' => ' ào', + '慟' => ' tòng', + '慞' => ' zhāng', + '慝' => ' tè', + '慛' => ' cuī', + '慐' => ' gong', + '慚' => ' cán', + '慙' => ' cán', + '慘' => ' cǎn', + '慗' => ' chì', + '慖' => ' guó', + '慕' => ' mù', + '慔' => ' mù', + '慓' => ' piāo', + '慒' => ' cóng', + '慑' => ' shè', + '愶' => ' xié', + '愴' => ' chuàng', + '慨' => ' kǎi', + '愎' => ' bì', + '愘' => ' qià', + '愗' => ' mào', + '愖' => ' chén', + '愕' => ' è', + '愔' => ' yīn', + '愓' => ' dàng', + '愒' => ' kài', + '愑' => ' yǒng', + '愐' => ' miǎn', + '意' => ' yì', + '愍' => ' mǐn', + '愚' => ' yú', + '愌' => ' huàn', + '愋' => ' xuān', + '愊' => ' bì', + '愉' => ' yú', + '愈' => ' yù', + '愇' => ' wěi', + '愆' => ' qiān', + '愅' => ' gé', + '愄' => ' wēi', + '愃' => ' xuān', + '愙' => ' kè', + '愛' => ' ài', + '愳' => ' jù', + '愨' => ' què', + '愲' => ' gǔ', + '愱' => ' jí', + '愰' => ' huàng', + '愯' => ' sǒng', + '愮' => ' yáo', + '愭' => ' qí', + '愬' => ' sù', + '愫' => ' sù', + '愪' => ' yún', + '愩' => ' gōng', + '愧' => ' kuì', + '愜' => ' qiè', + '愦' => ' kuì', + '愥' => ' ying', + '愤' => ' fèn', + '愣' => ' lèng', + '愢' => ' sāi', + '愡' => ' zǒng', + '愠' => ' yùn', + '感' => ' gǎn', + '愞' => ' nuò', + '愝' => ' yǎn', + '慧' => ' huì', + '慩' => ' lián', + '愁' => ' chóu', + '憪' => ' xián', + '憴' => ' shéng', + '憳' => ' tǎn', + '憲' => ' xiàn', + '憱' => ' cù', + '憰' => ' jué', + '憯' => ' cǎn', + '憮' => ' wǔ', + '憭' => ' liǎo', + '憬' => ' jǐng', + '憫' => ' mǐn', + '憩' => ' qì', + '憶' => ' yì', + '憨' => ' hān', + '憧' => ' chōng', + '憦' => ' lào', + '憥' => ' láo', + '憤' => ' fèn', + '憣' => ' fān', + '憢' => ' xiāo', + '憡' => ' cè', + '憠' => ' jué', + '憟' => ' sù', + '憵' => ' pī', + '憷' => ' chù', + '憝' => ' duì', + '懄' => ' qín', + '懎' => ' sè', + '懍' => ' lǐn', + '懌' => ' yì', + '懋' => ' mào', + '懊' => ' ào', + '應' => ' yīng', + '懈' => ' xiè', + '懇' => ' kěn', + '懆' => ' cǎo', + '懅' => ' jù', + '懃' => ' qín', + '憸' => ' xiān', + '懂' => ' dǒng', + '懁' => ' xuān', + '懀' => ' wèi', + '憿' => ' jiǎo', + '憾' => ' hàn', + '憽' => ' sōng', + '憼' => ' jǐng', + '憻' => ' tǎn', + '憺' => ' dàn', + '憹' => ' náo', + '憞' => ' duì', + '憜' => ' duǒ', + '慪' => ' òu', + '慶' => ' qìng', + '憀' => ' liáo', + '慿' => ' píng', + '慾' => ' yù', + '慽' => ' qī', + '慼' => ' qī', + '慻' => ' juàn', + '慺' => ' lóu', + '慹' => ' zhí', + '慸' => ' dì', + '慷' => ' kāng', + '慵' => ' yōng', + '憂' => ' yōu', + '慴' => ' shè', + '慳' => ' qiān', + '慲' => ' mán', + '慱' => ' tuán', + '慰' => ' wèi', + '慯' => ' shāng', + '慮' => ' lǜ', + '慭' => ' yìn', + '慬' => ' qín', + '慫' => ' sǒng', + '憁' => ' còng', + '憃' => ' chōng', + '憛' => ' tán', + '憐' => ' lián', + '憚' => ' dàn', + '憙' => ' xī', + '憘' => ' xǐ', + '憗' => ' yìn', + '憖' => ' yìn', + '憕' => ' chéng', + '憔' => ' qiáo', + '憓' => ' huì', + '憒' => ' kuì', + '憑' => ' píng', + '憏' => ' chì', + '憄' => ' zhì', + '憎' => ' zēng', + '憍' => ' jiāo', + '憌' => ' qióng', + '憋' => ' biē', + '憊' => ' bèi', + '憉' => ' péng', + '憈' => ' qū', + '憇' => ' qì', + '憆' => ' chēng', + '憅' => ' tòng', + '愂' => ' bèi', + '愀' => ' qiǎo', + '怱' => ' cōng', + '恱' => ' yuè', + '恻' => ' cè', + '恺' => ' kǎi', + '恹' => ' yān', + '恸' => ' tòng', + '恷' => ' xiao', + '恶' => ' è', + '恵' => ' huì', + '恴' => ' dé', + '恳' => ' kěn', + '恲' => ' pēng', + '恰' => ' qià', + '恽' => ' yùn', + '息' => ' xī', + '恮' => ' quān', + '恭' => ' gōng', + '恬' => ' tián', + '恫' => ' dòng', + '恪' => ' kè', + '恩' => ' ēn', + '恨' => ' hèn', + '恧' => ' nǜ', + '恦' => ' shàng', + '恼' => ' nǎo', + '恾' => ' máng', + '恤' => ' xù', + '悋' => ' lìn', + '悕' => ' xī', + '悔' => ' huǐ', + '悓' => ' qiàn', + '悒' => ' yì', + '悑' => ' bù', + '悐' => ' tì', + '悏' => ' qiè', + '悎' => ' hào', + '悍' => ' hàn', + '悌' => ' tì', + '悊' => ' zhé', + '恿' => ' yǒng', + '悉' => ' xī', + '悈' => ' jiè', + '悇' => ' tú', + '悆' => ' yù', + '悅' => ' yuè', + '悄' => ' qiāo', + '悃' => ' kǔn', + '悂' => ' pī', + '悁' => ' yuān', + '悀' => ' yǒng', + '恥' => ' chǐ', + '恣' => ' zì', + '悗' => ' mán', + '怽' => ' mo', + '恇' => ' kuāng', + '恆' => ' héng', + '恅' => ' lǎo', + '恄' => ' xì', + '恃' => ' shì', + '恂' => ' xún', + '恁' => ' nèn', + '恀' => ' shì', + '怿' => ' yì', + '怾' => ' zhǐ', + '怼' => ' duì', + '恉' => ' zhǐ', + '总' => ' zǒng', + '怺' => ' yong', + '怹' => ' tān', + '怸' => ' xī', + '怷' => ' shù', + '怶' => ' bì', + '怵' => ' chù', + '怴' => ' xù', + '怳' => ' huǎng', + '怲' => ' bǐng', + '恈' => ' móu', + '恊' => ' xié', + '恢' => ' huī', + '恗' => ' hū', + '恡' => ' lìn', + '恠' => ' guài', + '恟' => ' xiōng', + '恞' => ' yí', + '恝' => ' jiá', + '恜' => ' chì', + '恛' => ' huí', + '恚' => ' huì', + '恙' => ' yàng', + '恘' => ' qiū', + '恖' => ' si', + '恋' => ' liàn', + '恕' => ' shù', + '恔' => ' jiǎo', + '恓' => ' xī', + '恒' => ' héng', + '恑' => ' guǐ', + '恐' => ' kǒng', + '恏' => ' hào', + '恎' => ' dié', + '恍' => ' huǎng', + '恌' => ' tiāo', + '悖' => ' bèi', + '悘' => ' yī', + '惿' => ' tí', + '惚' => ' hū', + '惤' => ' jiān', + '惣' => ' zǒng', + '惢' => ' suǒ', + '惡' => ' è', + '惠' => ' huì', + '惟' => ' wéi', + '惞' => ' xīn', + '惝' => ' chǎng', + '惜' => ' xī', + '惛' => ' hūn', + '惙' => ' chuò', + '惦' => ' diàn', + '惘' => ' wǎng', + '惗' => ' niè', + '惖' => ' tì', + '惕' => ' tì', + '惔' => ' tán', + '惓' => ' quán', + '惒' => ' hé', + '惑' => ' huò', + '惐' => ' yù', + '惏' => ' lán', + '惥' => ' yǒng', + '惧' => ' jù', + '惍' => ' jīn', + '惴' => ' zhuì', + '惾' => ' zōng', + '惽' => ' mǐn', + '惼' => ' biǎn', + '惻' => ' cè', + '惺' => ' xīng', + '惹' => ' rě', + '惸' => ' qióng', + '惷' => ' chǔn', + '惶' => ' huáng', + '惵' => ' dié', + '想' => ' xiǎng', + '惨' => ' cǎn', + '惲' => ' yùn', + '惱' => ' nǎo', + '惰' => ' duò', + '惯' => ' guàn', + '惮' => ' dàn', + '惭' => ' cán', + '惬' => ' qiè', + '惫' => ' bèi', + '惪' => ' dé', + '惩' => ' chéng', + '惎' => ' jì', + '惌' => ' yuān', + '悙' => ' hēng', + '悥' => ' yì', + '悯' => ' mǐn', + '悮' => ' wù', + '悭' => ' qiān', + '悬' => ' xuán', + '悫' => ' què', + '悪' => ' è', + '悩' => ' nǎo', + '您' => ' nín', + '悧' => ' lì', + '悦' => ' yuè', + '悤' => ' cōng', + '悱' => ' fěi', + '患' => ' huàn', + '悢' => ' liàng', + '悡' => ' lí', + '悠' => ' yōu', + '悟' => ' wù', + '悞' => ' wù', + '悝' => ' kuī', + '悜' => ' chěng', + '悛' => ' quān', + '悚' => ' sǒng', + '悰' => ' cóng', + '悲' => ' bēi', + '惋' => ' wǎn', + '惀' => ' lún', + '惊' => ' jīng', + '惉' => ' zhān', + '惈' => ' guǒ', + '惇' => ' dūn', + '惆' => ' chóu', + '情' => ' qíng', + '惄' => ' nì', + '惃' => ' gǔn', + '惂' => ' kǎn', + '惁' => ' xī', + '悿' => ' tiǎn', + '悳' => ' duó', + '悾' => ' kōng', + '悽' => ' qī', + '悼' => ' dào', + '悻' => ' xìng', + '悺' => ' guàn', + '悹' => ' guàn', + '悸' => ' jì', + '悷' => ' sàn', + '悶' => ' mèn', + '悵' => ' chàng', + '悴' => ' cuì', + '攐' => ' qiān', + '攒' => ' zǎn', + '庐' => ' lú', + '楦' => ' xuàn', + '楰' => ' yú', + '楯' => ' dùn', + '楮' => ' chǔ', + '業' => ' yè', + '楬' => ' jié', + '楫' => ' jí', + '楪' => ' yè', + '楩' => ' pián', + '楨' => ' zhēn', + '楧' => ' yǎng', + '楥' => ' xuàn', + '楲' => ' wēi', + '楤' => ' sǒng', + '楣' => ' méi', + '楢' => ' yóu', + '楡' => ' yú', + '楠' => ' nán', + '楟' => ' tíng', + '楞' => ' léng', + '楝' => ' liàn', + '楜' => ' hú', + '楛' => ' hù', + '楱' => ' zòu', + '楳' => ' méi', + '楙' => ' mào', + '榀' => ' pǐn', + '榊' => ' shen', + '榉' => ' jǔ', + '榈' => ' lǘ', + '榇' => ' chèn', + '榆' => ' yú', + '榅' => ' wēn', + '榄' => ' lǎn', + '榃' => ' tán', + '概' => ' gài', + '榁' => ' shi', + '楿' => ' xiang', + '楴' => ' tì', + '楾' => ' quan', + '楽' => ' lè', + '楼' => ' lóu', + '楻' => ' huáng', + '楺' => ' rǒu', + '楹' => ' yíng', + '楸' => ' qiū', + '楷' => ' kǎi', + '楶' => ' jié', + '極' => ' jí', + '楚' => ' chǔ', + '楘' => ' mù', + '榌' => ' pi', + '椲' => ' wěi', + '椼' => ' yǎn', + '椻' => ' yàn', + '椺' => ' xí', + '椹' => ' shèn', + '椸' => ' yí', + '椷' => ' jiān', + '椶' => ' zōng', + '椵' => ' jiǎ', + '椴' => ' duàn', + '椳' => ' wēi', + '椱' => ' fù', + '椾' => ' jiān', + '椰' => ' yē', + '椯' => ' duǒ', + '椮' => ' sēn', + '椭' => ' tuǒ', + '椬' => ' yi', + '椫' => ' zhǎn', + '椪' => ' pèng', + '椩' => ' geng', + '椨' => ' fu', + '椧' => ' mìng', + '椽' => ' chuán', + '椿' => ' chūn', + '楗' => ' jiàn', + '楌' => ' yán', + '楖' => ' zhì', + '楕' => ' tuǒ', + '楔' => ' xiē', + '楓' => ' fēng', + '楒' => ' sī', + '楑' => ' kuí', + '楐' => ' jiè', + '楏' => ' kuí', + '楎' => ' huī', + '楍' => ' běn', + '楋' => ' là', + '楀' => ' yǔ', + '楊' => ' yáng', + '楉' => ' ruò', + '楈' => ' xū', + '楇' => ' huò', + '楆' => ' yāo', + '楅' => ' bī', + '楄' => ' pián', + '楃' => ' wò', + '楂' => ' zhā', + '楁' => ' hé', + '榋' => ' chu', + '榍' => ' xiè', + '椥' => ' zhī', + '槎' => ' chá', + '様' => ' yàng', + '槗' => ' qiao', + '槖' => ' tuó', + '槕' => ' zhuó', + '槔' => ' gāo', + '槓' => ' gàng', + '槒' => ' xù', + '槑' => ' méi', + '槐' => ' huái', + '槏' => ' qiǎn', + '槍' => ' qiāng', + '槚' => ' jiǎ', + '槌' => ' chuí', + '構' => ' gòu', + '槊' => ' shuò', + '槉' => ' jí', + '槈' => ' nòu', + '槇' => ' diān', + '槆' => ' xún', + '槅' => ' gé', + '槄' => ' tāo', + '槃' => ' pán', + '槙' => ' diān', + '槛' => ' kǎn', + '槁' => ' gǎo', + '槨' => ' guǒ', + '槲' => ' hú', + '槱' => ' yǒu', + '槰' => ' péng', + '槯' => ' cuī', + '槮' => ' sēn', + '槭' => ' qī', + '槬' => ' huà', + '槫' => ' tuán', + '槪' => ' gài', + '槩' => ' gài', + '槧' => ' qiàn', + '槜' => ' zuì', + '槦' => ' yōng', + '槥' => ' huì', + '槤' => ' lián', + '槣' => ' jī', + '槢' => ' xí', + '槡' => ' sang', + '槠' => ' zhū', + '槟' => ' bīn', + '槞' => ' long', + '槝' => ' dao', + '槂' => ' sūn', + '槀' => ' gǎo', + '榎' => ' jiǎ', + '榚' => ' yǎo', + '榤' => ' jié', + '榣' => ' yáo', + '榢' => ' jià', + '榡' => ' sù', + '榠' => ' míng', + '榟' => ' zǐ', + '榞' => ' yuán', + '榝' => ' shā', + '榜' => ' bǎng', + '榛' => ' zhēn', + '榙' => ' tā', + '榦' => ' gàn', + '榘' => ' jǔ', + '榗' => ' jiàn', + '榖' => ' gǔ', + '榕' => ' róng', + '榔' => ' láng', + '榓' => ' mì', + '榒' => ' nuò', + '榑' => ' fú', + '榐' => ' zhǎn', + '榏' => ' yì', + '榥' => ' huàng', + '榧' => ' fěi', + '榿' => ' qī', + '榴' => ' liú', + '榾' => ' gǔ', + '榽' => ' xī', + '榼' => ' kē', + '榻' => ' tà', + '榺' => ' shèng', + '榹' => ' sī', + '榸' => ' zhāi', + '榷' => ' què', + '榶' => ' táng', + '榵' => ' róng', + '榳' => ' tíng', + '榨' => ' zhà', + '榲' => ' yún', + '榱' => ' cuī', + '榰' => ' zhī', + '榯' => ' shí', + '榮' => ' róng', + '榭' => ' xiè', + '榬' => ' yuán', + '榫' => ' sǔn', + '榪' => ' mà', + '榩' => ' qián', + '椦' => ' quan', + '椤' => ' luó', + '槴' => ' hù', + '梖' => ' bèi', + '梠' => ' lǚ', + '梟' => ' xiāo', + '梞' => ' jì', + '條' => ' tiáo', + '梜' => ' jiā', + '梛' => ' nuó', + '梚' => ' wǎn', + '梙' => ' huàn', + '梘' => ' jiǎn', + '梗' => ' gěng', + '梕' => ' rèn', + '梢' => ' shāo', + '梔' => ' zhī', + '梓' => ' zǐ', + '梒' => ' hán', + '梑' => ' dí', + '梐' => ' bì', + '梏' => ' gù', + '梎' => ' āo', + '梍' => ' zào', + '梌' => ' tú', + '梋' => ' xuān', + '梡' => ' hún', + '梣' => ' cén', + '梉' => ' zhuāng', + '械' => ' xiè', + '梺' => ' xia', + '梹' => ' bīn', + '梸' => ' lí', + '梷' => ' jìng', + '梶' => ' wěi', + '梵' => ' fàn', + '梴' => ' chān', + '梳' => ' shū', + '梲' => ' zhuó', + '梱' => ' kǔn', + '梯' => ' tī', + '梤' => ' fén', + '梮' => ' jū', + '梭' => ' suō', + '梬' => ' yǐng', + '梫' => ' qǐn', + '梪' => ' dòu', + '梩' => ' lí', + '梨' => ' lí', + '梧' => ' wú', + '梦' => ' mèng', + '梥' => ' sōng', + '梊' => ' dì', + '梈' => ' pēng', + '梼' => ' táo', + '桢' => ' zhēn', + '桬' => ' shā', + '桫' => ' suō', + '桪' => ' xún', + '桩' => ' zhuāng', + '桨' => ' jiǎng', + '桧' => ' guì', + '桦' => ' huà', + '桥' => ' qiáo', + '桤' => ' qī', + '档' => ' dàng', + '桡' => ' ráo', + '桮' => ' bēi', + '桠' => ' yā', + '桟' => ' zhàn', + '桞' => ' liu', + '桝' => ' jie', + '桜' => ' yīng', + '桚' => ' zǎn', + '桙' => ' yú', + '桘' => ' zhuì', + '桗' => ' duò', + '桖' => ' xuè', + '桭' => ' zhēn', + '桯' => ' tīng', + '梇' => ' lòng', + '桼' => ' qī', + '梆' => ' bāng', + '梅' => ' méi', + '梄' => ' yǒu', + '梃' => ' tǐng', + '梂' => ' qiú', + '梁' => ' liáng', + '梀' => ' sù', + '桿' => ' gǎn', + '桾' => ' jūn', + '桽' => ' wěn', + '桻' => ' fēng', + '桰' => ' kuò', + '桺' => ' liǔ', + '桹' => ' láng', + '桸' => ' xī', + '桷' => ' jué', + '桶' => ' tǒng', + '桵' => ' ruí', + '桴' => ' fú', + '桳' => ' bèn', + '桲' => ' po', + '桱' => ' jìng', + '梻' => ' fo', + '梽' => ' zhì', + '椣' => ' dian', + '棾' => ' qíng', + '椈' => ' jú', + '椇' => ' jǔ', + '椆' => ' chóu', + '椅' => ' yǐ', + '椄' => ' jiē', + '椃' => ' háo', + '椂' => ' lù', + '椁' => ' guǒ', + '椀' => ' wǎn', + '棿' => ' ní', + '棽' => ' shēn', + '椊' => ' zuó', + '棼' => ' fén', + '棻' => ' fēn', + '棺' => ' guān', + '棹' => ' zhào', + '棸' => ' zōu', + '棷' => ' zōu', + '棶' => ' lái', + '棵' => ' kē', + '棴' => ' fú', + '棳' => ' zhuō', + '椉' => ' chéng', + '椋' => ' liáng', + '棱' => ' léng', + '椘' => ' chǔ', + '椢' => ' guì', + '椡' => ' dao', + '椠' => ' qiàn', + '椟' => ' dú', + '椞' => ' xì', + '椝' => ' guī', + '検' => ' jiǎn', + '椛' => ' hua', + '椚' => ' men', + '椙' => ' chang', + '椗' => ' dìng', + '椌' => ' qiāng', + '椖' => ' péng', + '椕' => ' bīn', + '椔' => ' zī', + '椓' => ' zhuó', + '椒' => ' jiāo', + '椑' => ' bēi', + '椐' => ' jū', + '椏' => ' yā', + '椎' => ' chuí', + '植' => ' zhí', + '棲' => ' qī', + '棰' => ' chuí', + '梾' => ' lái', + '棊' => ' qí', + '棔' => ' hūn', + '棓' => ' bàng', + '棒' => ' bàng', + '棑' => ' pái', + '棐' => ' fěi', + '棏' => ' dé', + '棎' => ' chán', + '棍' => ' gùn', + '棌' => ' cài', + '棋' => ' qí', + '棉' => ' mián', + '棖' => ' chéng', + '棈' => ' qiàn', + '棇' => ' cōng', + '棆' => ' lún', + '棅' => ' bìng', + '棄' => ' qì', + '棃' => ' lí', + '棂' => ' líng', + '棁' => ' zhuō', + '检' => ' jiǎn', + '梿' => ' lián', + '棕' => ' zōng', + '棗' => ' zǎo', + '棯' => ' rěn', + '棤' => ' cuò', + '森' => ' sēn', + '棭' => ' yì', + '棬' => ' quān', + '棫' => ' yù', + '棪' => ' yǎn', + '棩' => ' yuān', + '棨' => ' qǐ', + '棧' => ' zhàn', + '棦' => ' chēng', + '棥' => ' fán', + '棣' => ' dì', + '棘' => ' jí', + '棢' => ' wǎng', + '棡' => ' gāng', + '棠' => ' táng', + '棟' => ' dòng', + '棞' => ' jùn', + '棝' => ' gù', + '棜' => ' yù', + '棛' => ' yù', + '棚' => ' péng', + '棙' => ' lì', + '槳' => ' jiǎng', + '槵' => ' huàn', + '桔' => ' jú', + '欇' => ' shè', + '欑' => ' cuán', + '欐' => ' lì', + '欏' => ' luó', + '欎' => ' yù', + '欍' => ' jiu', + '欌' => ' cang', + '欋' => ' qú', + '權' => ' quán', + '欉' => ' cóng', + '欈' => ' wéi', + '欆' => ' shuāng', + '欓' => ' dǎng', + '欅' => ' jǔ', + '欄' => ' lán', + '欃' => ' chán', + '欂' => ' bó', + '欁' => ' nóng', + '欀' => ' xiāng', + '櫿' => ' yíng', + '櫾' => ' yóu', + '櫽' => ' yǐn', + '櫼' => ' jiān', + '欒' => ' luán', + '欔' => ' jué', + '櫺' => ' líng', + '次' => ' cì', + '欫' => ' qì', + '欪' => ' chù', + '欩' => ' chāo', + '欨' => ' xū', + '欧' => ' ōu', + '欦' => ' qiān', + '欥' => ' yì', + '欤' => ' yú', + '欣' => ' xīn', + '欢' => ' huān', + '欠' => ' qiàn', + '欕' => ' yan', + '欟' => ' guang', + '欞' => ' líng', + '欝' => ' yù', + '欜' => ' náng', + '欛' => ' bà', + '欚' => ' lǐ', + '欙' => ' léi', + '欘' => ' zhú', + '欗' => ' lán', + '欖' => ' lǎn', + '櫻' => ' yīng', + '櫹' => ' xiāo', + '欭' => ' yì', + '櫓' => ' lǔ', + '櫝' => ' dú', + '櫜' => ' gāo', + '櫛' => ' zhì', + '櫚' => ' lú', + '櫙' => ' ōu', + '櫘' => ' huì', + '櫗' => ' miè', + '櫖' => ' lǜ', + '櫕' => ' cuán', + '櫔' => ' lì', + '櫒' => ' sà', + '櫟' => ' lì', + '櫑' => ' léi', + '櫐' => ' lěi', + '櫏' => ' qiān', + '櫎' => ' huǎng', + '櫍' => ' zhì', + '櫌' => ' yōu', + '櫋' => ' mián', + '櫊' => ' ge', + '櫉' => ' chú', + '櫈' => ' dèng', + '櫞' => ' yuán', + '櫠' => ' fèi', + '櫸' => ' jǔ', + '櫭' => ' jié', + '櫷' => ' guī', + '櫶' => ' xiǎn', + '櫵' => ' jiao', + '櫴' => ' lài', + '櫳' => ' lóng', + '櫲' => ' yù', + '櫱' => ' niè', + '櫰' => ' huái', + '櫯' => ' sū', + '櫮' => ' è', + '櫬' => ' chèn', + '櫡' => ' zhuó', + '櫫' => ' zhū', + '櫪' => ' lì', + '櫩' => ' yán', + '櫨' => ' lú', + '櫧' => ' zhū', + '櫦' => ' qing', + '櫥' => ' chú', + '櫤' => ' jiang', + '櫣' => ' lián', + '櫢' => ' sǒu', + '欬' => ' kài', + '欮' => ' jué', + '櫆' => ' kuí', + '歯' => ' chǐ', + '歹' => ' dǎi', + '歸' => ' guī', + '歷' => ' lì', + '歶' => ' yú', + '歵' => ' cuò', + '歴' => ' lì', + '歳' => ' suì', + '歲' => ' suì', + '歱' => ' zhǒng', + '歰' => ' sè', + '歮' => ' sè', + '死' => ' sǐ', + '歭' => ' chí', + '歬' => ' qián', + '歫' => ' jù', + '歪' => ' wāi', + '歩' => ' bù', + '歨' => ' bù', + '歧' => ' qí', + '武' => ' wǔ', + '步' => ' bù', + '此' => ' cǐ', + '歺' => ' è', + '歼' => ' jiān', + '止' => ' zhǐ', + '殉' => ' xùn', + '殓' => ' liàn', + '殒' => ' yǔn', + '殑' => ' qíng', + '殐' => ' sù', + '殏' => ' qiú', + '殎' => ' qià', + '殍' => ' piǎo', + '殌' => ' jué', + '残' => ' cán', + '殊' => ' shū', + '殈' => ' xù', + '歽' => ' zhé', + '殇' => ' shāng', + '殆' => ' dài', + '殅' => ' shēng', + '殄' => ' tiǎn', + '殃' => ' yāng', + '殂' => ' cú', + '殁' => ' mò', + '殀' => ' yāo', + '歿' => ' mò', + '歾' => ' mò', + '正' => ' zhèng', + '歡' => ' huān', + '欯' => ' xì', + '欻' => ' chuā', + '歅' => ' yīn', + '歄' => ' guā', + '歃' => ' shà', + '歂' => ' chuǎn', + '歁' => ' kǎn', + '歀' => ' kuǎn', + '欿' => ' kǎn', + '款' => ' kuǎn', + '欽' => ' qīn', + '欼' => ' chǐ', + '欺' => ' qī', + '歇' => ' xiē', + '欹' => ' yī', + '欸' => ' āi', + '欷' => ' xī', + '欶' => ' shuò', + '欵' => ' kuǎn', + '欴' => ' láng', + '欳' => ' kuì', + '欲' => ' yù', + '欱' => ' hē', + '欰' => ' xù', + '歆' => ' xīn', + '歈' => ' yú', + '歠' => ' chuò', + '歕' => ' pēn', + '歟' => ' yú', + '歞' => ' è', + '歝' => ' yì', + '歜' => ' chù', + '歛' => ' hān', + '歚' => ' shàn', + '歙' => ' shè', + '歘' => ' chuā', + '歗' => ' xiào', + '歖' => ' xǐ', + '歔' => ' xū', + '歉' => ' qiàn', + '歓' => ' huān', + '歒' => ' tì', + '歑' => ' hū', + '歐' => ' ōu', + '歏' => ' jìn', + '歎' => ' tàn', + '歍' => ' wū', + '歌' => ' gē', + '歋' => ' yè', + '歊' => ' xiāo', + '櫇' => ' pó', + '櫅' => ' jī', + '槶' => ' guì', + '樶' => ' zuī', + '橀' => ' xī', + '樿' => ' shàn', + '樾' => ' yuè', + '樽' => ' zūn', + '樼' => ' zhēn', + '樻' => ' kuì', + '樺' => ' huà', + '樹' => ' shù', + '樸' => ' pǔ', + '樷' => ' cóng', + '樵' => ' qiáo', + '橂' => ' diàn', + '樴' => ' zhí', + '樳' => ' xún', + '樲' => ' èr', + '樱' => ' yīng', + '樰' => ' xue', + '樯' => ' qiáng', + '樮' => ' yan', + '樭' => ' ji', + '樬' => ' cōng', + '樫' => ' jiān', + '橁' => ' chūn', + '橃' => ' fá', + '権' => ' quán', + '橐' => ' tuó', + '橚' => ' sù', + '橙' => ' chéng', + '橘' => ' jú', + '橗' => ' méng', + '橖' => ' táng', + '橕' => ' chēng', + '橔' => ' dūn', + '橓' => ' shùn', + '橒' => ' yún', + '橑' => ' lǎo', + '橏' => ' zhǎn', + '橄' => ' gǎn', + '橎' => ' fán', + '橍' => ' rùn', + '橌' => ' xiàn', + '橋' => ' qiáo', + '橊' => ' liú', + '橉' => ' lìn', + '橈' => ' ráo', + '橇' => ' qiāo', + '橆' => ' wǔ', + '橅' => ' mó', + '横' => ' héng', + '樨' => ' xī', + '橜' => ' jué', + '樂' => ' lè', + '樌' => ' guàn', + '樋' => ' tǒng', + '樊' => ' fán', + '樉' => ' shuǎng', + '樈' => ' qíng', + '樇' => ' xiū', + '樆' => ' lí', + '樅' => ' cōng', + '樄' => ' chén', + '樃' => ' lǎng', + '樁' => ' zhuāng', + '樎' => ' sù', + '樀' => ' dī', + '槿' => ' jǐn', + '槾' => ' màn', + '槽' => ' cáo', + '槼' => ' guī', + '槻' => ' guī', + '槺' => ' kāng', + '槹' => ' gāo', + '槸' => ' yì', + '槷' => ' niè', + '樍' => ' zé', + '樏' => ' lěi', + '樧' => ' shā', + '樜' => ' zhè', + '樦' => ' zhù', + '樥' => ' péng', + '樤' => ' tiáo', + '樣' => ' yàng', + '樢' => ' niǎo', + '模' => ' mó', + '樠' => ' mán', + '樟' => ' zhāng', + '樞' => ' shū', + '樝' => ' zhā', + '樛' => ' jiū', + '樐' => ' lǔ', + '樚' => ' lù', + '標' => ' biāo', + '樘' => ' táng', + '樗' => ' chū', + '樖' => ' kē', + '樕' => ' sù', + '樔' => ' cháo', + '樓' => ' lóu', + '樒' => ' mì', + '樑' => ' liáng', + '橛' => ' jué', + '橝' => ' diàn', + '櫄' => ' chūn', + '檟' => ' jiǎ', + '檩' => ' lǐn', + '檨' => ' shē', + '檧' => ' sōng', + '檦' => ' biǎo', + '檥' => ' yǐ', + '檤' => ' dào', + '檣' => ' qiáng', + '檢' => ' jiǎn', + '檡' => ' zhái', + '檠' => ' qíng', + '檞' => ' jiě', + '檫' => ' chá', + '檝' => ' jí', + '檜' => ' guì', + '檛' => ' zhuā', + '檚' => ' chǔ', + '檙' => ' chéng', + '檘' => ' píng', + '檗' => ' bò', + '檖' => ' suì', + '檕' => ' jì', + '檔' => ' dàng', + '檪' => ' li', + '檬' => ' méng', + '檒' => ' fēng', + '檹' => ' yī', + '櫃' => ' guì', + '櫂' => ' zhào', + '櫁' => ' mì', + '櫀' => ' qí', + '檿' => ' yǎn', + '檾' => ' qǐng', + '檽' => ' nòu', + '檼' => ' yìn', + '檻' => ' kǎn', + '檺' => ' gǎo', + '檸' => ' níng', + '檭' => ' yín', + '檷' => ' nǐ', + '檶' => ' qiān', + '檵' => ' jì', + '檴' => ' huò', + '檳' => ' bīn', + '檲' => ' tuán', + '檱' => ' qí', + '檰' => ' mián', + '檯' => ' tái', + '檮' => ' táo', + '檓' => ' huǐ', + '檑' => ' léi', + '橞' => ' huì', + '橪' => ' rǎn', + '橴' => ' zi', + '橳' => ' sheng', + '橲' => ' xi', + '橱' => ' chú', + '橰' => ' gāo', + '橯' => ' lào', + '橮' => ' liǔ', + '橭' => ' gū', + '橬' => ' qián', + '橫' => ' héng', + '橩' => ' qióng', + '橶' => ' jí', + '橨' => ' fén', + '橧' => ' zēng', + '橦' => ' tóng', + '橥' => ' zhū', + '橤' => ' ruǐ', + '橣' => ' nǐng', + '橢' => ' tuǒ', + '橡' => ' xiàng', + '橠' => ' nuǒ', + '機' => ' jī', + '橵' => ' san', + '橷' => ' dōu', + '檐' => ' yán', + '檅' => ' suì', + '檏' => ' pǔ', + '檎' => ' qín', + '檍' => ' yì', + '檌' => ' zuì', + '檋' => ' jú', + '檊' => ' gàn', + '檉' => ' chēng', + '檈' => ' xuán', + '檇' => ' zuì', + '檆' => ' shān', + '檄' => ' xí', + '橸' => ' jing', + '檃' => ' yǐn', + '檂' => ' nóng', + '檁' => ' lǐn', + '檀' => ' tán', + '橿' => ' jiāng', + '橾' => ' shū', + '橽' => ' tà', + '橼' => ' yuán', + '橻' => ' chu', + '橺' => ' jian', + '橹' => ' lǔ', + '桕' => ' jiù', + '桓' => ' huán', + '攓' => ' qiān', + '昤' => ' líng', + '昮' => ' zòng', + '昭' => ' zhāo', + '昬' => ' hūn', + '昫' => ' xù', + '昪' => ' biàn', + '昩' => ' mò', + '昨' => ' zuó', + '昧' => ' mèi', + '昦' => ' hào', + '春' => ' chūn', + '昣' => ' zhěn', + '昰' => ' shì', + '昢' => ' pò', + '昡' => ' xuàn', + '映' => ' yìng', + '星' => ' xīng', + '昞' => ' bǐng', + '昝' => ' zǎn', + '昜' => ' yáng', + '昛' => ' jù', + '昚' => ' shèn', + '昙' => ' tán', + '是' => ' shì', + '昱' => ' yù', + '昗' => ' zè', + '显' => ' xiǎn', + '晈' => ' jiǎo', + '晇' => ' xū', + '晆' => ' kuí', + '晅' => ' xuǎn', + '晄' => ' huǎng', + '晃' => ' huǎng', + '時' => ' shí', + '晁' => ' cháo', + '晀' => ' tiǎo', + '昿' => ' kuàng', + '昽' => ' lóng', + '昲' => ' fèi', + '昼' => ' zhòu', + '昻' => ' áng', + '昺' => ' bǐng', + '昹' => ' ǎi', + '昸' => ' dōng', + '昷' => ' wēn', + '昶' => ' chǎng', + '昵' => ' nì', + '昴' => ' mǎo', + '昳' => ' dié', + '昘' => ' fǎng', + '昖' => ' yán', + '晊' => ' zhì', + '旰' => ' gàn', + '旺' => ' wàng', + '旹' => ' shí', + '旸' => ' yáng', + '旷' => ' kuàng', + '时' => ' shí', + '旵' => ' chǎn', + '旴' => ' xū', + '旳' => ' dì', + '旲' => ' tái', + '旱' => ' hàn', + '旯' => ' lá', + '旼' => ' mín', + '旮' => ' gā', + '旭' => ' xù', + '旬' => ' xún', + '旫' => ' tiāo', + '旪' => ' xié', + '早' => ' zǎo', + '旨' => ' zhǐ', + '旧' => ' jiù', + '旦' => ' dàn', + '日' => ' rì', + '旻' => ' mín', + '旽' => ' tùn', + '昕' => ' xīn', + '昊' => ' hào', + '昔' => ' xī', + '易' => ' yì', + '昒' => ' hū', + '昑' => ' qǐn', + '昐' => ' fēn', + '昏' => ' hūn', + '明' => ' míng', + '昍' => ' xuān', + '昌' => ' chāng', + '昋' => ' guì', + '昉' => ' fǎng', + '旾' => ' chūn', + '昈' => ' hù', + '昇' => ' shēng', + '昆' => ' kūn', + '昅' => ' jié', + '昄' => ' bǎn', + '昃' => ' zè', + '昂' => ' áng', + '昁' => ' bèi', + '昀' => ' yún', + '旿' => ' wǔ', + '晉' => ' jìn', + '晋' => ' jìn', + '旣' => ' jì', + '暌' => ' kuí', + '暖' => ' nuǎn', + '暕' => ' jiǎn', + '暔' => ' nán', + '暓' => ' mào', + '暒' => ' qíng', + '暑' => ' shǔ', + '暐' => ' wěi', + '暏' => ' shǔ', + '暎' => ' yìng', + '暍' => ' yē', + '暋' => ' mǐn', + '暘' => ' yáng', + '暊' => ' xǔ', + '暉' => ' huī', + '暈' => ' yūn', + '暇' => ' xiá', + '暆' => ' yí', + '暅' => ' gèng', + '暄' => ' xuān', + '暃' => ' fei', + '暂' => ' zàn', + '暁' => ' xiǎo', + '暗' => ' àn', + '暙' => ' chūn', + '晿' => ' chāng', + '暦' => ' lì', + '暰' => ' cōng', + '暯' => ' mò', + '暮' => ' mù', + '暭' => ' hào', + '暬' => ' xiè', + '暫' => ' zàn', + '暪' => ' mèn', + '暩' => ' jì', + '暨' => ' jì', + '暧' => ' ài', + '暥' => ' yàn', + '暚' => ' yáo', + '暤' => ' hào', + '暣' => ' qì', + '暢' => ' chàng', + '暡' => ' wěng', + '暠' => ' gǎo', + '暟' => ' kǎi', + '暞' => ' jiǎo', + '暝' => ' míng', + '暜' => ' jìn', + '暛' => ' suǒ', + '暀' => ' wǎng', + '晾' => ' liàng', + '晌' => ' shǎng', + '晘' => ' hàn', + '晢' => ' zhé', + '晡' => ' bū', + '晠' => ' shèng', + '晟' => ' chéng', + '晞' => ' xī', + '晝' => ' zhòu', + '晜' => ' kūn', + '晛' => ' xiàn', + '晚' => ' wǎn', + '晙' => ' jùn', + '晗' => ' hán', + '晤' => ' wù', + '晖' => ' huī', + '晕' => ' yūn', + '晔' => ' yè', + '晓' => ' xiǎo', + '晒' => ' shài', + '晑' => ' xiǎng', + '晐' => ' gāi', + '晏' => ' yàn', + '晎' => ' hǒng', + '晍' => ' tóng', + '晣' => ' zhé', + '晥' => ' hàn', + '晽' => ' lín', + '晲' => ' nǐ', + '晼' => ' wǎn', + '晻' => ' àn', + '智' => ' zhì', + '晹' => ' yì', + '晸' => ' zhěng', + '晷' => ' guǐ', + '晶' => ' jīng', + '晵' => ' qǐ', + '晴' => ' qíng', + '晳' => ' xī', + '晱' => ' shǎn', + '晦' => ' huì', + '晰' => ' xī', + '景' => ' jǐng', + '普' => ' pǔ', + '晭' => ' zhǒu', + '晬' => ' zuì', + '晫' => ' zhuó', + '晪' => ' tiǎn', + '晩' => ' wǎn', + '晨' => ' chén', + '晧' => ' hào', + '旤' => ' huò', + '既' => ' jì', + '暲' => ' zhāng', + '敓' => ' duó', + '敞' => ' chǎng', + '敝' => ' bì', + '敜' => ' niè', + '敚' => ' duó', + '教' => ' jiào', + '敘' => ' xù', + '敗' => ' bài', + '敖' => ' áo', + '敕' => ' chì', + '敔' => ' yǔ', + '敒' => ' shēn', + '敠' => ' duō', + '救' => ' jiù', + '敐' => ' chén', + '敏' => ' mǐn', + '敎' => ' jiào', + '敍' => ' xù', + '敌' => ' dí', + '敋' => ' gé', + '敊' => ' chù', + '敉' => ' mǐ', + '效' => ' xiào', + '敟' => ' diǎn', + '敡' => ' yì', + '敆' => ' hé', + '敮' => ' xiá', + '數' => ' shù', + '敷' => ' fū', + '敶' => ' zhèn', + '敵' => ' dí', + '整' => ' zhěng', + '敳' => ' ái', + '敲' => ' qiāo', + '敱' => ' ái', + '数' => ' shù', + '敯' => ' mín', + '敭' => ' yáng', + '敢' => ' gǎn', + '敬' => ' jìng', + '敫' => ' jiǎo', + '敪' => ' duó', + '敩' => ' xiào', + '敨' => ' tǒu', + '敧' => ' jī', + '敦' => ' dūn', + '敥' => ' yàn', + '敤' => ' kě', + '散' => ' sàn', + '敇' => ' cè', + '故' => ' gù', + '敺' => ' qū', + '攟' => ' jùn', + '攩' => ' dǎng', + '攨' => ' wā', + '攧' => ' diān', + '攦' => ' lì', + '攥' => ' zuàn', + '攤' => ' tān', + '攣' => ' luán', + '攢' => ' zǎn', + '攡' => ' lí', + '攠' => ' mí', + '攞' => ' luó', + '攫' => ' jué', + '攝' => ' shè', + '攜' => ' xié', + '攛' => ' cuān', + '攚' => ' ying', + '攙' => ' chān', + '攘' => ' rǎng', + '攗' => ' méi', + '攖' => ' yīng', + '攕' => ' xiān', + '攔' => ' lán', + '攪' => ' jiǎo', + '攬' => ' lǎn', + '敄' => ' wù', + '改' => ' gǎi', + '敃' => ' mǐn', + '敂' => ' kòu', + '敁' => ' diān', + '敀' => ' pò', + '政' => ' zhèng', + '放' => ' fàng', + '攽' => ' bān', + '攼' => ' gān', + '攻' => ' gōng', + '攺' => ' yǐ', + '攸' => ' yōu', + '攭' => ' lì', + '攷' => ' kǎo', + '收' => ' shōu', + '攵' => ' pū', + '攴' => ' pū', + '攳' => ' xún', + '攲' => ' qī', + '攱' => ' guǐ', + '攰' => ' guì', + '支' => ' zhī', + '攮' => ' nǎng', + '敹' => ' liáo', + '敻' => ' xiòng', + '旡' => ' jì', + '於' => ' yú', + '旆' => ' pèi', + '旅' => ' lǚ', + '旄' => ' máo', + '旃' => ' zhān', + '旂' => ' qí', + '旁' => ' páng', + '旀' => ' mèi', + '斿' => ' yóu', + '斾' => ' pèi', + '施' => ' shī', + '斻' => ' háng', + '旈' => ' liú', + '斺' => ' chǎn', + '方' => ' fāng', + '斸' => ' zhǔ', + '斷' => ' duàn', + '斶' => ' chù', + '斵' => ' zhuó', + '斴' => ' lín', + '斳' => ' qín', + '斲' => ' zhuó', + '斱' => ' zhuó', + '旇' => ' pī', + '旉' => ' fū', + '斯' => ' sī', + '旖' => ' yǐ', + '无' => ' wú', + '旟' => ' yú', + '旞' => ' suì', + '旝' => ' kuài', + '旜' => ' zhān', + '旛' => ' fān', + '旚' => ' piāo', + '旙' => ' fān', + '旘' => ' zhì', + '旗' => ' qí', + '旕' => ' yú', + '旊' => ' fǎng', + '旔' => ' jiàn', + '旓' => ' shāo', + '旒' => ' liú', + '旑' => ' yǐ', + '旐' => ' zhào', + '族' => ' zú', + '旎' => ' nǐ', + '旍' => ' jīng', + '旌' => ' jīng', + '旋' => ' xuán', + '新' => ' xīn', + '斮' => ' cuò', + '敼' => ' yǐ', + '斈' => ' xué', + '斒' => ' bān', + '斑' => ' bān', + '斐' => ' fěi', + '斏' => ' láng', + '斎' => ' zhāi', + '斍' => ' jué', + '斌' => ' bīn', + '斋' => ' zhāi', + '斊' => ' qí', + '斉' => ' qí', + '文' => ' wén', + '斔' => ' yǔ', + '斆' => ' xiào', + '斅' => ' xiào', + '斄' => ' lí', + '斃' => ' bì', + '斂' => ' liǎn', + '斁' => ' yì', + '斀' => ' zhuó', + '敿' => ' jiǎo', + '敾' => ' shan', + '敽' => ' jiǎo', + '斓' => ' lán', + '斕' => ' lán', + '断' => ' duàn', + '斢' => ' tiǎo', + '斬' => ' zhǎn', + '斫' => ' zhuó', + '斪' => ' qú', + '斩' => ' zhǎn', + '斨' => ' qiāng', + '斧' => ' fǔ', + '斦' => ' yín', + '斥' => ' chì', + '斤' => ' jīn', + '斣' => ' dòu', + '斡' => ' wò', + '斖' => ' wěi', + '斠' => ' jiào', + '斟' => ' zhēn', + '斞' => ' yǔ', + '斝' => ' jiǎ', + '斜' => ' xié', + '斛' => ' hú', + '斚' => ' jiǎ', + '料' => ' liào', + '斘' => ' shēng', + '斗' => ' dòu', + '暱' => ' nì', + '暳' => ' huì', + '桒' => ' sāng', + '柅' => ' nǐ', + '柏' => ' bǎi', + '柎' => ' fū', + '柍' => ' yǎng', + '柌' => ' cí', + '柋' => ' dài', + '柊' => ' zhōng', + '柉' => ' fán', + '柈' => ' bàn', + '柇' => ' hé', + '柆' => ' lā', + '柄' => ' bǐng', + '柑' => ' gān', + '柃' => ' líng', + '柂' => ' yí', + '柁' => ' duò', + '柀' => ' bǐ', + '枿' => ' niè', + '枾' => ' shì', + '枽' => ' yè', + '枼' => ' yè', + '枻' => ' yì', + '枺' => ' mò', + '某' => ' mǒu', + '柒' => ' qī', + '枸' => ' gǒu', + '柟' => ' nán', + '柩' => ' jiù', + '柨' => ' bù', + '柧' => ' gū', + '柦' => ' dàn', + '查' => ' chá', + '柤' => ' zhā', + '柣' => ' zhì', + '柢' => ' dǐ', + '柡' => ' yǒng', + '柠' => ' níng', + '柞' => ' zhà', + '染' => ' rǎn', + '柝' => ' tuò', + '柜' => ' guì', + '柛' => ' shēn', + '柚' => ' yòu', + '柙' => ' xiá', + '柘' => ' zhè', + '柗' => ' sōng', + '柖' => ' sháo', + '柕' => ' mào', + '柔' => ' róu', + '枹' => ' bāo', + '枷' => ' jiā', + '柫' => ' fú', + '枑' => ' hù', + '枛' => ' zhào', + '枚' => ' méi', + '枙' => ' ě', + '枘' => ' ruì', + '林' => ' lín', + '枖' => ' yāo', + '枕' => ' zhěn', + '枔' => ' xín', + '枓' => ' dǒu', + '枒' => ' yā', + '析' => ' xī', + '枝' => ' zhī', + '枏' => ' nán', + '枎' => ' fú', + '枍' => ' yì', + '枌' => ' fén', + '枋' => ' fāng', + '枊' => ' àng', + '枉' => ' wǎng', + '枈' => ' bì', + '枇' => ' pí', + '枆' => ' máo', + '果' => ' guǒ', + '枞' => ' cōng', + '架' => ' jià', + '枫' => ' fēng', + '枵' => ' xiāo', + '枴' => ' guǎi', + '枳' => ' zhǐ', + '枲' => ' xǐ', + '枱' => ' tái', + '枰' => ' píng', + '枯' => ' kū', + '枮' => ' xiān', + '枭' => ' xiāo', + '枬' => ' nán', + '枪' => ' qiāng', + '枟' => ' yùn', + '枩' => ' song', + '枨' => ' chéng', + '枧' => ' jiǎn', + '枦' => ' lu', + '枥' => ' lì', + '枤' => ' duo', + '枣' => ' zǎo', + '枢' => ' shū', + '枡' => ' dǒu', + '枠' => ' zui', + '柪' => ' āo', + '柬' => ' jiǎn', + '构' => ' gòu', + '栭' => ' ér', + '样' => ' yàng', + '栶' => ' yīn', + '栵' => ' liè', + '栴' => ' zhān', + '栳' => ' lǎo', + '栲' => ' kǎo', + '栱' => ' gǒng', + '栰' => ' fá', + '栯' => ' yǒu', + '栮' => ' ěr', + '栬' => ' zuì', + '根' => ' gēn', + '栫' => ' jiàn', + '株' => ' zhū', + '栩' => ' xǔ', + '栨' => ' cì', + '栧' => ' yì', + '栦' => ' chóu', + '栥' => ' zī', + '栤' => ' bìng', + '栣' => ' rěn', + '栢' => ' bǎi', + '核' => ' hé', + '栺' => ' yì', + '栠' => ' rěn', + '桇' => ' rú', + '桑' => ' sāng', + '桐' => ' tóng', + '桏' => ' qióng', + '桎' => ' zhì', + '桍' => ' kū', + '桌' => ' zhuō', + '桋' => ' yí', + '桊' => ' juàn', + '桉' => ' ān', + '案' => ' àn', + '框' => ' kuāng', + '栻' => ' shì', + '桅' => ' wéi', + '桄' => ' guāng', + '桃' => ' táo', + '桂' => ' guì', + '桁' => ' héng', + '桀' => ' jié', + '栿' => ' fú', + '栾' => ' luán', + '栽' => ' zāi', + '格' => ' gé', + '校' => ' xiào', + '栟' => ' bēn', + '柭' => ' bā', + '柹' => ' shì', + '栃' => ' li', + '栂' => ' méi', + '栁' => ' liǔ', + '栀' => ' zhī', + '柿' => ' shì', + '柾' => ' jiù', + '柽' => ' chēng', + '柼' => ' yǎo', + '査' => ' zhā', + '柺' => ' guǎi', + '柸' => ' pēi', + '栅' => ' zhà', + '柷' => ' chù', + '柶' => ' sì', + '柵' => ' shān', + '柴' => ' chái', + '柳' => ' liǔ', + '柲' => ' bì', + '柱' => ' zhù', + '柰' => ' nài', + '柯' => ' kē', + '柮' => ' duò', + '栄' => ' róng', + '栆' => ' zao', + '栞' => ' kān', + '栓' => ' shuān', + '栝' => ' guā', + '栜' => ' sè', + '栛' => ' lì', + '栚' => ' zhèn', + '栙' => ' xiáng', + '栘' => ' yí', + '栗' => ' lì', + '栖' => ' qī', + '栕' => ' zhēn', + '栔' => ' qì', + '栒' => ' xún', + '标' => ' biāo', + '树' => ' shù', + '栐' => ' yǒng', + '栏' => ' lán', + '栎' => ' lì', + '栍' => ' shēng', + '栌' => ' lú', + '栋' => ' dòng', + '栊' => ' lóng', + '栉' => ' zhì', + '栈' => ' zhàn', + '枅' => ' jī', + '枃' => ' jìn', + '暴' => ' bào', + '更' => ' gèng', + '曾' => ' céng', + '曽' => ' cēng', + '曼' => ' màn', + '曻' => ' sheng', + '曺' => ' cáo', + '曹' => ' cáo', + '書' => ' shū', + '曷' => ' hé', + '曶' => ' hū', + '曵' => ' yè', + '曳' => ' yè', + '最' => ' zuì', + '曲' => ' qū', + '曱' => ' yuē', + '曰' => ' yuē', + '曯' => ' zhú', + '曮' => ' yǎn', + '曭' => ' tǎng', + '曬' => ' shài', + '曫' => ' luán', + '曪' => ' luǒ', + '曩' => ' nǎng', + '替' => ' tì', + '朁' => ' cǎn', + '曧' => ' róng', + '朎' => ' líng', + '朘' => ' zuī', + '朗' => ' lǎng', + '朖' => ' lǎng', + '朕' => ' zhèn', + '朔' => ' shuò', + '朓' => ' tiǎo', + '朒' => ' nǜ', + '朑' => ' tì', + '朐' => ' qú', + '朏' => ' fěi', + '服' => ' fú', + '朂' => ' xù', + '朌' => ' fén', + '朋' => ' péng', + '朊' => ' ruǎn', + '有' => ' yǒu', + '月' => ' yuè', + '朇' => ' pí', + '朆' => ' fēn', + '朅' => ' qiè', + '朄' => ' yǐn', + '會' => ' huì', + '曨' => ' lóng', + '曦' => ' xī', + '朚' => ' huāng', + '曀' => ' yì', + '曊' => ' fèi', + '曉' => ' xiǎo', + '曈' => ' tóng', + '曇' => ' tán', + '曆' => ' lì', + '曅' => ' yè', + '曄' => ' yè', + '曃' => ' dài', + '曂' => ' huàng', + '曁' => ' jì', + '暿' => ' xǐ', + '曌' => ' zhào', + '暾' => ' tūn', + '暽' => ' lín', + '暼' => ' piē', + '暻' => ' jǐng', + '暺' => ' dàn', + '暹' => ' xiān', + '暸' => ' liáo', + '暷' => ' chuán', + '暶' => ' xuán', + '暵' => ' hàn', + '曋' => ' shěn', + '曍' => ' hào', + '曥' => ' lú', + '曚' => ' méng', + '曤' => ' huò', + '曣' => ' yàn', + '曢' => ' liǎo', + '曡' => ' dié', + '曠' => ' kuàng', + '曟' => ' chén', + '曞' => ' lì', + '曝' => ' pù', + '曜' => ' yào', + '曛' => ' xūn', + '曙' => ' shǔ', + '曎' => ' yì', + '曘' => ' rú', + '曗' => ' yè', + '曖' => ' ài', + '曕' => ' yàn', + '曔' => ' jìng', + '曓' => ' bào', + '曒' => ' jiǎo', + '曑' => ' shēn', + '曐' => ' xīng', + '曏' => ' xiǎng', + '朙' => ' míng', + '望' => ' wàng', + '枂' => ' wò', + '杝' => ' lí', + '杧' => ' máng', + '杦' => ' jiu', + '来' => ' lái', + '杤' => ' wan', + '杣' => ' shan', + '杢' => ' jiang', + '条' => ' tiáo', + '杠' => ' gāng', + '束' => ' shù', + '杞' => ' qǐ', + '杜' => ' dù', + '杩' => ' mà', + '杛' => ' gōng', + '杚' => ' gài', + '杙' => ' yì', + '杘' => ' chì', + '杗' => ' máng', + '杖' => ' zhàng', + '杕' => ' dì', + '杔' => ' tuō', + '杓' => ' biāo', + '杒' => ' rèn', + '杨' => ' yáng', + '杪' => ' miǎo', + '材' => ' cái', + '杷' => ' pá', + '极' => ' jí', + '枀' => ' sōng', + '板' => ' bǎn', + '松' => ' sōng', + '杽' => ' chǒu', + '杼' => ' zhù', + '杻' => ' chǒu', + '杺' => ' xīn', + '杹' => ' huà', + '杸' => ' shū', + '杶' => ' chūn', + '杫' => ' sì', + '杵' => ' chǔ', + '杴' => ' xiān', + '杳' => ' yǎo', + '杲' => ' gǎo', + '東' => ' dōng', + '杰' => ' jié', + '杯' => ' bēi', + '杮' => ' fèi', + '杭' => ' háng', + '杬' => ' yuán', + '村' => ' cūn', + '杏' => ' xìng', + '朜' => ' tūn', + '木' => ' mù', + '朲' => ' rén', + '朱' => ' zhū', + '朰' => ' mù', + '术' => ' shù', + '朮' => ' shù', + '札' => ' zhá', + '本' => ' běn', + '末' => ' mò', + '未' => ' wèi', + '朩' => ' děng', + '朧' => ' lóng', + '朴' => ' pǔ', + '朦' => ' méng', + '朥' => ' lao', + '朤' => ' lǎng', + '朣' => ' tóng', + '朢' => ' wàng', + '朡' => ' zōng', + '朠' => ' yīng', + '期' => ' qī', + '朞' => ' jī', + '朝' => ' cháo', + '朳' => ' bā', + '朵' => ' duǒ', + '李' => ' lǐ', + '权' => ' quán', + '杍' => ' zǐ', + '杌' => ' wù', + '杋' => ' fán', + '杊' => ' xún', + '杉' => ' shān', + '杈' => ' chā', + '杇' => ' wū', + '杆' => ' gān', + '杅' => ' yú', + '杄' => ' qiān', + '杂' => ' zá', + '朶' => ' duǒ', + '杁' => ' ru', + '杀' => ' shā', + '朿' => ' cì', + '朾' => ' chéng', + '朽' => ' xiǔ', + '朼' => ' bǐ', + '朻' => ' jiū', + '机' => ' jī', + '朹' => ' guǐ', + '朸' => ' lì', + '朷' => ' dāo', + '庑' => ' wǔ', + '序' => ' xù', + '冋' => ' jiōng', + '嗜' => ' shì', + '嗦' => ' suo', + '嗥' => ' háo', + '嗤' => ' chī', + '嗣' => ' sì', + '嗢' => ' wà', + '嗡' => ' wēng', + '嗠' => ' luò', + '嗟' => ' jiē', + '嗞' => ' zī', + '嗝' => ' gé', + '嗛' => ' qiǎn', + '嗩' => ' suǒ', + '嗚' => ' wū', + '嗙' => ' pǎng', + '嗘' => ' jī', + '嗗' => ' wā', + '嗖' => ' sōu', + '嗕' => ' rù', + '嗔' => ' chēn', + '嗓' => ' sǎng', + '嗒' => ' dā', + '嗑' => ' kē', + '嗨' => ' hāi', + '嗪' => ' qín', + '嗏' => ' chā', + '嗷' => ' áo', + '嘁' => ' qī', + '嘀' => ' dí', + '嗿' => ' tǎn', + '嗾' => ' sǒu', + '嗽' => ' sòu', + '嗼' => ' mò', + '嗻' => ' zhē', + '嗺' => ' zuī', + '嗹' => ' lián', + '嗸' => ' áo', + '嗶' => ' bì', + '嗫' => ' niè', + '嗵' => ' tōng', + '嗴' => ' qiang', + '嗳' => ' āi', + '嗲' => ' diǎ', + '嗱' => ' ná', + '嗰' => ' gè', + '嗯' => ' ń', + '嗮' => ' sài', + '嗭' => ' zhí', + '嗬' => ' hē', + '嗐' => ' hài', + '嗎' => ' ma', + '嘃' => ' chōng', + '喨' => ' liàng', + '喲' => ' yō', + '喱' => ' lí', + '喰' => ' cān', + '喯' => ' pèn', + '單' => ' dān', + '喭' => ' yàn', + '喬' => ' qiáo', + '喫' => ' chī', + '喪' => ' sàng', + '喩' => ' yù', + '喧' => ' xuān', + '喴' => ' wēi', + '喦' => ' niè', + '喥' => ' duó', + '喤' => ' huáng', + '喣' => ' xù', + '喢' => ' shà', + '喡' => ' wéi', + '喠' => ' zhǒng', + '喟' => ' kuì', + '喞' => ' jī', + '喝' => ' hē', + '喳' => ' zhā', + '喵' => ' miāo', + '嗍' => ' suō', + '嗂' => ' yáo', + '嗌' => ' ài', + '嗋' => ' xié', + '嗊' => ' hǒng', + '嗉' => ' sù', + '嗈' => ' yōng', + '嗇' => ' sè', + '嗆' => ' qiāng', + '嗅' => ' xiù', + '嗄' => ' á', + '嗃' => ' hè', + '嗁' => ' tí', + '営' => ' yíng', + '嗀' => ' hù', + '喿' => ' zào', + '喾' => ' kù', + '喽' => ' lóu', + '喼' => ' jié', + '喻' => ' yù', + '喺' => ' xì', + '喹' => ' kuí', + '喸' => ' bǔ', + '喷' => ' pēn', + '嘂' => ' jiào', + '嘄' => ' jiāo', + '喛' => ' huàn', + '噅' => ' huī', + '噏' => ' xī', + '噎' => ' yē', + '噍' => ' jiào', + '噌' => ' cēng', + '噋' => ' tūn', + '噊' => ' yù', + '噉' => ' dàn', + '噈' => ' cù', + '噇' => ' chuáng', + '噆' => ' zǎn', + '噄' => ' chī', + '噑' => ' háo', + '噃' => ' fān', + '噂' => ' zǔn', + '噁' => ' ě', + '噀' => ' xùn', + '嘿' => ' hēi', + '嘾' => ' dàn', + '嘽' => ' chǎn', + '嘼' => ' chù', + '嘻' => ' xī', + '嘺' => ' qiáo', + '噐' => ' qì', + '噒' => ' lián', + '嘸' => ' fǔ', + '噟' => ' yìng', + '噩' => ' è', + '器' => ' qì', + '噧' => ' xiè', + '噦' => ' huì', + '噥' => ' nóng', + '噤' => ' jìn', + '噣' => ' zhòu', + '噢' => ' ō', + '噡' => ' zhān', + '噠' => ' dā', + '噞' => ' yǎn', + '噓' => ' xū', + '噝' => ' sī', + '噜' => ' lū', + '噛' => ' niè', + '噚' => ' xún', + '噙' => ' qín', + '噘' => ' juē', + '噗' => ' pū', + '噖' => ' yín', + '噕' => ' huī', + '噔' => ' dēng', + '嘹' => ' liáo', + '嘷' => ' háo', + '嘅' => ' kǎi', + '嘑' => ' hū', + '嘛' => ' ma', + '嘚' => ' dē', + '嘙' => ' pó', + '嘘' => ' xū', + '嘗' => ' cháng', + '嘖' => ' zé', + '嘕' => ' xiān', + '嘔' => ' ǒu', + '嘓' => ' guō', + '嘒' => ' huì', + '嘐' => ' xiāo', + '嘝' => ' hú', + '嘏' => ' gǔ', + '嘎' => ' gā', + '嘍' => ' lóu', + '嘌' => ' piào', + '嘋' => ' xiāo', + '嘊' => ' ái', + '嘉' => ' jiā', + '嘈' => ' cáo', + '嘇' => ' shān', + '嘆' => ' tàn', + '嘜' => ' mà', + '嘞' => ' lei', + '嘶' => ' sī', + '嘫' => ' rán', + '嘵' => ' xiāo', + '嘴' => ' zuǐ', + '嘳' => ' kuì', + '嘲' => ' cháo', + '嘱' => ' zhǔ', + '嘰' => ' jī', + '嘯' => ' xiào', + '嘮' => ' láo', + '嘭' => ' pēng', + '嘬' => ' chuài', + '嘪' => ' mǎi', + '嘟' => ' dū', + '嘩' => ' huā', + '嘨' => ' xiào', + '嘧' => ' mì', + '嘦' => ' jiào', + '嘥' => ' sai', + '嘤' => ' yīng', + '嘣' => ' bēng', + '嘢' => ' yě', + '嘡' => ' tāng', + '嘠' => ' gā', + '喜' => ' xǐ', + '喚' => ' huàn', + '噫' => ' yī', + '唌' => ' xián', + '唖' => ' yǎ', + '唕' => ' zào', + '唔' => ' wú', + '唓' => ' chē', + '唒' => ' qiú', + '唑' => ' zuò', + '唐' => ' táng', + '唏' => ' xī', + '唎' => ' lì', + '唍' => ' wǎn', + '唋' => ' tū', + '唘' => ' qǐ', + '唊' => ' jiá', + '唉' => ' āi', + '唈' => ' yì', + '唇' => ' chún', + '唆' => ' suō', + '唅' => ' hán', + '唄' => ' bei', + '唃' => ' gǔ', + '唂' => ' gū', + '唁' => ' yàn', + '唗' => ' dōu', + '唙' => ' dí', + '哿' => ' gě', + '唦' => ' shā', + '唰' => ' shuā', + '唯' => ' wéi', + '售' => ' shòu', + '唭' => ' qì', + '唬' => ' hǔ', + '唫' => ' jìn', + '唪' => ' fěng', + '唩' => ' wō', + '唨' => ' zuǒ', + '唧' => ' jī', + '唥' => ' lang', + '唚' => ' qìn', + '唤' => ' huàn', + '唣' => ' zào', + '唢' => ' suǒ', + '唡' => ' liǎng', + '唠' => ' láo', + '唟' => ' qù', + '唞' => ' dǒu', + '唝' => ' gòng', + '唜' => ' mò', + '唛' => ' mà', + '唀' => ' yòu', + '哾' => ' shuō', + '唲' => ' ér', + '哘' => ' xing', + '哢' => ' lòng', + '員' => ' yuán', + '哠' => ' hào', + '哟' => ' yō', + '哞' => ' mōu', + '哝' => ' nóng', + '哜' => ' jì', + '哛' => ' fēn', + '哚' => ' duǒ', + '哙' => ' kuài', + '哗' => ' huā', + '哤' => ' máng', + '哖' => ' nián', + '哕' => ' huì', + '哔' => ' bì', + '哓' => ' xiāo', + '哒' => ' dā', + '哑' => ' yǎ', + '哐' => ' kuāng', + '哏' => ' gén', + '哎' => ' āi', + '响' => ' xiǎng', + '哣' => ' pǒu', + '哥' => ' gē', + '哽' => ' gěng', + '哲' => ' zhé', + '哼' => ' hēng', + '哻' => ' hàn', + '哺' => ' bǔ', + '哹' => ' fú', + '哸' => ' suī', + '哷' => ' liè', + '哶' => ' miē', + '哵' => ' bā', + '哴' => ' liàng', + '哳' => ' zhā', + '哱' => ' bō', + '哦' => ' ó', + '哰' => ' láo', + '哯' => ' xiàn', + '哮' => ' xiāo', + '哭' => ' kū', + '哬' => ' hé', + '哫' => ' zú', + '哪' => ' nǎ', + '哩' => ' lī', + '哨' => ' shào', + '哧' => ' chī', + '唱' => ' chàng', + '唳' => ' lì', + '喙' => ' huì', + '啴' => ' chǎn', + '啾' => ' jiū', + '啽' => ' án', + '啼' => ' tí', + '啻' => ' chì', + '啺' => ' táng', + '啹' => ' jú', + '啸' => ' xiào', + '啷' => ' lāng', + '啶' => ' dìng', + '啵' => ' bo', + '啳' => ' quán', + '喀' => ' kā', + '啲' => ' dì', + '啱' => ' yān', + '啰' => ' luō', + '啯' => ' guō', + '啮' => ' niè', + '啭' => ' zhuàn', + '啬' => ' sè', + '啫' => ' zhě', + '啪' => ' pā', + '啩' => ' guà', + '啿' => ' dàn', + '喁' => ' yóng', + '啧' => ' zé', + '喎' => ' wāi', + '喘' => ' chuǎn', + '喗' => ' yǔn', + '喖' => ' hú', + '喕' => ' miǎn', + '喔' => ' ō', + '喓' => ' yāo', + '喒' => ' zá', + '喑' => ' yīn', + '喐' => ' yù', + '喏' => ' nuò', + '喍' => ' chái', + '喂' => ' wèi', + '喌' => ' zhōu', + '喋' => ' dié', + '喊' => ' hǎn', + '喉' => ' hóu', + '喈' => ' jiē', + '喇' => ' lǎ', + '喆' => ' zhé', + '喅' => ' yù', + '善' => ' shàn', + '喃' => ' nán', + '啨' => ' yīng', + '啦' => ' la', + '唴' => ' qiàng', + '啀' => ' ái', + '啊' => ' a', + '啉' => ' lín', + '啈' => ' hēng', + '啇' => ' dì', + '商' => ' shāng', + '啅' => ' zhuó', + '啄' => ' zhuó', + '啃' => ' kěn', + '啂' => ' nǒu', + '啁' => ' zhāo', + '唿' => ' hū', + '啌' => ' xiāng', + '唾' => ' tuò', + '唽' => ' xī', + '唼' => ' shà', + '唻' => ' lài', + '唺' => ' tiǎn', + '唹' => ' yū', + '唸' => ' niàn', + '唷' => ' yō', + '唶' => ' zé', + '唵' => ' ǎn', + '啋' => ' cǎi', + '啍' => ' tūn', + '啥' => ' shà', + '啚' => ' bǐ', + '啤' => ' pí', + '啣' => ' xián', + '啢' => ' liǎng', + '啡' => ' fēi', + '啠' => ' zhé', + '啟' => ' qǐ', + '啞' => ' yǎ', + '啝' => ' hé', + '啜' => ' chuài', + '啛' => ' cuì', + '啙' => ' zǐ', + '啎' => ' wǔ', + '啘' => ' yè', + '啗' => ' dàn', + '啖' => ' dàn', + '啕' => ' táo', + '啔' => ' qǐ', + '啓' => ' qǐ', + '啒' => ' gǔ', + '啑' => ' shà', + '啐' => ' cuì', + '問' => ' wèn', + '噪' => ' zào', + '噬' => ' shì', + '哋' => ' diè', + '坾' => ' zhù', + '垈' => ' dài', + '垇' => ' ào', + '垆' => ' lú', + '垅' => ' lǒng', + '垄' => ' lǒng', + '垃' => ' lā', + '垂' => ' chuí', + '垁' => ' zhì', + '垀' => ' hū', + '坿' => ' fù', + '坽' => ' líng', + '垊' => ' min', + '坼' => ' chè', + '坻' => ' chí', + '坺' => ' bá', + '坹' => ' xuè', + '坸' => ' gòu', + '坷' => ' kě', + '坶' => ' mǔ', + '坵' => ' qiū', + '坴' => ' lù', + '坳' => ' ào', + '垉' => ' páo', + '型' => ' xíng', + '坱' => ' yǎng', + '垘' => ' fú', + '垢' => ' gòu', + '垡' => ' fá', + '垠' => ' yín', + '垟' => ' yáng', + '垞' => ' chá', + '垝' => ' guǐ', + '垜' => ' duǒ', + '垛' => ' duǒ', + '垚' => ' yáo', + '垙' => ' guāng', + '垗' => ' zhào', + '垌' => ' dòng', + '垖' => ' duī', + '垕' => ' hòu', + '垔' => ' yīn', + '垓' => ' gāi', + '垒' => ' lěi', + '垑' => ' chǐ', + '垐' => ' cí', + '垏' => ' lǜ', + '垎' => ' hè', + '垍' => ' jì', + '坲' => ' fó', + '坰' => ' jiōng', + '垤' => ' dié', + '坊' => ' fāng', + '坔' => ' dì', + '坓' => ' jǐng', + '坒' => ' bì', + '坑' => ' kēng', + '坐' => ' zuò', + '坏' => ' huài', + '坎' => ' kǎn', + '坍' => ' tān', + '坌' => ' bèn', + '坋' => ' bèn', + '坉' => ' tún', + '坖' => ' jì', + '坈' => ' rǒng', + '均' => ' jūn', + '坆' => ' méi', + '坅' => ' qǐn', + '坄' => ' yì', + '坃' => ' xūn', + '坂' => ' bǎn', + '坁' => ' zhǐ', + '址' => ' zhǐ', + '圿' => ' jiá', + '坕' => ' jīng', + '块' => ' kuài', + '坯' => ' pī', + '坤' => ' kūn', + '坮' => ' tái', + '坭' => ' ní', + '坬' => ' guà', + '坫' => ' diàn', + '坪' => ' píng', + '坩' => ' gān', + '坨' => ' tuó', + '坧' => ' zhī', + '坦' => ' tǎn', + '坥' => ' qū', + '坣' => ' tāng', + '坘' => ' dǐ', + '坢' => ' bàn', + '坡' => ' pō', + '坠' => ' zhuì', + '坟' => ' fén', + '坞' => ' wù', + '坝' => ' bà', + '坜' => ' lì', + '坛' => ' tán', + '坚' => ' jiān', + '坙' => ' jīng', + '垣' => ' yuán', + '垥' => ' xié', + '圽' => ' mò', + '埦' => ' wǎn', + '埰' => ' cài', + '埯' => ' ǎn', + '埮' => ' tàn', + '埭' => ' dài', + '埬' => ' dōng', + '埫' => ' chǒng', + '埪' => ' kōng', + '埩' => ' zhēng', + '埨' => ' lǔn', + '埧' => ' jù', + '埥' => ' qīng', + '埲' => ' běng', + '埤' => ' pí', + '埣' => ' suì', + '埢' => ' quán', + '埡' => ' yā', + '埠' => ' bù', + '域' => ' yù', + '埞' => ' dǐ', + '埝' => ' niàn', + '埜' => ' yě', + '埛' => ' jiōng', + '埱' => ' chù', + '埳' => ' kǎn', + '埙' => ' xūn', + '堀' => ' kū', + '堊' => ' è', + '堉' => ' yù', + '堈' => ' gāng', + '堇' => ' jǐn', + '堆' => ' duī', + '堅' => ' jiān', + '堄' => ' nì', + '堃' => ' kūn', + '堂' => ' táng', + '堁' => ' kè', + '埿' => ' ní', + '埴' => ' zhí', + '埾' => ' jù', + '埽' => ' sào', + '埼' => ' qí', + '埻' => ' zhǔn', + '基' => ' jī', + '培' => ' péi', + '埸' => ' yì', + '執' => ' zhí', + '埶' => ' yì', + '埵' => ' duǒ', + '埚' => ' guō', + '埘' => ' shí', + '垦' => ' kěn', + '垲' => ' kǎi', + '垼' => ' yì', + '垻' => ' bà', + '垺' => ' fū', + '垹' => ' bāng', + '垸' => ' yuàn', + '垷' => ' xiàn', + '垶' => ' xīng', + '垵' => ' ǎn', + '垴' => ' nǎo', + '垳' => ' hang', + '垱' => ' dàng', + '垾' => ' hàn', + '垰' => ' ka', + '垯' => ' da', + '垮' => ' kuǎ', + '垭' => ' yā', + '垬' => ' hóng', + '垫' => ' diàn', + '垪' => ' bing', + '垩' => ' è', + '垨' => ' shǒu', + '垧' => ' shǎng', + '垽' => ' yìn', + '垿' => ' xù', + '埗' => ' bù', + '埌' => ' làng', + '埖' => ' hua', + '埕' => ' chéng', + '埔' => ' bù', + '埓' => ' liè', + '埒' => ' liè', + '埑' => ' zhé', + '埐' => ' jīn', + '埏' => ' shān', + '城' => ' chéng', + '埍' => ' juǎn', + '埋' => ' mái', + '埀' => ' chuí', + '埊' => ' dì', + '埉' => ' jiā', + '埈' => ' jùn', + '埇' => ' yǒng', + '埆' => ' què', + '埅' => ' fáng', + '埄' => ' běng', + '埃' => ' āi', + '埂' => ' gěng', + '埁' => ' qín', + '圾' => ' jī', + '圼' => ' niè', + '噭' => ' jiào', + '嚭' => ' pǐ', + '嚷' => ' rǎng', + '嚶' => ' yīng', + '嚵' => ' chán', + '嚴' => ' yán', + '嚳' => ' kù', + '嚲' => ' duǒ', + '嚱' => ' xì', + '嚰' => ' mó', + '嚯' => ' huò', + '嚮' => ' xiàng', + '嚬' => ' pín', + '嚹' => ' lá', + '嚫' => ' chèn', + '嚪' => ' dàn', + '嚩' => ' mó', + '嚨' => ' lóng', + '嚧' => ' lú', + '嚦' => ' lì', + '嚥' => ' yàn', + '嚤' => ' mó', + '嚣' => ' xiāo', + '嚢' => ' náng', + '嚸' => ' diǎn', + '嚺' => ' tà', + '嚠' => ' liú', + '囇' => ' lì', + '囑' => ' zhǔ', + '囐' => ' zá', + '囏' => ' jiān', + '囎' => ' zen', + '囍' => ' xǐ', + '囌' => ' sū', + '囋' => ' zá', + '囊' => ' náng', + '囉' => ' luō', + '囈' => ' yì', + '囆' => ' chài', + '嚻' => ' xiāo', + '囅' => ' chǎn', + '囄' => ' lí', + '囃' => ' cà', + '囂' => ' xiāo', + '囁' => ' niè', + '囀' => ' zhuàn', + '嚿' => ' huò', + '嚾' => ' huān', + '嚽' => ' chuò', + '嚼' => ' jué', + '嚡' => ' hai', + '嚟' => ' lí', + '囓' => ' niè', + '噹' => ' dāng', + '嚃' => ' tā', + '嚂' => ' làn', + '嚁' => ' dí', + '嚀' => ' níng', + '噿' => ' zuǐ', + '噾' => ' yīn', + '噽' => ' pǐ', + '噼' => ' pī', + '噻' => ' sāi', + '噺' => ' xin', + '噸' => ' dūn', + '嚅' => ' rú', + '噷' => ' hm', + '噶' => ' gá', + '噵' => ' dào', + '噴' => ' pēn', + '噳' => ' yǔ', + '噲' => ' kuài', + '噱' => ' jué', + '噰' => ' yōng', + '噯' => ' āi', + '噮' => ' yuàn', + '嚄' => ' huō', + '嚆' => ' hāo', + '嚞' => ' zhé', + '嚓' => ' cā', + '嚝' => ' hōng', + '嚜' => ' me', + '嚛' => ' hù', + '嚚' => ' yín', + '嚙' => ' niè', + '嚘' => ' yōu', + '嚗' => ' bó', + '嚖' => ' huì', + '嚕' => ' lǔ', + '嚔' => ' tì', + '嚒' => ' me', + '嚇' => ' xià', + '嚑' => ' xun', + '嚐' => ' cháng', + '嚏' => ' tì', + '嚎' => ' háo', + '嚍' => ' jìn', + '嚌' => ' jì', + '嚋' => ' chóu', + '嚊' => ' pì', + '嚉' => ' duō', + '嚈' => ' yè', + '囒' => ' lán', + '囔' => ' nāng', + '圻' => ' qí', + '圖' => ' tú', + '圠' => ' yà', + '土' => ' tǔ', + '圞' => ' luán', + '圝' => ' luán', + '圜' => ' huán', + '圛' => ' yì', + '圚' => ' huì', + '圙' => ' lüè', + '團' => ' tuán', + '圗' => ' tú', + '圕' => ' tú', + '圢' => ' tǐng', + '圔' => ' yà', + '圓' => ' yuán', + '園' => ' yuán', + '圑' => ' fù', + '圐' => ' kū', + '圏' => ' quān', + '圎' => ' yuán', + '圍' => ' wéi', + '圌' => ' chuán', + '國' => ' guó', + '圡' => ' tǔ', + '圣' => ' shèng', + '圉' => ' yǔ', + '地' => ' dì', + '场' => ' chǎng', + '圹' => ' kuàng', + '圸' => ' shan', + '圷' => ' xia', + '圶' => ' qià', + '圵' => ' dàng', + '圴' => ' zhuó', + '圳' => ' zhèn', + '圲' => ' qiān', + '圱' => ' qiān', + '圯' => ' yí', + '圤' => ' pǔ', + '圮' => ' pǐ', + '圭' => ' guī', + '圬' => ' wū', + '圫' => ' yù', + '圪' => ' gē', + '圩' => ' wéi', + '在' => ' zài', + '圧' => ' yā', + '圦' => ' kuai', + '圥' => ' lù', + '圊' => ' qīng', + '圈' => ' quān', + '囕' => ' lǎn', + '囡' => ' nān', + '囫' => ' hú', + '囪' => ' cōng', + '囩' => ' yún', + '囨' => ' piān', + '囧' => ' jiǒng', + '囦' => ' yuān', + '囥' => ' kàng', + '囤' => ' dùn', + '団' => ' tuán', + '团' => ' tuán', + '因' => ' yīn', + '园' => ' yuán', + '囟' => ' xìn', + '回' => ' huí', + '囝' => ' jiǎn', + '囜' => ' nín', + '四' => ' sì', + '囚' => ' qiú', + '囙' => ' yīn', + '囘' => ' huí', + '囗' => ' wéi', + '囖' => ' lo', + '囬' => ' huí', + '囮' => ' é', + '圇' => ' lún', + '囼' => ' tāi', + '圆' => ' yuán', + '圅' => ' hán', + '圄' => ' yǔ', + '圃' => ' pǔ', + '圂' => ' hùn', + '圁' => ' yín', + '圀' => ' guó', + '囿' => ' yòu', + '图' => ' tú', + '国' => ' guó', + '囻' => ' guó', + '囯' => ' guó', + '固' => ' gù', + '囹' => ' líng', + '囸' => ' rì', + '囷' => ' qūn', + '囶' => ' guó', + '囵' => ' lún', + '围' => ' wéi', + '図' => ' tú', + '囲' => ' tōng', + '囱' => ' cōng', + '困' => ' kùn', + '哌' => ' pài', + '哊' => ' yòu', + '堌' => ' gù', + '力' => ' lì', + '劥' => ' kēng', + '劤' => ' jìn', + '劣' => ' liè', + '劢' => ' mài', + '务' => ' wù', + '加' => ' jiā', + '功' => ' gōng', + '办' => ' bàn', + '劝' => ' quàn', + '劜' => ' yà', + '劚' => ' zhǔ', + '劧' => ' zhǐ', + '劙' => ' lí', + '劘' => ' mó', + '劗' => ' jiǎn', + '劖' => ' chán', + '劕' => ' zhì', + '劔' => ' jiàn', + '劓' => ' yì', + '劒' => ' jiàn', + '劑' => ' jì', + '劐' => ' huō', + '劦' => ' xié', + '动' => ' dòng', + '劎' => ' jiàn', + '劵' => ' juàn', + '势' => ' shì', + '劾' => ' hé', + '劽' => ' liè', + '劼' => ' jié', + '劻' => ' kuāng', + '劺' => ' móu', + '効' => ' xiào', + '劸' => ' wā', + '劷' => ' yáng', + '劶' => ' kǒu', + '労' => ' láo', + '助' => ' zhù', + '劳' => ' láo', + '劲' => ' jìn', + '励' => ' lì', + '劰' => ' mò', + '劯' => ' zhū', + '劮' => ' yì', + '劭' => ' shào', + '劬' => ' qú', + '劫' => ' jié', + '努' => ' nǔ', + '劏' => ' tāng', + '劍' => ' jiàn', + '勁' => ' jìn', + '剧' => ' jù', + '剱' => ' jiàn', + '剰' => ' shèng', + '副' => ' fù', + '剮' => ' guǎ', + '剭' => ' wū', + '剬' => ' duān', + '剫' => ' duó', + '剪' => ' jiǎn', + '剩' => ' shèng', + '剨' => ' huō', + '剦' => ' yān', + '剳' => ' dá', + '剥' => ' bō', + '剤' => ' jì', + '剣' => ' jiàn', + '剢' => ' dū', + '剡' => ' shàn', + '剠' => ' qíng', + '剟' => ' duō', + '剞' => ' jī', + '剝' => ' bō', + '剜' => ' wān', + '割' => ' gē', + '剴' => ' kǎi', + '劌' => ' guì', + '劁' => ' qiāo', + '劋' => ' jiǎo', + '劊' => ' guì', + '劉' => ' liú', + '劈' => ' pī', + '劇' => ' jù', + '劆' => ' lián', + '劅' => ' zhuò', + '劄' => ' zhā', + '劃' => ' huà', + '劂' => ' jué', + '劀' => ' guā', + '創' => ' chuàng', + '剿' => ' jiǎo', + '剾' => ' kōu', + '剽' => ' piāo', + '剼' => ' shān', + '剻' => ' pěng', + '剺' => ' lí', + '剹' => ' lù', + '剸' => ' tuán', + '剷' => ' chǎn', + '剶' => ' chuān', + '勀' => ' kè', + '勂' => ' gào', + '剚' => ' zì', + '匃' => ' gài', + '匍' => ' pú', + '匌' => ' gé', + '匋' => ' táo', + '匊' => ' jū', + '匉' => ' pēng', + '匈' => ' xiōng', + '匇' => ' yi', + '匆' => ' cōng', + '包' => ' bāo', + '匄' => ' gài', + '匂' => ' bi', + '匏' => ' páo', + '匁' => ' wén', + '匀' => ' yún', + '勿' => ' wù', + '勾' => ' gōu', + '勽' => ' bào', + '勼' => ' jiū', + '勻' => ' yún', + '勺' => ' sháo', + '勹' => ' bāo', + '勸' => ' quàn', + '匎' => ' è', + '匐' => ' fú', + '勶' => ' chè', + '匝' => ' zā', + '匧' => ' qiè', + '匦' => ' guǐ', + '匥' => ' biàn', + '匤' => ' qū', + '匣' => ' xiá', + '匢' => ' hū', + '匡' => ' kuāng', + '匠' => ' jiàng', + '匟' => ' kàng', + '匞' => ' jiàng', + '匜' => ' yí', + '匑' => ' gōng', + '匛' => ' jiù', + '匚' => ' fāng', + '匙' => ' shi', + '匘' => ' nǎo', + '北' => ' běi', + '化' => ' huà', + '匕' => ' bǐ', + '匔' => ' qiōng', + '匓' => ' jiù', + '匒' => ' dá', + '勷' => ' ráng', + '勵' => ' lì', + '勃' => ' bó', + '勏' => ' bù', + '務' => ' wù', + '勘' => ' kān', + '勗' => ' xù', + '勖' => ' xù', + '動' => ' dòng', + '勔' => ' miǎn', + '勓' => ' kài', + '勒' => ' lēi', + '勑' => ' chì', + '勐' => ' měng', + '勎' => ' lù', + '勛' => ' xūn', + '勍' => ' qíng', + '勌' => ' juàn', + '勋' => ' xūn', + '勊' => ' kè', + '勉' => ' miǎn', + '勈' => ' yǒng', + '勇' => ' yǒng', + '勆' => ' láng', + '勅' => ' chì', + '勄' => ' mǐn', + '勚' => ' yì', + '勜' => ' wěng', + '勴' => ' lǜ', + '勩' => ' yì', + '勳' => ' xūn', + '勲' => ' xūn', + '勱' => ' mài', + '勰' => ' xié', + '勯' => ' dān', + '勮' => ' jù', + '勭' => ' tóng', + '勬' => ' juān', + '勫' => ' fān', + '勪' => ' jué', + '勨' => ' xiàng', + '勝' => ' shèng', + '勧' => ' quàn', + '勦' => ' chāo', + '勥' => ' jiàng', + '勤' => ' qín', + '勣' => ' jī', + '勢' => ' shì', + '勡' => ' piào', + '勠' => ' lù', + '募' => ' mù', + '勞' => ' láo', + '剛' => ' gāng', + '剙' => ' chuàng', + '匩' => ' kuāng', + '凋' => ' diāo', + '凕' => ' mǐng', + '凔' => ' chuàng', + '凓' => ' lì', + '凒' => ' yí', + '凑' => ' còu', + '凐' => ' yīn', + '减' => ' jiǎn', + '凎' => ' gàn', + '凍' => ' dòng', + '凌' => ' líng', + '凊' => ' qìng', + '凗' => ' cuī', + '凉' => ' liáng', + '凈' => ' jìng', + '凇' => ' sōng', + '准' => ' zhǔn', + '凅' => ' gù', + '凄' => ' qī', + '凃' => ' tú', + '凂' => ' měi', + '凁' => ' shù', + '净' => ' jìng', + '凖' => ' zhun', + '凘' => ' sī', + '冾' => ' qià', + '凥' => ' jū', + '凯' => ' kǎi', + '凮' => ' fēng', + '凭' => ' píng', + '凬' => ' fēng', + '凫' => ' fú', + '凪' => ' zhi', + '凩' => ' mu', + '凨' => ' fēng', + '凧' => ' zheng', + '処' => ' chǔ', + '凤' => ' fèng', + '凙' => ' duó', + '凣' => ' fán', + '凢' => ' fán', + '凡' => ' fán', + '几' => ' jǐ', + '凟' => ' dú', + '凞' => ' xī', + '凝' => ' níng', + '凜' => ' lǐn', + '凛' => ' lǐn', + '凚' => ' jìn', + '冿' => ' jiān', + '冽' => ' liè', + '凱' => ' kǎi', + '冗' => ' rǒng', + '冡' => ' méng', + '冠' => ' guān', + '冟' => ' shì', + '冞' => ' mí', + '冝' => ' yí', + '农' => ' nóng', + '军' => ' jūn', + '冚' => ' kǎn', + '写' => ' xiě', + '冘' => ' yín', + '冖' => ' mì', + '冣' => ' jù', + '冕' => ' miǎn', + '冔' => ' xǔ', + '冓' => ' gòu', + '冒' => ' mào', + '冑' => ' zhòu', + '冐' => ' mào', + '冏' => ' jiǒng', + '冎' => ' guǎ', + '再' => ' zài', + '册' => ' cè', + '冢' => ' zhǒng', + '冤' => ' yuān', + '冼' => ' xiǎn', + '冱' => ' hù', + '冻' => ' dòng', + '冺' => ' mǐn', + '冹' => ' fú', + '冸' => ' pàn', + '冷' => ' lěng', + '冶' => ' yě', + '况' => ' kuàng', + '冴' => ' hù', + '决' => ' jué', + '冲' => ' chōng', + '冰' => ' bīng', + '冥' => ' míng', + '冯' => ' féng', + '冮' => ' gāng', + '冭' => ' tái', + '冬' => ' dōng', + '冫' => ' bīng', + '冪' => ' mì', + '冩' => ' xiě', + '冨' => ' fù', + '冧' => ' lín', + '冦' => ' kòu', + '凰' => ' huáng', + '凲' => ' gān', + '剘' => ' qí', + '刳' => ' kū', + '刽' => ' guì', + '刼' => ' jié', + '刻' => ' kè', + '刺' => ' cì', + '刹' => ' shā', + '券' => ' quàn', + '刷' => ' shuā', + '制' => ' zhì', + '刵' => ' èr', + '刴' => ' duò', + '刲' => ' kuī', + '刿' => ' guì', + '刱' => ' chuàng', + '到' => ' dào', + '刯' => ' gēng', + '刮' => ' guā', + '刭' => ' jǐng', + '刬' => ' chǎn', + '别' => ' bié', + '刪' => ' shān', + '利' => ' lì', + '刨' => ' páo', + '刾' => ' cì', + '剀' => ' kǎi', + '刦' => ' jié', + '前' => ' qián', + '剗' => ' chǎn', + '剖' => ' pōu', + '剕' => ' fèi', + '剔' => ' tī', + '剓' => ' lí', + '剒' => ' cuò', + '剑' => ' jiàn', + '剐' => ' guǎ', + '剏' => ' chuàng', + '剎' => ' shā', + '剌' => ' lá', + '剁' => ' duò', + '剋' => ' kè', + '削' => ' xuē', + '剉' => ' cuò', + '剈' => ' yuān', + '則' => ' zé', + '剆' => ' luǒ', + '剅' => ' lóu', + '剄' => ' jǐng', + '剃' => ' tì', + '剂' => ' jì', + '刧' => ' jié', + '別' => ' bié', + '凳' => ' dèng', + '凿' => ' záo', + '刉' => ' jī', + '刈' => ' yì', + '切' => ' qiè', + '分' => ' fēn', + '刅' => ' chuāng', + '刄' => ' rèn', + '刃' => ' rèn', + '刂' => ' dāo', + '刁' => ' diāo', + '刀' => ' dāo', + '凾' => ' hán', + '刋' => ' qiàn', + '函' => ' hán', + '凼' => ' dàng', + '击' => ' jī', + '出' => ' chū', + '凹' => ' āo', + '凸' => ' tū', + '凷' => ' kuài', + '凶' => ' xiōng', + '凵' => ' qiǎn', + '凴' => ' píng', + '刊' => ' kān', + '刌' => ' cǔn', + '判' => ' pàn', + '则' => ' zé', + '刣' => ' zhōng', + '刢' => ' líng', + '刡' => ' mǐn', + '删' => ' shān', + '刟' => ' jū', + '刞' => ' qù', + '初' => ' chū', + '刜' => ' fú', + '创' => ' chuàng', + '刚' => ' gāng', + '刘' => ' liú', + '刍' => ' chú', + '列' => ' liè', + '刖' => ' yuè', + '刕' => ' lí', + '刔' => ' jué', + '刓' => ' wán', + '划' => ' huà', + '刑' => ' xíng', + '刐' => ' dǎn', + '刏' => ' jī', + '刎' => ' wěn', + '匨' => ' zāng', + '匪' => ' fěi', + '哉' => ' zāi', + '吼' => ' hǒu', + '呆' => ' dāi', + '呅' => ' méi', + '呄' => ' gé', + '呃' => ' è', + '呂' => ' lǚ', + '呁' => ' jùn', + '呀' => ' ya', + '吿' => ' gào', + '吾' => ' wú', + '吽' => ' hōng', + '吻' => ' wěn', + '呈' => ' chéng', + '吺' => ' dōu', + '吹' => ' chuī', + '吸' => ' xī', + '吷' => ' xuè', + '吶' => ' nà', + '吵' => ' chǎo', + '吴' => ' wú', + '吳' => ' wú', + '吲' => ' yǐn', + '吱' => ' zhī', + '呇' => ' qǐ', + '呉' => ' wú', + '启' => ' qǐ', + '呖' => ' lì', + '呠' => ' pěn', + '呟' => ' juǎn', +); \ No newline at end of file diff --git a/vendor/overtrue/pinyin/data/words_5 b/vendor/overtrue/pinyin/data/words_5 new file mode 100644 index 0000000..f7eeee6 --- /dev/null +++ b/vendor/overtrue/pinyin/data/words_5 @@ -0,0 +1,2056 @@ + ' shī', + '呝' => ' è', + '呜' => ' wū', + '呛' => ' qiāng', + '呚' => ' wen', + '呙' => ' guō', + '员' => ' yuán', + '呗' => ' bei', + '呕' => ' ǒu', + '告' => ' gào', + '呔' => ' dāi', + '呓' => ' yì', + '呒' => ' fǔ', + '呑' => ' tūn', + '呐' => ' nà', + '呏' => ' shēng', + '呎' => ' chǐ', + '呍' => ' hōng', + '呌' => ' jiào', + '呋' => ' fū', + '吰' => ' hóng', + '吮' => ' shǔn', + '呢' => ' ne', + '合' => ' hé', + '吒' => ' zhā', + '向' => ' xiàng', + '吐' => ' tǔ', + '吏' => ' lì', + '后' => ' hòu', + '名' => ' míng', + '同' => ' tóng', + '吋' => ' cùn', + '吊' => ' diào', + '吉' => ' jí', + '吇' => ' zǐ', + '吔' => ' yě', + '吆' => ' yāo', + '吅' => ' xuān', + '各' => ' gè', + '吃' => ' chī', + '吂' => ' máng', + '吁' => ' xū', + '吀' => ' miē', + '叿' => ' hōng', + '叾' => ' liǎo', + '叽' => ' jī', + '吓' => ' xià', + '吕' => ' lǚ', + '吭' => ' kēng', + '吢' => ' qìn', + '听' => ' tīng', + '含' => ' hán', + '吪' => ' é', + '吩' => ' fēn', + '吨' => ' dūn', + '吧' => ' ba', + '否' => ' fǒu', + '吥' => ' bù', + '吤' => ' jiè', + '吣' => ' qìn', + '吡' => ' bǐ', + '吖' => ' yā', + '吠' => ' fèi', + '吟' => ' yín', + '吞' => ' tūn', + '吝' => ' lìn', + '吜' => ' chǒu', + '君' => ' jūn', + '吚' => ' yī', + '吙' => ' huō', + '吘' => ' ǒu', + '吗' => ' ma', + '呡' => ' wěn', + '呣' => ' ḿ', + '叻' => ' lè', + '咤' => ' zhà', + '咮' => ' zhòu', + '咭' => ' jī', + '咬' => ' yǎo', + '咫' => ' zhǐ', + '咪' => ' mī', + '咩' => ' miē', + '咨' => ' zī', + '咧' => ' liě', + '咦' => ' yí', + '咥' => ' xì', + '咣' => ' guāng', + '咰' => ' shù', + '咢' => ' è', + '咡' => ' èr', + '咠' => ' qì', + '咟' => ' huò', + '咞' => ' xiàn', + '咝' => ' sī', + '咜' => ' ta', + '咛' => ' níng', + '咚' => ' dōng', + '咙' => ' lóng', + '咯' => ' gē', + '咱' => ' zán', + '咗' => ' zuo', + '咾' => ' lǎo', + '哈' => ' hā', + '哇' => ' wa', + '哆' => ' duō', + '哅' => ' xiōng', + '哄' => ' hōng', + '哃' => ' tóng', + '哂' => ' shěn', + '品' => ' pǐn', + '哀' => ' āi', + '咿' => ' yī', + '咽' => ' yàn', + '咲' => ' xiào', + '咼' => ' guō', + '咻' => ' xiū', + '咺' => ' xuǎn', + '咹' => ' è', + '咸' => ' xián', + '咷' => ' táo', + '咶' => ' huài', + '咵' => ' kuǎ', + '咴' => ' huī', + '咳' => ' hāi', + '咘' => ' bù', + '咖' => ' kā', + '呤' => ' lìng', + '呰' => ' zǐ', + '呺' => ' xiāo', + '呹' => ' yì', + '呸' => ' pēi', + '呷' => ' gā', + '呶' => ' náo', + '呵' => ' ā', + '呴' => ' xǔ', + '味' => ' wèi', + '呲' => ' cī', + '呱' => ' gū', + '呯' => ' píng', + '呼' => ' hū', + '呮' => ' qì', + '呭' => ' yì', + '呬' => ' xì', + '呫' => ' tiè', + '呪' => ' zhòu', + '呩' => ' shì', + '周' => ' zhōu', + '呧' => ' dǐ', + '呦' => ' yōu', + '呥' => ' rán', + '呻' => ' shēn', + '命' => ' mìng', + '咕' => ' gū', + '咊' => ' hé', + '咔' => ' kā', + '咓' => ' wǎ', + '咒' => ' zhòu', + '咑' => ' dā', + '咐' => ' fù', + '咏' => ' yǒng', + '咎' => ' jiù', + '咍' => ' hāi', + '和' => ' hé', + '咋' => ' zǎ', + '咉' => ' yāng', + '呾' => ' dá', + '咈' => ' fú', + '咇' => ' bié', + '咆' => ' páo', + '咅' => ' pǒu', + '咄' => ' duō', + '咃' => ' tuō', + '咂' => ' zā', + '咁' => ' hán', + '咀' => ' jǔ', + '呿' => ' qù', + '叼' => ' diāo', + '叺' => ' chǐ', + '匫' => ' hū', + '卫' => ' wèi', + '卵' => ' luǎn', + '却' => ' què', + '即' => ' jí', + '卲' => ' shào', + '危' => ' wēi', + '印' => ' yìn', + '卯' => ' mǎo', + '卮' => ' zhī', + '卭' => ' qióng', + '卬' => ' áng', + '卪' => ' jié', + '卷' => ' juǎn', + '卩' => ' jié', + '卨' => ' xiè', + '卧' => ' wò', + '卦' => ' guà', + '卥' => ' xī', + '卤' => ' lǔ', + '卣' => ' yǒu', + '卢' => ' lú', + '卡' => ' kǎ', + '占' => ' zhàn', + '卶' => ' chǐ', + '卸' => ' xiè', + '卞' => ' biàn', + '厅' => ' tīng', + '厏' => ' zhǎ', + '厎' => ' dǐ', + '厍' => ' shè', + '厌' => ' yàn', + '压' => ' yā', + '厊' => ' yǎ', + '厉' => ' lì', + '厈' => ' hǎn', + '厇' => ' zhé', + '历' => ' lì', + '厄' => ' è', + '卹' => ' xù', + '厃' => ' wěi', + '厂' => ' chǎng', + '厁' => ' san', + '厀' => ' xī', + '卿' => ' qīng', + '卾' => ' è', + '卽' => ' jí', + '卼' => ' wù', + '卻' => ' què', + '卺' => ' jǐn', + '卟' => ' bǔ', + '卝' => ' kuàng', + '厑' => ' yá', + '匷' => ' jué', + '十' => ' shí', + '區' => ' qū', + '匿' => ' nì', + '匾' => ' biǎn', + '匽' => ' yǎn', + '匼' => ' kē', + '医' => ' yī', + '区' => ' qū', + '匹' => ' pǐ', + '匸' => ' xì', + '匶' => ' jiù', + '千' => ' qiān', + '匵' => ' dú', + '匴' => ' suǎn', + '匳' => ' lián', + '匲' => ' lián', + '匱' => ' guì', + '匰' => ' dān', + '匯' => ' huì', + '匮' => ' kuì', + '匭' => ' guǐ', + '匬' => ' yǔ', + '卂' => ' xùn', + '卄' => ' niàn', + '卜' => ' bo', + '卑' => ' bēi', + '卛' => ' shuài', + '博' => ' bó', + '卙' => ' jí', + '単' => ' dān', + '南' => ' nán', + '卖' => ' mài', + '单' => ' dān', + '協' => ' xié', + '卓' => ' zhuō', + '卒' => ' zú', + '卐' => ' wàn', + '卅' => ' sà', + '协' => ' xié', + '华' => ' huá', + '卍' => ' wàn', + '卌' => ' xì', + '卋' => ' shì', + '半' => ' bàn', + '卉' => ' huì', + '午' => ' wǔ', + '升' => ' shēng', + '卆' => ' zú', + '厐' => ' páng', + '厒' => ' hé', + '叹' => ' tàn', + '叔' => ' shū', + '叞' => ' wèi', + '叝' => ' gào', + '叜' => ' sǒu', + '叛' => ' pàn', + '叚' => ' jiǎ', + '叙' => ' xù', + '变' => ' biàn', + '受' => ' shòu', + '取' => ' qǔ', + '叕' => ' zhuó', + '叓' => ' shì', + '叠' => ' dié', + '叒' => ' ruò', + '发' => ' fā', + '叐' => ' bá', + '叏' => ' guài', + '収' => ' shōu', + '反' => ' fǎn', + '双' => ' shuāng', + '友' => ' yǒu', + '及' => ' jí', + '叉' => ' chā', + '叟' => ' sǒu', + '叡' => ' ruì', + '叇' => ' dài', + '叮' => ' dīng', + '司' => ' sī', + '号' => ' hào', + '叶' => ' yè', + '叵' => ' pǒ', + '叴' => ' qiú', + '右' => ' yòu', + '史' => ' shǐ', + '叱' => ' chì', + '台' => ' tái', + '可' => ' kě', + '叭' => ' bā', + '叢' => ' cóng', + '召' => ' zhào', + '叫' => ' jiào', + '只' => ' zhǐ', + '叩' => ' kòu', + '叨' => ' dāo', + '叧' => ' guǎ', + '另' => ' lìng', + '句' => ' jù', + '古' => ' gǔ', + '口' => ' kǒu', + '又' => ' yòu', + '叆' => ' ài', + '厓' => ' yá', + '原' => ' yuán', + '厩' => ' jiù', + '厨' => ' chú', + '厧' => ' diān', + '厦' => ' shà', + '厥' => ' jué', + '厤' => ' lì', + '厣' => ' yǎn', + '厢' => ' xiāng', + '厡' => ' yuán', + '厠' => ' cè', + '厞' => ' fèi', + '厫' => ' áo', + '厝' => ' cuò', + '厜' => ' zuī', + '厛' => ' tīng', + '厚' => ' hòu', + '厙' => ' shè', + '厘' => ' lí', + '厗' => ' tí', + '厖' => ' páng', + '厕' => ' cè', + '厔' => ' zhì', + '厪' => ' jǐn', + '厬' => ' guǐ', + '叅' => ' cān', + '厺' => ' qù', + '叄' => ' cān', + '參' => ' cān', + '参' => ' cān', + '叁' => ' sān', + '叀' => ' zhuān', + '县' => ' xiàn', + '厾' => ' dū', + '厽' => ' lěi', + '厼' => ' ěr', + '去' => ' qù', + '厹' => ' róu', + '厭' => ' yàn', + '厸' => ' lín', + '厷' => ' gōng', + '厶' => ' sī', + '厵' => ' yuán', + '厴' => ' yǎn', + '厳' => ' yán', + '厲' => ' lì', + '厱' => ' lán', + '厰' => ' chǎng', + '厯' => ' lì', + '厮' => ' sī', + '堋' => ' péng', + '堍' => ' tù', + '庎' => ' jiè', + '屠' => ' tú', + '屪' => ' liáo', + '屩' => ' juē', + '屨' => ' jù', + '屧' => ' xiè', + '屦' => ' jù', + '履' => ' lǚ', + '層' => ' céng', + '屣' => ' xǐ', + '屢' => ' lǚ', + '屡' => ' lǚ', + '屟' => ' xiè', + '屬' => ' shǔ', + '属' => ' shǔ', + '屝' => ' fèi', + '屜' => ' tì', + '屛' => ' píng', + '屚' => ' lòu', + '屙' => ' ē', + '屘' => ' mǎn', + '屗' => ' wěi', + '屖' => ' xī', + '展' => ' zhǎn', + '屫' => ' jué', + '屭' => ' xì', + '屓' => ' xiè', + '屺' => ' qǐ', + '岄' => ' yuè', + '岃' => ' rèn', + '岂' => ' qǐ', + '岁' => ' suì', + '岀' => ' chū', + '屿' => ' yǔ', + '屾' => ' shēn', + '屽' => ' hàn', + '屼' => ' wù', + '屻' => ' rèn', + '屹' => ' yì', + '屮' => ' chè', + '屸' => ' lóng', + '屷' => ' hui', + '屶' => ' dao', + '屵' => ' è', + '屴' => ' lì', + '屳' => ' xiān', + '屲' => ' wa', + '山' => ' shān', + '屰' => ' nì', + '屯' => ' tún', + '屔' => ' ní', + '屒' => ' zhěn', + '岆' => ' yǎo', + '尬' => ' gà', + '尶' => ' gān', + '尵' => ' tuí', + '尴' => ' gān', + '尳' => ' gǔ', + '尲' => ' gān', + '就' => ' jiù', + '尰' => ' zhǒng', + '尯' => ' kuì', + '尮' => ' duò', + '尭' => ' yáo', + '尫' => ' wāng', + '尸' => ' shī', + '尪' => ' wāng', + '尩' => ' wāng', + '尨' => ' máng', + '尧' => ' yáo', + '尦' => ' liào', + '尥' => ' liào', + '尤' => ' yóu', + '尣' => ' wāng', + '尢' => ' yóu', + '尡' => ' kun', + '尷' => ' gān', + '尹' => ' yǐn', + '屑' => ' xiè', + '屆' => ' jiè', + '屐' => ' jī', + '屏' => ' píng', + '屎' => ' shǐ', + '屍' => ' shī', + '屌' => ' diǎo', + '屋' => ' wū', + '届' => ' jiè', + '屉' => ' tì', + '屈' => ' qū', + '屇' => ' tián', + '居' => ' jū', + '尺' => ' chǐ', + '屄' => ' bī', + '屃' => ' xì', + '层' => ' céng', + '屁' => ' pì', + '局' => ' jú', + '尿' => ' niào', + '尾' => ' wěi', + '尽' => ' jǐn', + '尼' => ' ní', + '尻' => ' kāo', + '岅' => ' bǎn', + '岇' => ' áng', + '尟' => ' xiǎn', + '峈' => ' luò', + '峒' => ' dòng', + '峑' => ' quān', + '峐' => ' gāi', + '峏' => ' ér', + '峎' => ' ěn', + '峍' => ' lù', + '峌' => ' dié', + '峋' => ' xún', + '峊' => ' fù', + '峉' => ' è', + '峇' => ' bā', + '峔' => ' mǔ', + '峆' => ' hé', + '峅' => ' bian', + '峄' => ' yì', + '峃' => ' xué', + '峂' => ' tóng', + '峁' => ' mǎo', + '峀' => ' xiù', + '岿' => ' kuī', + '岾' => ' hàn', + '岽' => ' dōng', + '峓' => ' yí', + '峕' => ' shí', + '岻' => ' chí', + '峢' => ' lǐ', + '峬' => ' bū', + '峫' => ' xié', + '峪' => ' yù', + '峩' => ' é', + '峨' => ' é', + '峧' => ' jiāo', + '峦' => ' luán', + '峥' => ' zhēng', + '峤' => ' jiào', + '峣' => ' yáo', + '峡' => ' xiá', + '峖' => ' ān', + '峠' => ' gu', + '峟' => ' yòu', + '峞' => ' wéi', + '峝' => ' tóng', + '峜' => ' jì', + '峛' => ' lǐ', + '峚' => ' mì', + '峙' => ' zhì', + '峘' => ' huán', + '峗' => ' wéi', + '岼' => ' ping', + '岺' => ' líng', + '岈' => ' yá', + '岔' => ' chà', + '岞' => ' zuò', + '岝' => ' zuò', + '岜' => ' bā', + '岛' => ' dǎo', + '岚' => ' lán', + '岙' => ' ào', + '岘' => ' xiàn', + '岗' => ' gǎng', + '岖' => ' qū', + '岕' => ' jiè', + '岓' => ' qí', + '岠' => ' jù', + '岒' => ' qián', + '岑' => ' cén', + '岐' => ' qí', + '岏' => ' wán', + '岎' => ' fén', + '岍' => ' qiān', + '岌' => ' jí', + '岋' => ' è', + '岊' => ' jié', + '岉' => ' wù', + '岟' => ' yǎng', + '岡' => ' gāng', + '岹' => ' tiáo', + '岮' => ' tuó', + '岸' => ' àn', + '岷' => ' mín', + '岶' => ' pò', + '岵' => ' hù', + '岴' => ' qū', + '岳' => ' yuè', + '岲' => ' kuàng', + '岱' => ' dài', + '岰' => ' ào', + '岯' => ' pí', + '岭' => ' lǐng', + '岢' => ' kě', + '岬' => ' jiǎ', + '岫' => ' xiù', + '岪' => ' fú', + '岩' => ' yán', + '岨' => ' qū', + '岧' => ' tiáo', + '岦' => ' lì', + '岥' => ' pō', + '岤' => ' xué', + '岣' => ' gǒu', + '尠' => ' xiǎn', + '尞' => ' liào', + '峮' => ' qūn', + '宐' => ' yí', + '定' => ' dìng', + '宙' => ' zhòu', + '官' => ' guān', + '宗' => ' zōng', + '宖' => ' hóng', + '宕' => ' dàng', + '宔' => ' zhǔ', + '宓' => ' mì', + '宒' => ' zhūn', + '宑' => ' jǐng', + '宏' => ' hóng', + '宜' => ' yí', + '宎' => ' yǎo', + '宍' => ' ròu', + '完' => ' wán', + '宋' => ' sòng', + '宊' => ' tū', + '安' => ' ān', + '守' => ' shǒu', + '宇' => ' yǔ', + '宆' => ' qióng', + '宅' => ' zhái', + '宛' => ' wǎn', + '宝' => ' bǎo', + '它' => ' tā', + '宪' => ' xiàn', + '宴' => ' yàn', + '害' => ' hài', + '宲' => ' bǎo', + '宱' => ' zhà', + '宰' => ' zǎi', + '宯' => ' xiāo', + '宮' => ' gōng', + '宭' => ' qún', + '宬' => ' chéng', + '宫' => ' gōng', + '宩' => ' shǐ', + '实' => ' shí', + '宨' => ' tiǎo', + '宧' => ' yí', + '宦' => ' huàn', + '宥' => ' yòu', + '室' => ' shì', + '宣' => ' xuān', + '客' => ' kè', + '审' => ' shěn', + '宠' => ' chǒng', + '実' => ' shí', + '宄' => ' guǐ', + '宂' => ' rǒng', + '家' => ' jiā', + '孜' => ' zī', + '学' => ' xué', + '孥' => ' nú', + '孤' => ' gū', + '季' => ' jì', + '孢' => ' bāo', + '孡' => ' tāi', + '孠' => ' sì', + '孟' => ' mèng', + '孞' => ' xìn', + '孝' => ' xiào', + '孛' => ' bèi', + '孨' => ' zhuǎn', + '孚' => ' fú', + '孙' => ' sūn', + '存' => ' cún', + '字' => ' zì', + '孖' => ' mā', + '孕' => ' yùn', + '孔' => ' kǒng', + '孓' => ' jué', + '孒' => ' jué', + '孑' => ' jié', + '孧' => ' you', + '孩' => ' hái', + '宁' => ' níng', + '孶' => ' zī', + '宀' => ' mián', + '孿' => ' luán', + '孾' => ' yīng', + '孽' => ' niè', + '孼' => ' niè', + '孻' => ' nái', + '孺' => ' rú', + '孹' => ' bò', + '學' => ' xué', + '孷' => ' lí', + '孵' => ' fū', + '孪' => ' luán', + '孴' => ' nǐ', + '孳' => ' zī', + '孲' => ' yā', + '孱' => ' càn', + '孰' => ' shú', + '孯' => ' qiān', + '孮' => ' cóng', + '孭' => ' miē', + '孬' => ' nāo', + '孫' => ' sūn', + '宵' => ' xiāo', + '宷' => ' shěn', + '尝' => ' cháng', + '寸' => ' cùn', + '専' => ' zhuān', + '封' => ' fēng', + '尀' => ' pǒ', + '寿' => ' shòu', + '対' => ' duì', + '寽' => ' lǜ', + '导' => ' dǎo', + '寻' => ' xún', + '寺' => ' sì', + '对' => ' duì', + '寷' => ' fēng', + '射' => ' shè', + '寶' => ' bǎo', + '寵' => ' chǒng', + '寴' => ' qīn', + '寳' => ' bǎo', + '寲' => ' yí', + '寱' => ' yì', + '寰' => ' huán', + '寯' => ' jùn', + '寮' => ' liáo', + '寭' => ' huì', + '尃' => ' fū', + '尅' => ' kè', + '寫' => ' xiě', + '尒' => ' ěr', + '尜' => ' gá', + '尛' => ' mo', + '尚' => ' shàng', + '尙' => ' shàng', + '尘' => ' chén', + '尗' => ' shū', + '尖' => ' jiān', + '尕' => ' gǎ', + '尔' => ' ěr', + '尓' => ' ěr', + '少' => ' shǎo', + '将' => ' jiāng', + '尐' => ' jié', + '小' => ' xiǎo', + '導' => ' dǎo', + '對' => ' duì', + '尌' => ' shù', + '尋' => ' xún', + '尊' => ' zūn', + '尉' => ' wèi', + '專' => ' zhuān', + '將' => ' jiāng', + '寬' => ' kuān', + '寪' => ' wěi', + '宸' => ' chén', + '寄' => ' jì', + '寎' => ' bìng', + '寍' => ' níng', + '富' => ' fù', + '寋' => ' jiàn', + '寊' => ' zhēn', + '寉' => ' què', + '寈' => ' qīng', + '寇' => ' kòu', + '密' => ' mì', + '寅' => ' yín', + '寃' => ' yuān', + '寐' => ' mèi', + '寂' => ' jì', + '寁' => ' zǎn', + '寀' => ' cǎi', + '宿' => ' sù', + '宾' => ' bīn', + '宽' => ' kuān', + '宼' => ' kòu', + '宻' => ' mì', + '宺' => ' huǎng', + '容' => ' róng', + '寏' => ' huán', + '寑' => ' qǐn', + '審' => ' shěn', + '寞' => ' mò', + '寨' => ' zhài', + '寧' => ' níng', + '實' => ' shí', + '寥' => ' liáo', + '寤' => ' wù', + '寣' => ' hū', + '寢' => ' qǐn', + '寡' => ' guǎ', + '寠' => ' jù', + '察' => ' chá', + '寝' => ' qǐn', + '寒' => ' hán', + '寜' => ' níng', + '寛' => ' kuān', + '寚' => ' bǎo', + '寙' => ' yǔ', + '寘' => ' zhì', + '寗' => ' níng', + '寖' => ' jìn', + '寕' => ' níng', + '寔' => ' shí', + '寓' => ' yù', + '峭' => ' qiào', + '峯' => ' fēng', + '孏' => ' lǎn', + '币' => ' bì', + '帋' => ' zhǐ', + '帊' => ' pà', + '帉' => ' fēn', + '师' => ' shī', + '帇' => ' niè', + '帆' => ' fān', + '帅' => ' shuài', + '帄' => ' dīng', + '布' => ' bù', + '市' => ' shì', + '帀' => ' zā', + '帍' => ' hù', + '巿' => ' fú', + '巾' => ' jīn', + '巽' => ' xùn', + '巼' => ' bā', + '巻' => ' juàn', + '巺' => ' xùn', + '巹' => ' jǐn', + '巸' => ' yí', + '巷' => ' xiàng', + '巶' => ' zhāo', + '希' => ' xī', + '帎' => ' dàn', + '巴' => ' bā', + '帛' => ' bó', + '帥' => ' shuài', + '帤' => ' rú', + '帣' => ' juǎn', + '帢' => ' qià', + '帡' => ' píng', + '帠' => ' yì', + '帟' => ' yì', + '帞' => ' mò', + '帝' => ' dì', + '帜' => ' zhì', + '帚' => ' zhǒu', + '帏' => ' wéi', + '帙' => ' zhì', + '帘' => ' lián', + '帗' => ' bō', + '帖' => ' tiē', + '帕' => ' pà', + '帔' => ' pèi', + '帓' => ' mò', + '帒' => ' dài', + '帑' => ' tǎng', + '帐' => ' zhàng', + '巵' => ' zhī', + '巳' => ' sì', + '帧' => ' zhèng', + '巍' => ' wēi', + '巗' => ' yán', + '巖' => ' yán', + '巕' => ' nie', + '巔' => ' diān', + '巓' => ' diān', + '巒' => ' luán', + '巑' => ' cuán', + '巐' => ' chǎo', + '巏' => ' quán', + '巎' => ' náo', + '巌' => ' yán', + '巙' => ' kuí', + '巋' => ' kuī', + '巊' => ' yǐng', + '巉' => ' chán', + '巈' => ' jú', + '巇' => ' xī', + '巆' => ' róng', + '巅' => ' diān', + '巄' => ' lóng', + '巃' => ' lóng', + '巂' => ' guī', + '巘' => ' yǎn', + '巚' => ' yǎn', + '已' => ' yǐ', + '巧' => ' qiǎo', + '己' => ' jǐ', + '巰' => ' qiú', + '巯' => ' qiú', + '差' => ' chà', + '巭' => ' pu', + '巬' => ' pu', + '巫' => ' wū', + '巪' => ' jù', + '巩' => ' gǒng', + '巨' => ' jù', + '左' => ' zuǒ', + '巛' => ' chuān', + '工' => ' gōng', + '巤' => ' liè', + '巣' => ' cháo', + '巢' => ' cháo', + '巡' => ' xún', + '巠' => ' jīng', + '巟' => ' huāng', + '州' => ' zhōu', + '川' => ' chuān', + '巜' => ' kuài', + '带' => ' dài', + '帨' => ' shuì', + '巀' => ' jié', + '幩' => ' fén', + '平' => ' píng', + '干' => ' gàn', + '幱' => ' lán', + '幰' => ' xiǎn', + '幯' => ' jié', + '幮' => ' chú', + '幭' => ' miè', + '幬' => ' chóu', + '幫' => ' bāng', + '幪' => ' méng', + '幨' => ' chān', + '幵' => ' jiān', + '幧' => ' qiāo', + '幦' => ' mì', + '幥' => ' zhang', + '幤' => ' bi', + '幣' => ' bì', + '幢' => ' chuáng', + '幡' => ' fān', + '幠' => ' hū', + '幟' => ' zhì', + '幞' => ' fú', + '年' => ' nián', + '并' => ' bìng', + '幜' => ' jǐng', + '広' => ' guǎng', + '庍' => ' bài', + '庌' => ' yǎ', + '庋' => ' guǐ', + '床' => ' chuáng', + '庉' => ' dùn', + '庈' => ' qín', + '庇' => ' bì', + '庆' => ' qìng', + '庅' => ' mó', + '庄' => ' zhuāng', + '庂' => ' zè', + '幷' => ' bìng', + '庁' => ' tīng', + '庀' => ' pǐ', + '广' => ' guǎng', + '幾' => ' jǐ', + '幽' => ' yōu', + '幼' => ' yòu', + '幻' => ' huàn', + '幺' => ' yāo', + '幹' => ' gàn', + '幸' => ' xìng', + '幝' => ' chǎn', + '幛' => ' zhàng', + '帩' => ' qiào', + '帵' => ' wān', + '帿' => ' hóu', + '帾' => ' dǔ', + '帽' => ' mào', + '帼' => ' guó', + '帻' => ' zé', + '帺' => ' qí', + '帹' => ' shà', + '常' => ' cháng', + '帷' => ' wéi', + '帶' => ' dài', + '帴' => ' sàn', + '幁' => ' xū', + '帳' => ' zhàng', + '帲' => ' píng', + '帱' => ' chóu', + '帰' => ' guī', + '帯' => ' dài', + '帮' => ' bāng', + '席' => ' xí', + '帬' => ' qún', + '師' => ' shī', + '帪' => ' zhēn', + '幀' => ' zhèng', + '幂' => ' mì', + '幚' => ' bāng', + '幏' => ' jià', + '幙' => ' mù', + '幘' => ' zé', + '幗' => ' guó', + '幖' => ' biāo', + '幕' => ' mù', + '幔' => ' màn', + '幓' => ' shān', + '幒' => ' zhōng', + '幑' => ' huī', + '幐' => ' téng', + '幎' => ' mì', + '幃' => ' wéi', + '幍' => ' tāo', + '幌' => ' huǎng', + '幋' => ' pán', + '幊' => ' gōng', + '幉' => ' die', + '幈' => ' píng', + '幇' => ' bāng', + '幆' => ' yì', + '幅' => ' fú', + '幄' => ' wò', + '巁' => ' lì', + '嶿' => ' ru', + '峰' => ' fēng', + '崰' => ' zi', + '崺' => ' yǐ', + '崹' => ' tí', + '崸' => ' yáng', + '崷' => ' qiú', + '崶' => ' fēng', + '崵' => ' yáng', + '崴' => ' wǎi', + '崳' => ' yú', + '崲' => ' huáng', + '崱' => ' zè', + '崯' => ' yín', + '崼' => ' shì', + '崮' => ' gù', + '崭' => ' zhǎn', + '崬' => ' dōng', + '崫' => ' kū', + '崪' => ' zú', + '崩' => ' bēng', + '崨' => ' jié', + '崧' => ' sōng', + '崦' => ' yān', + '崥' => ' pí', + '崻' => ' zhì', + '崽' => ' zǎi', + '崣' => ' wěi', + '嵊' => ' shèng', + '嵔' => ' wěi', + '嵓' => ' yán', + '嵒' => ' yán', + '嵑' => ' kě', + '嵐' => ' lán', + '嵏' => ' zōng', + '嵎' => ' yú', + '嵍' => ' wù', + '嵌' => ' qiàn', + '嵋' => ' méi', + '嵉' => ' tíng', + '崾' => ' yǎo', + '嵈' => ' huàn', + '嵇' => ' jī', + '嵆' => ' jī', + '嵅' => ' hán', + '嵄' => ' měi', + '嵃' => ' yǎn', + '嵂' => ' lǜ', + '嵁' => ' kān', + '嵀' => ' zhù', + '崿' => ' è', + '崤' => ' xiáo', + '崢' => ' zhēng', + '嵖' => ' chá', + '峼' => ' hào', + '崆' => ' kōng', + '崅' => ' què', + '崄' => ' xiǎn', + '崃' => ' lái', + '崂' => ' láo', + '崁' => ' kàn', + '崀' => ' làng', + '峿' => ' yǔ', + '峾' => ' yín', + '峽' => ' xiá', + '峻' => ' jùn', + '崈' => ' chóng', + '峺' => ' gěng', + '峹' => ' tú', + '峸' => ' chéng', + '峷' => ' shēn', + '島' => ' dǎo', + '峵' => ' hóng', + '峴' => ' xiàn', + '峳' => ' yóu', + '峲' => ' lǐ', + '峱' => ' náo', + '崇' => ' chóng', + '崉' => ' tà', + '崡' => ' hán', + '崖' => ' yá', + '崠' => ' dōng', + '崟' => ' yín', + '崞' => ' guō', + '崝' => ' zhēng', + '崜' => ' duō', + '崛' => ' jué', + '崚' => ' léng', + '崙' => ' lún', + '崘' => ' lún', + '崗' => ' gǎng', + '崕' => ' yá', + '崊' => ' lín', + '崔' => ' cuī', + '崓' => ' gù', + '崒' => ' zú', + '崑' => ' kūn', + '崐' => ' kūn', + '崏' => ' mín', + '崎' => ' qí', + '崍' => ' lái', + '崌' => ' jū', + '崋' => ' huà', + '嵕' => ' zōng', + '嵗' => ' suì', + '嶾' => ' yǐn', + '嶙' => ' lín', + '嶣' => ' jiāo', + '嶢' => ' yáo', + '嶡' => ' guì', + '嶠' => ' jiào', + '嶟' => ' zūn', + '嶞' => ' duò', + '嶝' => ' dèng', + '嶜' => ' jīn', + '嶛' => ' liáo', + '嶚' => ' liáo', + '嶘' => ' zhàn', + '嶥' => ' jué', + '嶗' => ' láo', + '嶖' => ' yǎn', + '嶕' => ' jiāo', + '嶔' => ' qīn', + '嶓' => ' bō', + '嶒' => ' céng', + '嶑' => ' xiàng', + '嶐' => ' lóng', + '嶏' => ' pèi', + '嶎' => ' yù', + '嶤' => ' yáo', + '嶦' => ' zhān', + '嶌' => ' dǎo', + '嶳' => ' dì', + '嶽' => ' yuè', + '嶼' => ' yǔ', + '嶻' => ' zá', + '嶺' => ' lǐng', + '嶹' => ' dǎo', + '嶸' => ' róng', + '嶷' => ' yí', + '嶶' => ' wei', + '嶵' => ' zuǐ', + '嶴' => ' ào', + '嶲' => ' xī', + '嶧' => ' yì', + '嶱' => ' kě', + '嶰' => ' xiè', + '嶯' => ' jí', + '嶮' => ' xiǎn', + '嶭' => ' niè', + '嶬' => ' yí', + '嶫' => ' yè', + '嶪' => ' yè', + '嶩' => ' náo', + '嶨' => ' xué', + '嶍' => ' xí', + '嶋' => ' dǎo', + '嵘' => ' róng', + '嵤' => ' róng', + '嵮' => ' diān', + '嵭' => ' bēng', + '嵬' => ' wéi', + '嵫' => ' zī', + '嵪' => ' qiāo', + '嵩' => ' sōng', + '嵨' => ' wù', + '嵧' => ' liú', + '嵦' => ' kǎi', + '嵥' => ' jié', + '嵣' => ' dàng', + '嵰' => ' qiǎn', + '嵢' => ' cāng', + '嵡' => ' wěng', + '嵠' => ' xī', + '嵟' => ' duī', + '嵞' => ' tú', + '嵝' => ' lǒu', + '嵜' => ' ti', + '嵛' => ' yú', + '嵚' => ' qīn', + '嵙' => ' ke', + '嵯' => ' cuó', + '嵱' => ' yǒng', + '嶊' => ' zuǐ', + '嵿' => ' dǐng', + '嶉' => ' cuī', + '嶈' => ' qiāng', + '嶇' => ' qū', + '嶆' => ' cáo', + '嶅' => ' áo', + '嶄' => ' zhǎn', + '嶃' => ' zhǎn', + '嶂' => ' zhàng', + '嶁' => ' lǒu', + '嶀' => ' tū', + '嵾' => ' cēn', + '嵲' => ' niè', + '嵽' => ' dié', + '嵼' => ' chǎn', + '嵻' => ' kāng', + '嵺' => ' liáo', + '嵹' => ' jiàng', + '嵸' => ' zōng', + '嵷' => ' sǒng', + '嵶' => ' ruo', + '嵵' => ' shi', + '嵴' => ' jǐ', + '嵳' => ' cuó', + '子' => ' zi', + '孎' => ' zhú', + '堎' => ' lèng', + '够' => ' gòu', + '天' => ' tiān', + '夨' => ' zè', + '大' => ' dà', + '夦' => ' chěn', + '夥' => ' huǒ', + '夤' => ' yín', + '夣' => ' mèng', + '夢' => ' mèng', + '夡' => ' qì', + '夠' => ' gòu', + '夞' => ' wài', + '夫' => ' fū', + '夝' => ' qíng', + '夜' => ' yè', + '夛' => ' duō', + '多' => ' duō', + '夙' => ' sù', + '夘' => ' mǎo', + '夗' => ' yuàn', + '外' => ' wài', + '夕' => ' xī', + '夔' => ' kuí', + '太' => ' tài', + '夬' => ' guài', + '夒' => ' náo', + '夹' => ' jiā', + '奃' => ' dī', + '奂' => ' huàn', + '奁' => ' lián', + '奀' => ' ēn', + '夿' => ' bā', + '夾' => ' jiā', + '夽' => ' yǔn', + '夼' => ' kuǎng', + '夻' => ' huà', + '夺' => ' duó', + '夸' => ' kuā', + '夭' => ' yāo', + '夷' => ' yí', + '夶' => ' bǐ', + '夵' => ' yǎn', + '头' => ' tóu', + '夳' => ' tài', + '夲' => ' tāo', + '失' => ' shī', + '夰' => ' gǎo', + '夯' => ' hāng', + '央' => ' yāng', + '夓' => ' xià', + '夑' => ' xie', + '奅' => ' pào', + '士' => ' shì', + '壵' => ' zhuàng', + '壴' => ' zhù', + '壳' => ' ké', + '売' => ' mài', + '壱' => ' yī', + '声' => ' shēng', + '壯' => ' zhuàng', + '壮' => ' zhuàng', + '壭' => ' san', + '壬' => ' rén', + '壪' => ' wān', + '壷' => ' hú', + '壩' => ' bà', + '壨' => ' léi', + '壧' => ' yán', + '壦' => ' xūn', + '壥' => ' chan', + '壤' => ' rǎng', + '壣' => ' lín', + '壢' => ' lì', + '壡' => ' ruì', + '壠' => ' lǒng', + '壶' => ' hú', + '壸' => ' kǔn', + '夐' => ' xiòng', + '夅' => ' jiàng', + '夏' => ' xià', + '夎' => ' cuò', + '复' => ' fù', + '夌' => ' líng', + '夋' => ' qūn', + '夊' => ' suī', + '変' => ' biàn', + '夈' => ' zhai', + '备' => ' bèi', + '夆' => ' féng', + '处' => ' chù', + '壹' => ' yī', + '夃' => ' gǔ', + '夂' => ' zhǐ', + '夁' => ' yī', + '夀' => ' shòu', + '壿' => ' zūn', + '壾' => ' mǎng', + '壽' => ' shòu', + '壼' => ' kǔn', + '壻' => ' xù', + '壺' => ' hú', + '奄' => ' yǎn', + '奆' => ' juàn', + '壞' => ' huài', + '妇' => ' fù', + '妑' => ' pā', + '妐' => ' zhōng', + '妏' => ' wèn', + '妎' => ' hài', + '妍' => ' yán', + '妌' => ' jìng', + '妋' => ' fū', + '妊' => ' rèn', + '妉' => ' dān', + '妈' => ' mā', + '妆' => ' zhuāng', + '妓' => ' jì', + '妅' => ' hóng', + '妄' => ' wàng', + '妃' => ' fēi', + '如' => ' rú', + '妁' => ' shuò', + '妀' => ' jǐ', + '奿' => ' fàn', + '奾' => ' xiān', + '好' => ' hǎo', + '奼' => ' chà', + '妒' => ' dù', + '妔' => ' kēng', + '奺' => ' jiǔ', + '妡' => ' xīn', + '妫' => ' guī', + '妪' => ' yù', + '妩' => ' wǔ', + '妨' => ' fáng', + '妧' => ' wàn', + '妦' => ' fēng', + '妥' => ' tuǒ', + '妤' => ' yú', + '妣' => ' bǐ', + '妢' => ' fén', + '妠' => ' nà', + '妕' => ' zhòng', + '妟' => ' yàn', + '妞' => ' niū', + '妝' => ' zhuāng', + '妜' => ' yuè', + '妛' => ' chi', + '妚' => ' fǒu', + '妙' => ' miào', + '妘' => ' yún', + '妗' => ' jìn', + '妖' => ' yāo', + '奻' => ' nuán', + '她' => ' tā', + '奇' => ' qí', + '奓' => ' zhā', + '奝' => ' diāo', + '奜' => ' fěi', + '奛' => ' huǎng', + '奚' => ' xī', + '奙' => ' běn', + '奘' => ' zàng', + '套' => ' tào', + '奖' => ' jiǎng', + '奕' => ' yì', + '奔' => ' bēn', + '奒' => ' kāi', + '奟' => ' bēng', + '契' => ' qì', + '奐' => ' huàn', + '奏' => ' zòu', + '奎' => ' kuí', + '奍' => ' yang', + '奌' => ' diǎn', + '奋' => ' fèn', + '奊' => ' xié', + '奉' => ' fèng', + '奈' => ' nài', + '奞' => ' xùn', + '奠' => ' diàn', + '奸' => ' jiān', + '奭' => ' shì', + '奷' => ' qiān', + '奶' => ' nǎi', + '奵' => ' dǐng', + '奴' => ' nú', + '女' => ' nǚ', + '奲' => ' duǒ', + '奱' => ' luán', + '奰' => ' bì', + '奯' => ' huò', + '奮' => ' fèn', + '奬' => ' jiǎng', + '奡' => ' ào', + '奫' => ' yūn', + '奪' => ' duó', + '奩' => ' lián', + '奨' => ' jiǎng', + '奧' => ' ào', + '奦' => ' wù', + '奥' => ' ào', + '奤' => ' hǎ', + '奣' => ' wěng', + '奢' => ' shē', + '壟' => ' lǒng', + '壝' => ' wěi', + '妭' => ' bá', + '塎' => ' yǒng', + '塘' => ' táng', + '塗' => ' tú', + '塖' => ' chéng', + '塕' => ' wěng', + '塔' => ' tǎ', + '塓' => ' mì', + '塒' => ' shí', + '塑' => ' sù', + '塐' => ' sù', + '塏' => ' kǎi', + '塍' => ' chéng', + '塚' => ' zhǒng', + '塌' => ' tā', + '塋' => ' yíng', + '塊' => ' kuài', + '塉' => ' jí', + '塈' => ' jì', + '塇' => ' xuān', + '塆' => ' wān', + '塅' => ' duàn', + '塄' => ' léng', + '塃' => ' huāng', + '塙' => ' què', + '塛' => ' lì', + '塁' => ' lei', + '塨' => ' gōng', + '塲' => ' cháng', + '塱' => ' lǎng', + '塰' => ' hai', + '塯' => ' liù', + '塮' => ' xiè', + '塭' => ' wēn', + '塬' => ' yuán', + '填' => ' tián', + '塪' => ' kǎn', + '塩' => ' yán', + '塧' => ' ài', + '塜' => ' zhǒng', + '塦' => ' zhèn', + '塥' => ' gé', + '塤' => ' xūn', + '塣' => ' zhèng', + '塢' => ' wù', + '塡' => ' tián', + '塠' => ' duī', + '塟' => ' zàng', + '塞' => ' sāi', + '塝' => ' bàng', + '塂' => ' xiàng', + '塀' => ' ping', + '塴' => ' bèng', + '堚' => ' hún', + '堤' => ' dī', + '堣' => ' yú', + '堢' => ' bǎo', + '堡' => ' bǎo', + '堠' => ' hòu', + '堟' => ' zhuàn', + '堞' => ' dié', + '堝' => ' guō', + '堜' => ' liàn', + '堛' => ' bì', + '堙' => ' yīn', + '堦' => ' jiē', + '堘' => ' chéng', + '堗' => ' tū', + '堖' => ' nǎo', + '堕' => ' duò', + '堔' => ' shen', + '堓' => ' àn', + '堒' => ' kun', + '堑' => ' qiàn', + '堐' => ' yá', + '堏' => ' fang', + '堥' => ' máo', + '堧' => ' ruán', + '堿' => ' jiǎn', + '場' => ' chǎng', + '堾' => ' chūn', + '堽' => ' gāng', + '堼' => ' hèng', + '堻' => ' jīn', + '堺' => ' jiè', + '堹' => ' zhòng', + '堸' => ' féng', + '堷' => ' yìn', + '堶' => ' tuó', + '堵' => ' dǔ', + '堳' => ' méi', + '堨' => ' yè', + '堲' => ' cí', + '報' => ' bào', + '堰' => ' yàn', + '堯' => ' yáo', + '堮' => ' è', + '堭' => ' huáng', + '堬' => ' yú', + '堫' => ' zōng', + '堪' => ' kān', + '堩' => ' gèng', + '塳' => ' péng', + '塵' => ' chén', + '壜' => ' tán', + '墷' => ' yè', + '壁' => ' bì', + '壀' => ' pí', + '墿' => ' yì', + '墾' => ' kěn', + '墽' => ' qiāo', + '墼' => ' jī', + '墻' => ' qiáng', + '墺' => ' ào', + '墹' => ' jian', + '墸' => ' zhu', + '墶' => ' da', + '壃' => ' jiāng', + '墵' => ' tán', + '墴' => ' huáng', + '墳' => ' fén', + '墲' => ' mú', + '墱' => ' dèng', + '墰' => ' tán', + '墯' => ' duò', + '墮' => ' duò', + '墭' => ' shèng', + '墬' => ' dì', + '壂' => ' diàn', + '壄' => ' yě', + '墪' => ' dūn', + '壑' => ' hè', + '壛' => ' yán', + '壚' => ' lú', + '壙' => ' kuàng', + '壘' => ' lěi', + '壗' => ' jin', + '壖' => ' ruán', + '壕' => ' háo', + '壔' => ' dǎo', + '壓' => ' yā', + '壒' => ' ài', + '壐' => ' xǐ', + '壅' => ' yōng', + '壏' => ' xiàn', + '壎' => ' xūn', + '壍' => ' qiàn', + '壌' => ' rǎng', + '壋' => ' dàng', + '壊' => ' huài', + '壉' => ' jù', + '壈' => ' lǎn', + '壇' => ' tán', + '壆' => ' xué', + '墫' => ' zūn', + '墩' => ' dūn', + '塶' => ' lù', + '墂' => ' biāo', + '墌' => ' zhí', + '墋' => ' chěn', + '墊' => ' diàn', + '墉' => ' yōng', + '墈' => ' kàn', + '墇' => ' zhāng', + '墆' => ' zhì', + '墅' => ' shù', + '墄' => ' cè', + '境' => ' jìng', + '墁' => ' màn', + '墎' => ' guō', + '墀' => ' chí', + '塿' => ' lǒu', + '塾' => ' shú', + '塽' => ' shuǎng', + '塼' => ' zhuān', + '塻' => ' mò', + '塺' => ' méi', + '塹' => ' qiàn', + '塸' => ' ōu', + '塷' => ' lǔ', + '墍' => ' xì', + '墏' => ' qiǎng', + '墨' => ' mò', + '墝' => ' qiāo', + '墧' => ' què', + '墦' => ' fán', + '墥' => ' dǒng', + '墤' => ' kuài', + '墣' => ' pú', + '墢' => ' bá', + '墡' => ' shàn', + '墠' => ' shàn', + '墟' => ' xū', + '增' => ' zēng', + '墜' => ' zhuì', + '墐' => ' jìn', + '墛' => ' wei', + '墚' => ' liáng', + '墙' => ' qiáng', + '墘' => ' qián', + '増' => ' zēng', + '墖' => ' tǎ', + '墕' => ' yàn', + '墔' => ' cuī', + '墓' => ' mù', + '墒' => ' shāng', + '墑' => ' dì', + '妬' => ' dù', + '妮' => ' nī', + '孍' => ' yán', + '嫀' => ' qín', + '嫊' => ' sù', + '嫉' => ' jí', + '嫈' => ' yīng', + '嫇' => ' míng', + '嫆' => ' róng', + '嫅' => ' jiē', + '嫄' => ' yuán', + '嫃' => ' zhēn', + '嫂' => ' sǎo', + '嫁' => ' jià', + '媿' => ' kuì', + '嫌' => ' xián', + '媾' => ' gòu', + '媽' => ' mā', + '媼' => ' ǎo', + '媻' => ' pán', + '媺' => ' měi', + '媹' => ' liú', + '媸' => ' chī', + '媷' => ' rù', + '媶' => ' róng', + '媵' => ' yìng', + '嫋' => ' niǎo', + '嫍' => ' tāo', + '媳' => ' xí', + '嫚' => ' mān', + '嫤' => ' jǐn', + '嫣' => ' yān', + '嫢' => ' guī', + '嫡' => ' dí', + '嫠' => ' lí', + '嫟' => ' nì', + '嫞' => ' yōng', + '嫝' => ' kāng', + '嫜' => ' zhāng', + '嫛' => ' yī', + '嫙' => ' xuán', + '嫎' => ' páng', + '嫘' => ' léi', + '嫗' => ' yù', + '嫖' => ' piáo', + '嫕' => ' yì', + '嫔' => ' pín', + '嫓' => ' pì', + '嫒' => ' ài', + '嫑' => ' báo', + '嫐' => ' nǎo', + '嫏' => ' láng', + '媴' => ' yuán', + '媲' => ' pì', + '嫦' => ' cháng', + '媌' => ' miáo', + '媖' => ' yīng', + '媕' => ' ān', + '媔' => ' mián', + '媓' => ' huáng', + '媒' => ' méi', + '媑' => ' zhòng', + '媐' => ' yí', + '媏' => ' duān', + '媎' => ' jiě', + '媍' => ' fù', + '媋' => ' chūn', + '媘' => ' jiē', + '媊' => ' qián', + '媉' => ' wò', + '媈' => ' huī', + '媇' => ' qīn', + '媆' => ' ruǎn', + '媅' => ' dān', + '媄' => ' měi', + '媃' => ' róu', + '媂' => ' dì', + '媁' => ' wéi', + '媗' => ' xuān', + '媙' => ' wēi', + '媱' => ' yáo', + '媦' => ' wèi', + '媰' => ' chú', + '媯' => ' guī', + '媮' => ' tōu', + '媭' => ' xū', + '媬' => ' bǎo', + '媫' => ' qie', + '媪' => ' ǎo', + '媩' => ' hú', + '媨' => ' jiù', + '媧' => ' wā', + '媥' => ' piān', + '媚' => ' mèi', + '媤' => ' sī', + '媣' => ' rǎn', + '媢' => ' mào', + '媡' => ' liàn', + '媠' => ' tuǒ', + '媟' => ' xiè', + '媞' => ' shì', + '媝' => ' qiū', + '媜' => ' zhēng', + '媛' => ' yuàn', + '嫥' => ' zhuān', + '嫧' => ' zé', + '婿' => ' xù', + '嬨' => ' cí', + '嬲' => ' niǎo', + '嬱' => ' cán', + '嬰' => ' yīng', + '嬯' => ' tái', + '嬮' => ' yān', + '嬭' => ' nǎi', + '嬬' => ' rú', + '嬫' => ' róng', + '嬪' => ' pín', + '嬩' => ' yú', + '嬧' => ' jìn', + '嬴' => ' yíng', + '嬦' => ' chóu', + '嬥' => ' tiǎo', + '嬤' => ' mā', + '嬣' => ' níng', + '嬢' => ' niáng', + '嬡' => ' ài', + '嬠' => ' cān', + '嬟' => ' yì', + '嬞' => ' dǒng', + '嬝' => ' niǎo', + '嬳' => ' yuè', + '嬵' => ' mián', + '嬛' => ' huán', + '孂' => ' jiǎo', + '孌' => ' luán', + '孋' => ' lí', + '孊' => ' mǐ', + '孉' => ' quán', + '孈' => ' huì', + '孇' => ' shuāng', + '孆' => ' yīng', + '孅' => ' qiān', + '孄' => ' lǎn', + '孃' => ' niáng', + '孁' => ' líng', + '嬶' => ' bi', + '孀' => ' shuāng', + '嬿' => ' yàn', + '嬾' => ' lǎn', + '嬽' => ' yuān', + '嬼' => ' liǔ', + '嬻' => ' dú', + '嬺' => ' nì', + '嬹' => ' xìng', + '嬸' => ' shěn', + '嬷' => ' mā', + '嬜' => ' xīn', + '嬚' => ' liǎn', + '嫨' => ' hān', + '嫴' => ' gū', + '嫾' => ' lián', + '嫽' => ' liáo', + '嫼' => ' mò', + '嫻' => ' xián', + '嫺' => ' xián', + '嫹' => ' máo', + '嫸' => ' zhǎn', + '嫷' => ' tuǒ', + '嫶' => ' qiáo', + '嫵' => ' wǔ', + '嫳' => ' piè', + '嬀' => ' guī', + '嫲' => ' ma', + '嫱' => ' qiáng', + '嫰' => ' nèn', + '嫯' => ' ào', + '嫮' => ' hù', + '嫭' => ' hù', + '嫬' => ' zhē', + '嫫' => ' mó', + '嫪' => ' lào', + '嫩' => ' nèn', + '嫿' => ' huà', + '嬁' => ' dēng', + '嬙' => ' qiáng', + '嬎' => ' fàn', + '嬘' => ' suì', + '嬗' => ' shàn', + '嬖' => ' bì', + '嬕' => ' shì', + '嬔' => ' fù', + '嬓' => ' jiào', + '嬒' => ' huì', + '嬑' => ' yì', + '嬐' => ' xiān', + '嬏' => ' fān', + '嬍' => ' měi', + '嬂' => ' zhí', + '嬌' => ' jiāo', + '嬋' => ' chán', + '嬊' => ' yàn', + '嬉' => ' xī', + '嬈' => ' ráo', + '嬇' => ' kuì', + '嬆' => ' xī', + '嬅' => ' huà', + '嬄' => ' yī', + '嬃' => ' xū', + '媀' => ' yù', + '婾' => ' tōu', + '妯' => ' zhóu', + '姯' => ' guāng', + '姹' => ' chà', + '姸' => ' yán', + '姷' => ' yòu', + '姶' => ' è', + '姵' => ' pèi', + '姴' => ' liè', + '姳' => ' mǐng', + '姲' => ' yàn', + '姱' => ' kuā', + '姰' => ' jūn', + '姮' => ' héng', + '姻' => ' yīn', + '姭' => ' xiàn', + '姬' => ' jī', + '姫' => ' jī', + '姪' => ' zhí', + '姩' => ' niàn', + '姨' => ' yí', + '姧' => ' jiān', + '姦' => ' jiān', + '姥' => ' lǎo', + '姤' => ' gòu', + '姺' => ' shēn', + '姼' => ' shí', + '姢' => ' juān', + '娉' => ' pīng', + '娓' => ' wěi', + '娒' => ' méi', + '娑' => ' suō', + '娐' => ' fū', + '娏' => ' máng', + '娎' => ' xiè', + '娍' => ' chéng', + '娌' => ' lǐ', + '娋' => ' shào', + '娊' => ' xiàn', + '娈' => ' luán', + '姽' => ' guǐ', + '娇' => ' jiāo', + '娆' => ' ráo', + '娅' => ' yà', + '娄' => ' lóu', + '娃' => ' wá', + '娂' => ' hóng', + '威' => ' wēi', + '娀' => ' sōng', + '姿' => ' zī', + '姾' => ' quán', + '姣' => ' jiāo', + '姡' => ' huá', + '娕' => ' chuò', + '妻' => ' qī', + '姅' => ' bàn', + '姄' => ' mín', + '姃' => ' zhēng', + '姂' => ' fá', + '姁' => ' xǔ', + '姀' => ' hé', + '妿' => ' ē', + '妾' => ' qiè', + '妽' => ' shēn', + '妼' => ' bì', + '妺' => ' mò', + '姇' => ' fū', + '妹' => ' mèi', + '妸' => ' ē', + '妷' => ' zhí', + '妶' => ' xián', + '妵' => ' tǒu', + '妴' => ' yuàn', + '妳' => ' nǎi', + '妲' => ' dá', + '妱' => ' zhāo', + '妰' => ' zhuó', + '姆' => ' mǔ', + '姈' => ' líng', + '姠' => ' xiàng', + '姕' => ' zī', + '姟' => ' gāi', + '姞' => ' jí', + '姝' => ' shū', + '姜' => ' jiāng', + '姛' => ' dòng', + '姚' => ' yáo', + '姙' => ' rèn', + '姘' => ' pīn', + '姗' => ' shān', + '姖' => ' jù', + '委' => ' wěi', + '姉' => ' zǐ', + '姓' => ' xìng', + '姒' => ' sì', + '姑' => ' gū', + '姐' => ' jiě', + '姏' => ' mán', + '姎' => ' yāng', + '姍' => ' shān', + '姌' => ' rǎn', + '始' => ' shǐ', + '姊' => ' zǐ', + '娔' => ' kè', + '娖' => ' chuò', + '婽' => ' jiǎ', + '婘' => ' quán', + '婢' => ' bì', + '婡' => ' lái', + '婠' => ' wān', + '婟' => ' hù', + '婞' => ' xìng', + '婝' => ' diàn', + '婜' => ' qiān', + '婛' => ' jīng', + '婚' => ' hūn', + '婙' => ' jìng', + '婗' => ' ní', + '婤' => ' chōu', + '婖' => ' tiān', + '婕' => ' jié', + '婔' => ' fei', + '婓' => ' fēi', + '婒' => ' tán', + '婑' => ' ruí', + '婐' => ' wǒ', + '婏' => ' fàn', + '婎' => ' huī', + '婍' => ' qǐ', + '婣' => ' yīn', + '婥' => ' nào', + '婋' => ' xiāo', + '婲' => ' hua', + '婼' => ' chuò', + '婻' => ' nàn', + '婺' => ' wù', + '婹' => ' yǎo', + '婸' => ' dàng', + '婷' => ' tíng', + '婶' => ' shěn', + '婵' => ' chán', + '婴' => ' yīng', + '婳' => ' huà', + '婱' => ' xián', + '婦' => ' fù', + '婰' => ' diǎn', + '婯' => ' lì', + '婮' => ' jū', + '婭' => ' yà', + '婬' => ' yín', + '婫' => ' kūn', + '婪' => ' lán', + '婩' => ' àn', + '婨' => ' lún', + '婧' => ' jìng', + '婌' => ' shú', + '婊' => ' biǎo', + '娗' => ' tǐng', + '娣' => ' dì', + '娭' => ' āi', + '娬' => ' wǔ', + '娫' => ' yán', + '娪' => ' wú', + '娩' => ' miǎn', + '娨' => ' xiàn', + '娧' => ' tuì', + '娦' => ' pín', + '娥' => ' é', + '娤' => ' zhuāng', + '娢' => ' hán', + '娯' => ' yú', + '娡' => ' zhì', + '娠' => ' shēn', + '娟' => ' juān', + '娞' => ' něi', + '娝' => ' pōu', + '娜' => ' nà', + '娛' => ' yú', + '娚' => ' nán', + '娙' => ' xíng', + '娘' => ' niáng', + '娮' => ' yán', + '娰' => ' sì', + '婉' => ' wǎn', + '娾' => ' ǎi', + '婈' => ' líng', + '婇' => ' cāi', + '婆' => ' pó', + '婅' => ' jú', + '婄' => ' pǒu', + '婃' => ' cóng', + '婂' => ' mián', + '婁' => ' lóu', + '婀' => ' ē', + '娿' => ' ē', + '娽' => ' lù', + '娱' => ' yú', + '娼' => ' chāng', + '娻' => ' dōng', + '娺' => ' zhuó', + '娹' => ' xián', + '娸' => ' qī', + '娷' => ' zhuì', + '娶' => ' qǔ', + '娵' => ' jū', + '娴' => ' xián', + '娳' => ' lì', + '娲' => ' wā', + '皚' => ' ái', +); \ No newline at end of file diff --git a/vendor/overtrue/pinyin/src/DictLoaderInterface.php b/vendor/overtrue/pinyin/src/DictLoaderInterface.php new file mode 100644 index 0000000..8c5f55b --- /dev/null +++ b/vendor/overtrue/pinyin/src/DictLoaderInterface.php @@ -0,0 +1,42 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Overtrue\Pinyin; + +use Closure; + +/** + * Dict loader interface. + */ +interface DictLoaderInterface +{ + /** + * Load dict. + * + *
    +     * [
    +     *     '响应时间' => "[\t]xiǎng[\t]yìng[\t]shí[\t]jiān",
    +     *     '长篇连载' => '[\t]cháng[\t]piān[\t]lián[\t]zǎi',
    +     *     //...
    +     * ]
    +     * 
    + * + * @param Closure $callback + */ + public function map(Closure $callback); + + /** + * Load surname dict. + * + * @param Closure $callback + */ + public function mapSurname(Closure $callback); +} diff --git a/vendor/overtrue/pinyin/src/FileDictLoader.php b/vendor/overtrue/pinyin/src/FileDictLoader.php new file mode 100644 index 0000000..e9895f9 --- /dev/null +++ b/vendor/overtrue/pinyin/src/FileDictLoader.php @@ -0,0 +1,76 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Overtrue\Pinyin; + +use Closure; + +/** + * Dict File loader. + */ +class FileDictLoader implements DictLoaderInterface +{ + /** + * Words segment name. + * + * @var string + */ + protected $segmentName = 'words_%s'; + + /** + * Dict path. + * + * @var string + */ + protected $path; + + /** + * Constructor. + * + * @param string $path + */ + public function __construct($path) + { + $this->path = $path; + } + + /** + * Load dict. + * + * @param Closure $callback + */ + public function map(Closure $callback) + { + for ($i = 0; $i < 100; ++$i) { + $segment = $this->path.'/'.sprintf($this->segmentName, $i); + + if (file_exists($segment)) { + $dictionary = (array) include $segment; + $callback($dictionary); + } + } + } + + /** + * Load surname dict. + * + * @param Closure $callback + */ + public function mapSurname(Closure $callback) + { + $surnames = $this->path.'/surnames'; + + if (file_exists($surnames)) { + $dictionary = (array) include $surnames; + $callback($dictionary); + } + } +} diff --git a/vendor/overtrue/pinyin/src/GeneratorFileDictLoader.php b/vendor/overtrue/pinyin/src/GeneratorFileDictLoader.php new file mode 100644 index 0000000..87de561 --- /dev/null +++ b/vendor/overtrue/pinyin/src/GeneratorFileDictLoader.php @@ -0,0 +1,142 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Overtrue\Pinyin; + +use Closure; +use SplFileObject; +use Generator; + +/** + * Generator syntax(yield) Dict File loader. + */ +class GeneratorFileDictLoader implements DictLoaderInterface +{ + /** + * Data directory. + * + * @var string + */ + protected $path; + + /** + * Words segment name. + * + * @var string + */ + protected $segmentName = 'words_%s'; + + /** + * SplFileObjects. + * + * @var array + */ + protected static $handles = []; + + /** + * surnames. + * + * @var SplFileObject + */ + protected static $surnamesHandle; + + /** + * Constructor. + * + * @param string $path + */ + public function __construct($path) + { + $this->path = $path; + + for ($i = 0; $i < 100; ++$i) { + $segment = $this->path.'/'.sprintf($this->segmentName, $i); + + if (file_exists($segment) && is_file($segment)) { + array_push(static::$handles, $this->openFile($segment)); + } + } + } + + /** + * Construct a new file object. + * + * @param string $filename file path + * + * @return SplFileObject + */ + protected function openFile($filename, $mode = 'r') + { + return new SplFileObject($filename, $mode); + } + + /** + * get Generator syntax. + * + * @param array $handles SplFileObjects + */ + protected function getGenerator(array $handles) + { + foreach ($handles as $handle) { + $handle->seek(0); + while ($handle->eof() === false) { + $string = str_replace(['\'', ' ', PHP_EOL, ','], '', $handle->fgets()); + + if (strpos($string, '=>') === false) { + continue; + } + + list($string, $pinyin) = explode('=>', $string); + + yield $string => $pinyin; + } + } + } + + /** + * Traverse the stream. + * + * @param Generator $generator + * @param Closure $callback + * + * @author Seven Du + */ + protected function traversing(Generator $generator, Closure $callback) + { + foreach ($generator as $string => $pinyin) { + $callback([$string => $pinyin]); + } + } + + /** + * Load dict. + * + * @param Closure $callback + */ + public function map(Closure $callback) + { + $this->traversing($this->getGenerator(static::$handles), $callback); + } + + /** + * Load surname dict. + * + * @param Closure $callback + */ + public function mapSurname(Closure $callback) + { + if (!static::$surnamesHandle instanceof SplFileObject) { + static::$surnamesHandle = $this->openFile($this->path.'/surnames'); + } + + $this->traversing($this->getGenerator([static::$surnamesHandle]), $callback); + } +} diff --git a/vendor/overtrue/pinyin/src/MemoryFileDictLoader.php b/vendor/overtrue/pinyin/src/MemoryFileDictLoader.php new file mode 100644 index 0000000..e540fb8 --- /dev/null +++ b/vendor/overtrue/pinyin/src/MemoryFileDictLoader.php @@ -0,0 +1,96 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Overtrue\Pinyin; + +use Closure; + +/** + * Memory Dict File loader. + */ +class MemoryFileDictLoader implements DictLoaderInterface +{ + /** + * Data directory. + * + * @var string + */ + protected $path; + + /** + * Words segment name. + * + * @var string + */ + protected $segmentName = 'words_%s'; + + /** + * Segment files. + * + * @var array + */ + protected $segments = array(); + + /** + * Surname cache. + * + * @var array + */ + protected $surnames = array(); + + /** + * Constructor. + * + * @param string $path + */ + public function __construct($path) + { + $this->path = $path; + + for ($i = 0; $i < 100; ++$i) { + $segment = $path.'/'.sprintf($this->segmentName, $i); + + if (file_exists($segment)) { + $this->segments[] = (array) include $segment; + } + } + } + + /** + * Load dict. + * + * @param Closure $callback + */ + public function map(Closure $callback) + { + foreach ($this->segments as $dictionary) { + $callback($dictionary); + } + } + + /** + * Load surname dict. + * + * @param Closure $callback + */ + public function mapSurname(Closure $callback) + { + if (empty($this->surnames)) { + $surnames = $this->path.'/surnames'; + + if (file_exists($surnames)) { + $this->surnames = (array) include $surnames; + } + } + + $callback($this->surnames); + } +} diff --git a/vendor/overtrue/pinyin/src/Pinyin.php b/vendor/overtrue/pinyin/src/Pinyin.php new file mode 100644 index 0000000..b8b96ba --- /dev/null +++ b/vendor/overtrue/pinyin/src/Pinyin.php @@ -0,0 +1,309 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Overtrue\Pinyin; + +use InvalidArgumentException; + +/* + * Chinese to pinyin translator. + * + * @author overtrue + * @copyright 2015 overtrue + * + * @link https://github.com/overtrue/pinyin + * @link http://overtrue.me + */ + +define('PINYIN_NONE', 'none'); +define('PINYIN_ASCII', 'ascii'); +define('PINYIN_UNICODE', 'unicode'); + +class Pinyin +{ + const NONE = 'none'; + const ASCII = 'ascii'; + const UNICODE = 'unicode'; + + /** + * Dict loader. + * + * @var \Overtrue\Pinyin\DictLoaderInterface + */ + protected $loader; + + /** + * Punctuations map. + * + * @var array + */ + protected $punctuations = array( + ',' => ',', + '。' => '.', + '!' => '!', + '?' => '?', + ':' => ':', + '“' => '"', + '”' => '"', + '‘' => "'", + '’' => "'", + ); + + /** + * Constructor. + * + * @param string $loaderName + */ + public function __construct($loaderName = null) + { + $this->loader = $loaderName ?: 'Overtrue\\Pinyin\\FileDictLoader'; + } + + /** + * Convert string to pinyin. + * + * @param string $string + * @param string $option + * + * @return array + */ + public function convert($string, $option = self::NONE) + { + $pinyin = $this->romanize($string); + + return $this->splitWords($pinyin, $option); + } + + /** + * Convert string (person name) to pinyin. + * + * @param string $stringName + * @param string $option + * + * @return array + */ + public function name($stringName, $option = self::NONE) + { + $pinyin = $this->romanize($stringName, true); + + return $this->splitWords($pinyin, $option); + } + + /** + * Return a pinyin permalink from string. + * + * @param string $string + * @param string $delimiter + * + * @return string + */ + public function permalink($string, $delimiter = '-') + { + if (!in_array($delimiter, array('_', '-', '.', ''), true)) { + throw new InvalidArgumentException("Delimiter must be one of: '_', '-', '', '.'."); + } + + return implode($delimiter, $this->convert($string, false)); + } + + /** + * Return first letters. + * + * @param string $string + * @param string $delimiter + * + * @return string + */ + public function abbr($string, $delimiter = '') + { + return implode($delimiter, array_map(function ($pinyin) { + return $pinyin[0]; + }, $this->convert($string, false))); + } + + /** + * Chinese phrase to pinyin. + * + * @param string $string + * @param string $delimiter + * @param string $option + * + * @return string + */ + public function phrase($string, $delimiter = ' ', $option = self::NONE) + { + return implode($delimiter, $this->convert($string, $option)); + } + + /** + * Chinese to pinyin sentense. + * + * @param string $sentence + * @param bool $withTone + * + * @return string + */ + public function sentence($sentence, $withTone = false) + { + $marks = array_keys($this->punctuations); + $punctuationsRegex = preg_quote(implode(array_merge($marks, $this->punctuations)), '/'); + $regex = '/[^üāēīōūǖáéíóúǘǎěǐǒǔǚàèìòùǜa-z0-9'.$punctuationsRegex.'\s_]+/iu'; + + $pinyin = preg_replace($regex, '', $this->romanize($sentence)); + + $punctuations = array_merge($this->punctuations, array("\t" => ' ', ' ' => ' ')); + $pinyin = trim(str_replace(array_keys($punctuations), $punctuations, $pinyin)); + + return $withTone ? $pinyin : $this->format($pinyin, false); + } + + /** + * Loader setter. + * + * @param \Overtrue\Pinyin\DictLoaderInterface $loader + * + * @return $this + */ + public function setLoader(DictLoaderInterface $loader) + { + $this->loader = $loader; + + return $this; + } + + /** + * Return dict loader,. + * + * @return \Overtrue\Pinyin\DictLoaderInterface + */ + public function getLoader() + { + if (!($this->loader instanceof DictLoaderInterface)) { + $dataDir = dirname(__DIR__).'/data/'; + + $loaderName = $this->loader; + $this->loader = new $loaderName($dataDir); + } + + return $this->loader; + } + + /** + * Preprocess. + * + * @param string $string + * + * @return string + */ + protected function prepare($string) + { + $string = preg_replace_callback('~[a-z0-9_-]+~i', function ($matches) { + return "\t".$matches[0]; + }, $string); + + return preg_replace("~[^\p{Han}\p{P}\p{Z}\p{M}\p{N}\p{L}\t]~u", '', $string); + } + + /** + * Convert Chinese to pinyin. + * + * @param string $string + * @param bool $isName + * + * @return string + */ + protected function romanize($string, $isName = false) + { + $string = $this->prepare($string); + + $dictLoader = $this->getLoader(); + + if ($isName) { + $string = $this->convertSurname($string, $dictLoader); + } + + $dictLoader->map(function ($dictionary) use (&$string) { + $string = strtr($string, $dictionary); + }); + + return $string; + } + + /** + * Convert Chinese Surname to pinyin. + * + * @param string $string + * @param \Overtrue\Pinyin\DictLoaderInterface $dictLoader + * + * @return string + */ + protected function convertSurname($string, $dictLoader) + { + $dictLoader->mapSurname(function ($dictionary) use (&$string) { + foreach ($dictionary as $surname => $pinyin) { + if (strpos($string, $surname) === 0) { + $string = $pinyin.mb_substr($string, mb_strlen($surname, 'UTF-8'), mb_strlen($string, 'UTF-8') - 1, 'UTF-8'); + break; + } + } + }); + + return $string; + } + + /** + * Split pinyin string to words. + * + * @param string $pinyin + * @param string $option + * + * @return array + */ + public function splitWords($pinyin, $option) + { + $split = array_filter(preg_split('/[^üāēīōūǖáéíóúǘǎěǐǒǔǚàèìòùǜa-z\d]+/iu', $pinyin)); + + if ($option !== self::UNICODE) { + foreach ($split as $index => $pinyin) { + $split[$index] = $this->format($pinyin, $option === self::ASCII); + } + } + + return array_values($split); + } + + /** + * Format. + * + * @param string $pinyin + * @param bool $tone + * + * @return string + */ + protected function format($pinyin, $tone = false) + { + $replacements = array( + 'üē' => array('ue', 1), 'üé' => array('ue', 2), 'üě' => array('ue', 3), 'üè' => array('ue', 4), + 'ā' => array('a', 1), 'ē' => array('e', 1), 'ī' => array('i', 1), 'ō' => array('o', 1), 'ū' => array('u', 1), 'ǖ' => array('v', 1), + 'á' => array('a', 2), 'é' => array('e', 2), 'í' => array('i', 2), 'ó' => array('o', 2), 'ú' => array('u', 2), 'ǘ' => array('v', 2), + 'ǎ' => array('a', 3), 'ě' => array('e', 3), 'ǐ' => array('i', 3), 'ǒ' => array('o', 3), 'ǔ' => array('u', 3), 'ǚ' => array('v', 3), + 'à' => array('a', 4), 'è' => array('e', 4), 'ì' => array('i', 4), 'ò' => array('o', 4), 'ù' => array('u', 4), 'ǜ' => array('v', 4), + ); + + foreach ($replacements as $unicde => $replacement) { + if (false !== strpos($pinyin, $unicde)) { + $pinyin = str_replace($unicde, $replacement[0], $pinyin).($tone ? $replacement[1] : ''); + } + } + + return $pinyin; + } +} diff --git a/vendor/overtrue/socialite/.github/FUNDING.yml b/vendor/overtrue/socialite/.github/FUNDING.yml new file mode 100644 index 0000000..a3be7fa --- /dev/null +++ b/vendor/overtrue/socialite/.github/FUNDING.yml @@ -0,0 +1,9 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: overtrue +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +custom: # Replace with a single custom sponsorship URL diff --git a/vendor/overtrue/socialite/.gitignore b/vendor/overtrue/socialite/.gitignore new file mode 100644 index 0000000..d6eb268 --- /dev/null +++ b/vendor/overtrue/socialite/.gitignore @@ -0,0 +1,9 @@ +/vendor +composer.phar +composer.lock +.DS_Store +/.idea +Thumbs.db +/*.php +sftp-config.json +.php_cs.cache \ No newline at end of file diff --git a/vendor/overtrue/socialite/.php_cs b/vendor/overtrue/socialite/.php_cs new file mode 100644 index 0000000..bda3644 --- /dev/null +++ b/vendor/overtrue/socialite/.php_cs @@ -0,0 +1,28 @@ + + +This source file is subject to the MIT license that is bundled +with this source code in the file LICENSE. +EOF; + +return PhpCsFixer\Config::create() + ->setRiskyAllowed(true) + ->setRules(array( + '@Symfony' => true, + 'header_comment' => array('header' => $header), + 'array_syntax' => array('syntax' => 'short'), + 'ordered_imports' => true, + 'no_useless_else' => true, + 'no_useless_return' => true, + 'php_unit_construct' => true, + 'php_unit_strict' => true, + )) + ->setFinder( + PhpCsFixer\Finder::create() + ->exclude('vendor') + ->in(__DIR__) + ) +; \ No newline at end of file diff --git a/vendor/overtrue/socialite/.travis.yml b/vendor/overtrue/socialite/.travis.yml new file mode 100644 index 0000000..39912f9 --- /dev/null +++ b/vendor/overtrue/socialite/.travis.yml @@ -0,0 +1,13 @@ +language: php + +php: + - 7.0 + - 7.1 + - 7.2 + +sudo: false +dist: trusty + +install: travis_retry composer install --no-interaction --prefer-source + +script: vendor/bin/phpunit --verbose diff --git a/vendor/overtrue/socialite/LICENSE.txt b/vendor/overtrue/socialite/LICENSE.txt new file mode 100644 index 0000000..c5fe984 --- /dev/null +++ b/vendor/overtrue/socialite/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) overtrue + +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/overtrue/socialite/README.md b/vendor/overtrue/socialite/README.md new file mode 100644 index 0000000..1309f31 --- /dev/null +++ b/vendor/overtrue/socialite/README.md @@ -0,0 +1,267 @@ +

    Socialite

    +

    +Build Status +Latest Stable Version +Latest Unstable Version +Build Status +Scrutinizer Code Quality +Code Coverage +Total Downloads +License +

    + + +

    Socialite is an OAuth2 Authentication tool. It is inspired by laravel/socialite, You can easily use it in any PHP project.

    + +# Requirement + +``` +PHP >= 5.6 +``` +# Installation + +```shell +$ composer require "overtrue/socialite" -vvv +``` + +# Usage + +For Laravel 5: [overtrue/laravel-socialite](https://github.com/overtrue/laravel-socialite) + +`authorize.php`: + +```php + [ + 'client_id' => 'your-app-id', + 'client_secret' => 'your-app-secret', + 'redirect' => 'http://localhost/socialite/callback.php', + ], +]; + +$socialite = new SocialiteManager($config); + +$response = $socialite->driver('github')->redirect(); + +echo $response;// or $response->send(); +``` + +`callback.php`: + +```php + [ + 'client_id' => 'your-app-id', + 'client_secret' => 'your-app-secret', + 'redirect' => 'http://localhost/socialite/callback.php', + ], +]; + +$socialite = new SocialiteManager($config); + +$user = $socialite->driver('github')->user(); + +$user->getId(); // 1472352 +$user->getNickname(); // "overtrue" +$user->getUsername(); // "overtrue" +$user->getName(); // "安正超" +$user->getEmail(); // "anzhengchao@gmail.com" +$user->getProviderName(); // GitHub +... +``` + +### Configuration + +Now we support the following sites: + +`facebook`, `github`, `google`, `linkedin`, `outlook`, `weibo`, `taobao`, `qq`, `wechat`, `douyin`, `baidu`, `feishu`, and `douban`. + +Each driver uses the same configuration keys: `client_id`, `client_secret`, `redirect`. + +Example: +``` +... + 'weibo' => [ + 'client_id' => 'your-app-id', + 'client_secret' => 'your-app-secret', + 'redirect' => 'http://localhost/socialite/callback.php', + ], +... +``` + +### Scope + +Before redirecting the user, you may also set "scopes" on the request using the scope method. This method will overwrite all existing scopes: + +```php +$response = $socialite->driver('github') + ->scopes(['scope1', 'scope2'])->redirect(); + +``` + +### Redirect URL + +You may also want to dynamicly set `redirect`,you can use the following methods to change the `redirect` URL: + +```php +$socialite->redirect($url); +// or +$socialite->withRedirectUrl($url)->redirect(); +// or +$socialite->setRedirectUrl($url)->redirect(); +``` + +> WeChat scopes: +- `snsapi_base`, `snsapi_userinfo` - Used to Media Platform Authentication. +- `snsapi_login` - Used to web Authentication. + +### Additional parameters + +To include any optional parameters in the request, call the with method with an associative array: + +```php +$response = $socialite->driver('google') + ->with(['hd' => 'example.com'])->redirect(); +``` + +### User interface + +#### Standard user api: + +```php + +$user = $socialite->driver('weibo')->user(); +``` + +```json +{ + "id": 1472352, + "nickname": "overtrue", + "name": "安正超", + "email": "anzhengchao@gmail.com", + "avatar": "https://avatars.githubusercontent.com/u/1472352?v=3", + "original": { + "login": "overtrue", + "id": 1472352, + "avatar_url": "https://avatars.githubusercontent.com/u/1472352?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/overtrue", + "html_url": "https://github.com/overtrue", + ... + }, + "token": { + "access_token": "5b1dc56d64fffbd052359f032716cc4e0a1cb9a0", + "token_type": "bearer", + "scope": "user:email" + } +} +``` + +You can fetch the user attribute as a array keys like these: + +```php +$user['id']; // 1472352 +$user['nickname']; // "overtrue" +$user['name']; // "安正超" +$user['email']; // "anzhengchao@gmail.com" +... +``` + +Or using the method: + +```php +$user->getId(); +$user->getNickname(); +$user->getName(); +$user->getEmail(); +$user->getAvatar(); +$user->getOriginal(); +$user->getToken();// or $user->getAccessToken() +$user->getProviderName(); // GitHub/Google/Facebook... +``` + +#### Get original response from OAuth API + +The `$user->getOriginal()` method will return an array of the API raw response. + +#### Get access token Object + +You can get the access token instance of current session by call `$user->getToken()` or `$user->getAccessToken()` or `$user['token']` . + + +### Get user with access token + +```php +$accessToken = new AccessToken(['access_token' => $accessToken]); +$user = $socialite->user($accessToken); +``` + + +### Custom Session or Request instance. + +You can set the request with your custom `Request` instance which instanceof `Symfony\Component\HttpFoundation\Request` before you call `driver` method. + + +```php + +$request = new Request(); // or use AnotherCustomRequest. + +$socialite = new SocialiteManager($config, $request); +``` + +Or set request to `SocialiteManager` instance: + +```php +$socialite->setRequest($request); +``` + +You can get the request from the `SocialiteManager` instance by `getRequest()`: + +```php +$request = $socialite->getRequest(); +``` + +#### Set custom session manager. + +By default, the `SocialiteManager` uses the `Symfony\Component\HttpFoundation\Session\Session` instance as session manager, you can change it as follows: + +```php +$session = new YourCustomSessionManager(); +$socialite->getRequest()->setSession($session); +``` + +> Your custom session manager must be implement the [`Symfony\Component\HttpFoundation\Session\SessionInterface`](http://api.symfony.com/3.0/Symfony/Component/HttpFoundation/Session/SessionInterface.html). + +Enjoy it! :heart: + +# Reference + +- [Google - OpenID Connect](https://developers.google.com/identity/protocols/OpenIDConnect) +- [Facebook - Graph API](https://developers.facebook.com/docs/graph-api) +- [Linkedin - Authenticating with OAuth 2.0](https://developer.linkedin.com/docs/oauth2) +- [微博 - OAuth 2.0 授权机制说明](http://open.weibo.com/wiki/%E6%8E%88%E6%9D%83%E6%9C%BA%E5%88%B6%E8%AF%B4%E6%98%8E) +- [QQ - OAuth 2.0 登录QQ](http://wiki.connect.qq.com/oauth2-0%E7%AE%80%E4%BB%8B) +- [微信公众平台 - OAuth文档](http://mp.weixin.qq.com/wiki/9/01f711493b5a02f24b04365ac5d8fd95.html) +- [微信开放平台 - 网站应用微信登录开发指南](https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419316505&token=&lang=zh_CN) +- [微信开放平台 - 代公众号发起网页授权](https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419318590&token=&lang=zh_CN) +- [豆瓣 - OAuth 2.0 授权机制说明](http://developers.douban.com/wiki/?title=oauth2) +- [抖音 - 网站应用开发指南](http://open.douyin.com/platform/doc) +- [飞书 - 授权说明](https://open.feishu.cn/document/ukTMukTMukTM/uMTNz4yM1MjLzUzM) + +## PHP 扩展包开发 + +> 想知道如何从零开始构建 PHP 扩展包? +> +> 请关注我的实战课程,我会在此课程中分享一些扩展开发经验 —— [《PHP 扩展包实战教程 - 从入门到发布》](https://learnku.com/courses/creating-package) + +# License + +MIT diff --git a/vendor/overtrue/socialite/composer.json b/vendor/overtrue/socialite/composer.json new file mode 100644 index 0000000..d5b4aef --- /dev/null +++ b/vendor/overtrue/socialite/composer.json @@ -0,0 +1,34 @@ +{ + "name": "overtrue/socialite", + "description": "A collection of OAuth 2 packages that extracts from laravel/socialite.", + "keywords": [ + "OAuth", + "social", + "login", + "Weibo", + "WeChat", + "QQ" + ], + "autoload": { + "psr-4": { + "Overtrue\\Socialite\\": "src/" + } + }, + "require": { + "php": ">=5.6", + "guzzlehttp/guzzle": "^5.0|^6.0|^7.0", + "symfony/http-foundation": "^2.7|^3.0|^4.0|^5.0", + "ext-json": "*" + }, + "require-dev": { + "mockery/mockery": "~1.2", + "phpunit/phpunit": "^6.0|^7.0|^8.0|^9.0" + }, + "license": "MIT", + "authors": [ + { + "name": "overtrue", + "email": "anzhengchao@gmail.com" + } + ] +} diff --git a/vendor/overtrue/socialite/phpunit.xml b/vendor/overtrue/socialite/phpunit.xml new file mode 100644 index 0000000..3347b75 --- /dev/null +++ b/vendor/overtrue/socialite/phpunit.xml @@ -0,0 +1,18 @@ + + + + + ./tests/ + + + diff --git a/vendor/overtrue/socialite/src/AccessToken.php b/vendor/overtrue/socialite/src/AccessToken.php new file mode 100644 index 0000000..d62dfe0 --- /dev/null +++ b/vendor/overtrue/socialite/src/AccessToken.php @@ -0,0 +1,84 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Overtrue\Socialite; + +use ArrayAccess; +use InvalidArgumentException; +use JsonSerializable; + +/** + * Class AccessToken. + */ +class AccessToken implements AccessTokenInterface, ArrayAccess, JsonSerializable +{ + use HasAttributes; + + /** + * AccessToken constructor. + * + * @param array $attributes + */ + public function __construct(array $attributes) + { + if (empty($attributes['access_token'])) { + throw new InvalidArgumentException('The key "access_token" could not be empty.'); + } + + $this->attributes = $attributes; + } + + /** + * Return the access token string. + * + * @return string + */ + public function getToken() + { + return $this->getAttribute('access_token'); + } + + /** + * Return the refresh token string. + * + * @return string + */ + public function getRefreshToken() + { + return $this->getAttribute('refresh_token'); + } + + /** + * Set refresh token into this object. + * + * @param string $token + */ + public function setRefreshToken($token) + { + $this->setAttribute('refresh_token', $token); + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + return strval($this->getAttribute('access_token', '')); + } + + /** + * {@inheritdoc} + */ + public function jsonSerialize() + { + return $this->getToken(); + } +} diff --git a/vendor/overtrue/socialite/src/AccessTokenInterface.php b/vendor/overtrue/socialite/src/AccessTokenInterface.php new file mode 100644 index 0000000..f6f54bc --- /dev/null +++ b/vendor/overtrue/socialite/src/AccessTokenInterface.php @@ -0,0 +1,25 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Overtrue\Socialite; + +/** + * Interface AccessTokenInterface. + */ +interface AccessTokenInterface +{ + /** + * Return the access token string. + * + * @return string + */ + public function getToken(); +} diff --git a/vendor/overtrue/socialite/src/AuthorizeFailedException.php b/vendor/overtrue/socialite/src/AuthorizeFailedException.php new file mode 100644 index 0000000..cc2b128 --- /dev/null +++ b/vendor/overtrue/socialite/src/AuthorizeFailedException.php @@ -0,0 +1,35 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Overtrue\Socialite; + +class AuthorizeFailedException extends \RuntimeException +{ + /** + * Response body. + * + * @var array + */ + public $body; + + /** + * Constructor. + * + * @param string $message + * @param array $body + */ + public function __construct($message, $body) + { + parent::__construct($message, -1); + + $this->body = $body; + } +} diff --git a/vendor/overtrue/socialite/src/Config.php b/vendor/overtrue/socialite/src/Config.php new file mode 100644 index 0000000..bbe0862 --- /dev/null +++ b/vendor/overtrue/socialite/src/Config.php @@ -0,0 +1,180 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Overtrue\Socialite; + +use ArrayAccess; +use InvalidArgumentException; + +/** + * Class Config. + */ +class Config implements ArrayAccess +{ + /** + * @var array + */ + protected $config; + + /** + * Config constructor. + * + * @param array $config + */ + public function __construct(array $config) + { + $this->config = $config; + } + + /** + * Get an item from an array using "dot" notation. + * + * @param string $key + * @param mixed $default + * + * @return mixed + */ + public function get($key, $default = null) + { + $config = $this->config; + + if (is_null($key)) { + return $config; + } + if (isset($config[$key])) { + return $config[$key]; + } + foreach (explode('.', $key) as $segment) { + if (!is_array($config) || !array_key_exists($segment, $config)) { + return $default; + } + $config = $config[$segment]; + } + + return $config; + } + + /** + * Set an array item to a given value using "dot" notation. + * + * @param string $key + * @param mixed $value + * + * @return array + */ + public function set($key, $value) + { + if (is_null($key)) { + throw new InvalidArgumentException('Invalid config key.'); + } + + $keys = explode('.', $key); + $config = &$this->config; + + while (count($keys) > 1) { + $key = array_shift($keys); + if (!isset($config[$key]) || !is_array($config[$key])) { + $config[$key] = []; + } + $config = &$config[$key]; + } + + $config[array_shift($keys)] = $value; + + return $config; + } + + /** + * Determine if the given configuration value exists. + * + * @param string $key + * + * @return bool + */ + public function has($key) + { + return (bool) $this->get($key); + } + + /** + * Whether a offset exists. + * + * @see http://php.net/manual/en/arrayaccess.offsetexists.php + * + * @param mixed $offset

    + * An offset to check for. + *

    + * + * @return bool true on success or false on failure. + *

    + *

    + * The return value will be casted to boolean if non-boolean was returned + * + * @since 5.0.0 + */ + public function offsetExists($offset) + { + return array_key_exists($offset, $this->config); + } + + /** + * Offset to retrieve. + * + * @see http://php.net/manual/en/arrayaccess.offsetget.php + * + * @param mixed $offset

    + * The offset to retrieve. + *

    + * + * @return mixed Can return all value types + * + * @since 5.0.0 + */ + public function offsetGet($offset) + { + return $this->get($offset); + } + + /** + * Offset to set. + * + * @see http://php.net/manual/en/arrayaccess.offsetset.php + * + * @param mixed $offset

    + * The offset to assign the value to. + *

    + * @param mixed $value

    + * The value to set. + *

    + * + * @since 5.0.0 + */ + public function offsetSet($offset, $value) + { + $this->set($offset, $value); + } + + /** + * Offset to unset. + * + * @see http://php.net/manual/en/arrayaccess.offsetunset.php + * + * @param mixed $offset

    + * The offset to unset. + *

    + * + * @since 5.0.0 + */ + public function offsetUnset($offset) + { + $this->set($offset, null); + } +} diff --git a/vendor/overtrue/socialite/src/FactoryInterface.php b/vendor/overtrue/socialite/src/FactoryInterface.php new file mode 100644 index 0000000..7a4959c --- /dev/null +++ b/vendor/overtrue/socialite/src/FactoryInterface.php @@ -0,0 +1,27 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Overtrue\Socialite; + +/** + * Interface FactoryInterface. + */ +interface FactoryInterface +{ + /** + * Get an OAuth provider implementation. + * + * @param string $driver + * + * @return \Overtrue\Socialite\ProviderInterface + */ + public function driver($driver); +} diff --git a/vendor/overtrue/socialite/src/HasAttributes.php b/vendor/overtrue/socialite/src/HasAttributes.php new file mode 100644 index 0000000..eeff890 --- /dev/null +++ b/vendor/overtrue/socialite/src/HasAttributes.php @@ -0,0 +1,135 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Overtrue\Socialite; + +/** + * Trait HasAttributes. + */ +trait HasAttributes +{ + /** + * @var array + */ + protected $attributes = []; + + /** + * Return the attributes. + * + * @return array + */ + public function getAttributes() + { + return $this->attributes; + } + + /** + * Return the extra attribute. + * + * @param string $name + * @param string $default + * + * @return mixed + */ + public function getAttribute($name, $default = null) + { + return isset($this->attributes[$name]) ? $this->attributes[$name] : $default; + } + + /** + * Set extra attributes. + * + * @param string $name + * @param mixed $value + * + * @return $this + */ + public function setAttribute($name, $value) + { + $this->attributes[$name] = $value; + + return $this; + } + + /** + * Map the given array onto the user's properties. + * + * @param array $attributes + * + * @return $this + */ + public function merge(array $attributes) + { + $this->attributes = array_merge($this->attributes, $attributes); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function offsetExists($offset) + { + return array_key_exists($offset, $this->attributes); + } + + /** + * {@inheritdoc} + */ + public function offsetGet($offset) + { + return $this->getAttribute($offset); + } + + /** + * {@inheritdoc} + */ + public function offsetSet($offset, $value) + { + $this->setAttribute($offset, $value); + } + + /** + * {@inheritdoc} + */ + public function offsetUnset($offset) + { + unset($this->attributes[$offset]); + } + + /** + * {@inheritdoc} + */ + public function __get($property) + { + return $this->getAttribute($property); + } + + /** + * Return array. + * + * @return array + */ + public function toArray() + { + return $this->getAttributes(); + } + + /** + * Return JSON. + * + * @return string + */ + public function toJSON() + { + return json_encode($this->getAttributes(), JSON_UNESCAPED_UNICODE); + } +} diff --git a/vendor/overtrue/socialite/src/InvalidArgumentException.php b/vendor/overtrue/socialite/src/InvalidArgumentException.php new file mode 100644 index 0000000..c044e64 --- /dev/null +++ b/vendor/overtrue/socialite/src/InvalidArgumentException.php @@ -0,0 +1,16 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Overtrue\Socialite; + +class InvalidArgumentException extends \InvalidArgumentException +{ +} diff --git a/vendor/overtrue/socialite/src/InvalidStateException.php b/vendor/overtrue/socialite/src/InvalidStateException.php new file mode 100644 index 0000000..96ac503 --- /dev/null +++ b/vendor/overtrue/socialite/src/InvalidStateException.php @@ -0,0 +1,16 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Overtrue\Socialite; + +class InvalidStateException extends \InvalidArgumentException +{ +} diff --git a/vendor/overtrue/socialite/src/ProviderInterface.php b/vendor/overtrue/socialite/src/ProviderInterface.php new file mode 100644 index 0000000..e78d172 --- /dev/null +++ b/vendor/overtrue/socialite/src/ProviderInterface.php @@ -0,0 +1,31 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Overtrue\Socialite; + +interface ProviderInterface +{ + /** + * Redirect the user to the authentication page for the provider. + * + * @return \Symfony\Component\HttpFoundation\RedirectResponse + */ + public function redirect(); + + /** + * Get the User instance for the authenticated user. + * + * @param \Overtrue\Socialite\AccessTokenInterface $token + * + * @return \Overtrue\Socialite\User + */ + public function user(AccessTokenInterface $token = null); +} diff --git a/vendor/overtrue/socialite/src/Providers/AbstractProvider.php b/vendor/overtrue/socialite/src/Providers/AbstractProvider.php new file mode 100644 index 0000000..b33e6da --- /dev/null +++ b/vendor/overtrue/socialite/src/Providers/AbstractProvider.php @@ -0,0 +1,585 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Overtrue\Socialite\Providers; + +use GuzzleHttp\Client; +use GuzzleHttp\ClientInterface; +use Overtrue\Socialite\AccessToken; +use Overtrue\Socialite\AccessTokenInterface; +use Overtrue\Socialite\AuthorizeFailedException; +use Overtrue\Socialite\Config; +use Overtrue\Socialite\InvalidStateException; +use Overtrue\Socialite\ProviderInterface; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Request; + +/** + * Class AbstractProvider. + */ +abstract class AbstractProvider implements ProviderInterface +{ + /** + * Provider name. + * + * @var string + */ + protected $name; + + /** + * The HTTP request instance. + * + * @var \Symfony\Component\HttpFoundation\Request + */ + protected $request; + + /** + * Driver config. + * + * @var Config + */ + protected $config; + + /** + * The client ID. + * + * @var string + */ + protected $clientId; + + /** + * The client secret. + * + * @var string + */ + protected $clientSecret; + + /** + * @var \Overtrue\Socialite\AccessTokenInterface + */ + protected $accessToken; + + /** + * The redirect URL. + * + * @var string + */ + protected $redirectUrl; + + /** + * The custom parameters to be sent with the request. + * + * @var array + */ + protected $parameters = []; + + /** + * The scopes being requested. + * + * @var array + */ + protected $scopes = []; + + /** + * The separating character for the requested scopes. + * + * @var string + */ + protected $scopeSeparator = ','; + + /** + * The type of the encoding in the query. + * + * @var int Can be either PHP_QUERY_RFC3986 or PHP_QUERY_RFC1738 + */ + protected $encodingType = PHP_QUERY_RFC1738; + + /** + * Indicates if the session state should be utilized. + * + * @var bool + */ + protected $stateless = false; + + /** + * The options for guzzle\client. + * + * @var array + */ + protected static $guzzleOptions = ['http_errors' => false]; + + /** + * Create a new provider instance. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * @param array $config + */ + public function __construct(Request $request, $config) + { + // 兼容处理 + if (!\is_array($config)) { + $config = [ + 'client_id' => \func_get_arg(1), + 'client_secret' => \func_get_arg(2), + 'redirect' => \func_get_arg(3) ?: null, + ]; + } + $this->config = new Config($config); + $this->request = $request; + $this->clientId = $config['client_id']; + $this->clientSecret = $config['client_secret']; + $this->redirectUrl = isset($config['redirect']) ? $config['redirect'] : null; + } + + /** + * Get the authentication URL for the provider. + * + * @param string $state + * + * @return string + */ + abstract protected function getAuthUrl($state); + + /** + * Get the token URL for the provider. + * + * @return string + */ + abstract protected function getTokenUrl(); + + /** + * Get the raw user for the given access token. + * + * @param \Overtrue\Socialite\AccessTokenInterface $token + * + * @return array + */ + abstract protected function getUserByToken(AccessTokenInterface $token); + + /** + * Map the raw user array to a Socialite User instance. + * + * @param array $user + * + * @return \Overtrue\Socialite\User + */ + abstract protected function mapUserToObject(array $user); + + /** + * Redirect the user of the application to the provider's authentication screen. + * + * @param string $redirectUrl + * + * @return \Symfony\Component\HttpFoundation\RedirectResponse + */ + public function redirect($redirectUrl = null) + { + $state = null; + + if (!is_null($redirectUrl)) { + $this->redirectUrl = $redirectUrl; + } + + if ($this->usesState()) { + $state = $this->makeState(); + } + + return new RedirectResponse($this->getAuthUrl($state)); + } + + /** + * {@inheritdoc} + */ + public function user(AccessTokenInterface $token = null) + { + if (is_null($token) && $this->hasInvalidState()) { + throw new InvalidStateException(); + } + + $token = $token ?: $this->getAccessToken($this->getCode()); + + $user = $this->getUserByToken($token); + + $user = $this->mapUserToObject($user)->merge(['original' => $user]); + + return $user->setToken($token)->setProviderName($this->getName()); + } + + /** + * Set redirect url. + * + * @param string $redirectUrl + * + * @return $this + */ + public function setRedirectUrl($redirectUrl) + { + $this->redirectUrl = $redirectUrl; + + return $this; + } + + /** + * Set redirect url. + * + * @param string $redirectUrl + * + * @return $this + */ + public function withRedirectUrl($redirectUrl) + { + $this->redirectUrl = $redirectUrl; + + return $this; + } + + /** + * Return the redirect url. + * + * @return string + */ + public function getRedirectUrl() + { + return $this->redirectUrl; + } + + /** + * @param \Overtrue\Socialite\AccessTokenInterface $accessToken + * + * @return $this + */ + public function setAccessToken(AccessTokenInterface $accessToken) + { + $this->accessToken = $accessToken; + + return $this; + } + + /** + * Get the access token for the given code. + * + * @param string $code + * + * @return \Overtrue\Socialite\AccessTokenInterface + */ + public function getAccessToken($code) + { + if ($this->accessToken) { + return $this->accessToken; + } + + $guzzleVersion = \defined(ClientInterface::class.'::VERSION') ? \constant(ClientInterface::class.'::VERSION') : 7; + + $postKey = (1 === version_compare($guzzleVersion, '6')) ? 'form_params' : 'body'; + + $response = $this->getHttpClient()->post($this->getTokenUrl(), [ + 'headers' => ['Accept' => 'application/json'], + $postKey => $this->getTokenFields($code), + ]); + + return $this->parseAccessToken($response->getBody()); + } + + /** + * Set the scopes of the requested access. + * + * @param array $scopes + * + * @return $this + */ + public function scopes(array $scopes) + { + $this->scopes = $scopes; + + return $this; + } + + /** + * Set the request instance. + * + * @param Request $request + * + * @return $this + */ + public function setRequest(Request $request) + { + $this->request = $request; + + return $this; + } + + /** + * Get the request instance. + * + * @return \Symfony\Component\HttpFoundation\Request + */ + public function getRequest() + { + return $this->request; + } + + /** + * Indicates that the provider should operate as stateless. + * + * @return $this + */ + public function stateless() + { + $this->stateless = true; + + return $this; + } + + /** + * Set the custom parameters of the request. + * + * @param array $parameters + * + * @return $this + */ + public function with(array $parameters) + { + $this->parameters = $parameters; + + return $this; + } + + /** + * @throws \ReflectionException + * + * @return string + */ + public function getName() + { + if (empty($this->name)) { + $this->name = strstr((new \ReflectionClass(get_class($this)))->getShortName(), 'Provider', true); + } + + return $this->name; + } + + /** + * @return array + */ + public function getConfig() + { + return $this->config; + } + + /** + * Get the authentication URL for the provider. + * + * @param string $url + * @param string $state + * + * @return string + */ + protected function buildAuthUrlFromBase($url, $state) + { + return $url.'?'.http_build_query($this->getCodeFields($state), '', '&', $this->encodingType); + } + + /** + * Get the GET parameters for the code request. + * + * @param string|null $state + * + * @return array + */ + protected function getCodeFields($state = null) + { + $fields = array_merge([ + 'client_id' => $this->config['client_id'], + 'redirect_uri' => $this->redirectUrl, + 'scope' => $this->formatScopes($this->scopes, $this->scopeSeparator), + 'response_type' => 'code', + ], $this->parameters); + + if ($this->usesState()) { + $fields['state'] = $state; + } + + return $fields; + } + + /** + * Format the given scopes. + * + * @param array $scopes + * @param string $scopeSeparator + * + * @return string + */ + protected function formatScopes(array $scopes, $scopeSeparator) + { + return implode($scopeSeparator, $scopes); + } + + /** + * Determine if the current request / session has a mismatching "state". + * + * @return bool + */ + protected function hasInvalidState() + { + if ($this->isStateless()) { + return false; + } + + $state = $this->request->getSession()->get('state'); + + return !(strlen($state) > 0 && $this->request->get('state') === $state); + } + + /** + * Get the POST fields for the token request. + * + * @param string $code + * + * @return array + */ + protected function getTokenFields($code) + { + return [ + 'client_id' => $this->getConfig()->get('client_id'), + 'client_secret' => $this->getConfig()->get('client_secret'), + 'code' => $code, + 'redirect_uri' => $this->redirectUrl, + ]; + } + + /** + * Get the access token from the token response body. + * + * @param \Psr\Http\Message\StreamInterface|array $body + * + * @return \Overtrue\Socialite\AccessTokenInterface + */ + protected function parseAccessToken($body) + { + if (!is_array($body)) { + $body = json_decode($body, true); + } + + if (empty($body['access_token'])) { + throw new AuthorizeFailedException('Authorize Failed: '.json_encode($body, JSON_UNESCAPED_UNICODE), $body); + } + + return new AccessToken($body); + } + + /** + * Get the code from the request. + * + * @return string + */ + protected function getCode() + { + return $this->request->get('code'); + } + + /** + * Get a fresh instance of the Guzzle HTTP client. + * + * @return \GuzzleHttp\Client + */ + protected function getHttpClient() + { + return new Client(self::$guzzleOptions); + } + + /** + * Set options for Guzzle HTTP client. + * + * @param array $config + * + * @return array + */ + public static function setGuzzleOptions($config = []) + { + return self::$guzzleOptions = $config; + } + + /** + * Determine if the provider is operating with state. + * + * @return bool + */ + protected function usesState() + { + return !$this->stateless; + } + + /** + * Determine if the provider is operating as stateless. + * + * @return bool + */ + protected function isStateless() + { + return !$this->request->hasSession() || $this->stateless; + } + + /** + * Return array item by key. + * + * @param array $array + * @param string $key + * @param mixed $default + * + * @return mixed + */ + protected function arrayItem(array $array, $key, $default = null) + { + if (is_null($key)) { + return $array; + } + + if (isset($array[$key])) { + return $array[$key]; + } + + foreach (explode('.', $key) as $segment) { + if (!is_array($array) || !array_key_exists($segment, $array)) { + return $default; + } + + $array = $array[$segment]; + } + + return $array; + } + + /** + * Put state to session storage and return it. + * + * @return string|bool + */ + protected function makeState() + { + if (!$this->request->hasSession()) { + return false; + } + + $state = sha1(uniqid(mt_rand(1, 1000000), true)); + $session = $this->request->getSession(); + + if (is_callable([$session, 'put'])) { + $session->put('state', $state); + } elseif (is_callable([$session, 'set'])) { + $session->set('state', $state); + } else { + return false; + } + + return $state; + } +} diff --git a/vendor/overtrue/socialite/src/Providers/BaiduProvider.php b/vendor/overtrue/socialite/src/Providers/BaiduProvider.php new file mode 100644 index 0000000..e06b890 --- /dev/null +++ b/vendor/overtrue/socialite/src/Providers/BaiduProvider.php @@ -0,0 +1,134 @@ +buildAuthUrlFromBase($this->baseUrl.'/oauth/'.$this->version.'/authorize', $state); + } + + /** + * {@inheritdoc}. + */ + protected function getCodeFields($state = null) + { + return array_merge([ + 'response_type' => 'code', + 'client_id' => $this->getConfig()->get('client_id'), + 'redirect_uri' => $this->redirectUrl, + 'scope' => $this->formatScopes($this->scopes, $this->scopeSeparator), + 'display' => $this->display, + ], $this->parameters); + } + + /** + * Get the token URL for the provider. + * + * @return string + */ + protected function getTokenUrl() + { + return $this->baseUrl.'/oauth/'.$this->version.'/token'; + } + + /** + * Get the Post fields for the token request. + * + * @param string $code + * + * @return array + */ + protected function getTokenFields($code) + { + return parent::getTokenFields($code) + ['grant_type' => 'authorization_code']; + } + + /** + * Get the raw user for the given access token. + * + * @param \Overtrue\Socialite\AccessTokenInterface $token + * + * @return array + */ + protected function getUserByToken(AccessTokenInterface $token) + { + $response = $this->getHttpClient()->get($this->baseUrl.'/rest/'.$this->version.'/passport/users/getInfo', [ + 'query' => [ + 'access_token' => $token->getToken(), + ], + 'headers' => [ + 'Accept' => 'application/json', + ], + ]); + + return json_decode($response->getBody(), true); + } + + /** + * Map the raw user array to a Socialite User instance. + * + * @param array $user + * + * @return \Overtrue\Socialite\User + */ + protected function mapUserToObject(array $user) + { + $realname = $this->arrayItem($user, 'realname'); + + return new User([ + 'id' => $this->arrayItem($user, 'userid'), + 'nickname' => empty($realname) ? '' : $realname, + 'name' => $this->arrayItem($user, 'username'), + 'email' => '', + 'avatar' => $this->arrayItem($user, 'portrait'), + ]); + } +} diff --git a/vendor/overtrue/socialite/src/Providers/DouYinProvider.php b/vendor/overtrue/socialite/src/Providers/DouYinProvider.php new file mode 100644 index 0000000..f798a86 --- /dev/null +++ b/vendor/overtrue/socialite/src/Providers/DouYinProvider.php @@ -0,0 +1,169 @@ +buildAuthUrlFromBase($this->baseUrl.'/platform/oauth/connect', $state); + } + + /** + * 获取授权码接口参数. + * + * @param string|null $state + * + * @return array + */ + public function getCodeFields($state = null) + { + $fields = [ + 'client_key' => $this->getConfig()->get('client_id'), + 'redirect_uri' => $this->redirectUrl, + 'scope' => $this->formatScopes($this->scopes, $this->scopeSeparator), + 'response_type' => 'code', + ]; + + if ($this->usesState()) { + $fields['state'] = $state; + } + + return $fields; + } + + /** + * 获取access_token地址. + * + * {@inheritdoc} + */ + protected function getTokenUrl() + { + return $this->baseUrl.'/oauth/access_token'; + } + + /** + * 通过code获取access_token. + * + * @param string $code + * + * @return \Overtrue\Socialite\AccessToken + */ + public function getAccessToken($code) + { + $response = $this->getHttpClient()->get($this->getTokenUrl(), [ + 'query' => $this->getTokenFields($code), + ]); + + return $this->parseAccessToken($response->getBody()->getContents()); + } + + /** + * 获取access_token接口参数. + * + * @param string $code + * + * @return array + */ + protected function getTokenFields($code) + { + return [ + 'client_key' => $this->getConfig()->get('client_id'), + 'client_secret' => $this->getConfig()->get('client_secret'), + 'code' => $code, + 'grant_type' => 'authorization_code', + ]; + } + + /** + * 格式化token. + * + * @param \Psr\Http\Message\StreamInterface|array $body + * + * @return \Overtrue\Socialite\AccessTokenInterface + */ + protected function parseAccessToken($body) + { + if (!is_array($body)) { + $body = json_decode($body, true); + } + + if (empty($body['data']['access_token'])) { + throw new AuthorizeFailedException('Authorize Failed: '.json_encode($body, JSON_UNESCAPED_UNICODE), $body); + } + + return new AccessToken($body['data']); + } + + /** + * 通过token 获取用户信息. + * + * @param AccessTokenInterface $token + * + * @return array|mixed + */ + protected function getUserByToken(AccessTokenInterface $token) + { + $userUrl = $this->baseUrl.'/oauth/userinfo/'; + + $response = $this->getHttpClient()->get( + $userUrl, + [ + 'query' => [ + 'access_token' => $token->getToken(), + 'open_id' => $token['open_id'], + ], + ] + ); + + return json_decode($response->getBody(), true); + } + + /** + * 格式化用户信息. + * + * @param array $user + * + * @return User + */ + protected function mapUserToObject(array $user) + { + return new User([ + 'id' => $this->arrayItem($user, 'open_id'), + 'username' => $this->arrayItem($user, 'nickname'), + 'nickname' => $this->arrayItem($user, 'nickname'), + 'avatar' => $this->arrayItem($user, 'avatar'), + ]); + } +} diff --git a/vendor/overtrue/socialite/src/Providers/DoubanProvider.php b/vendor/overtrue/socialite/src/Providers/DoubanProvider.php new file mode 100644 index 0000000..f3b5c93 --- /dev/null +++ b/vendor/overtrue/socialite/src/Providers/DoubanProvider.php @@ -0,0 +1,88 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Overtrue\Socialite\Providers; + +use Overtrue\Socialite\AccessTokenInterface; +use Overtrue\Socialite\ProviderInterface; +use Overtrue\Socialite\User; + +/** + * Class DoubanProvider. + * + * @see http://developers.douban.com/wiki/?title=oauth2 [使用 OAuth 2.0 访问豆瓣 API] + */ +class DoubanProvider extends AbstractProvider implements ProviderInterface +{ + /** + * {@inheritdoc}. + */ + protected function getAuthUrl($state) + { + return $this->buildAuthUrlFromBase('https://www.douban.com/service/auth2/auth', $state); + } + + /** + * {@inheritdoc}. + */ + protected function getTokenUrl() + { + return 'https://www.douban.com/service/auth2/token'; + } + + /** + * {@inheritdoc}. + */ + protected function getUserByToken(AccessTokenInterface $token) + { + $response = $this->getHttpClient()->get('https://api.douban.com/v2/user/~me', [ + 'headers' => [ + 'Authorization' => 'Bearer '.$token->getToken(), + ], + ]); + + return json_decode($response->getBody()->getContents(), true); + } + + /** + * {@inheritdoc}. + */ + protected function mapUserToObject(array $user) + { + return new User([ + 'id' => $this->arrayItem($user, 'id'), + 'nickname' => $this->arrayItem($user, 'name'), + 'name' => $this->arrayItem($user, 'name'), + 'avatar' => $this->arrayItem($user, 'large_avatar'), + 'email' => null, + ]); + } + + /** + * {@inheritdoc}. + */ + protected function getTokenFields($code) + { + return parent::getTokenFields($code) + ['grant_type' => 'authorization_code']; + } + + /** + * {@inheritdoc}. + */ + public function getAccessToken($code) + { + $response = $this->getHttpClient()->post($this->getTokenUrl(), [ + 'form_params' => $this->getTokenFields($code), + ]); + + return $this->parseAccessToken($response->getBody()->getContents()); + } +} diff --git a/vendor/overtrue/socialite/src/Providers/FacebookProvider.php b/vendor/overtrue/socialite/src/Providers/FacebookProvider.php new file mode 100644 index 0000000..ce2cbec --- /dev/null +++ b/vendor/overtrue/socialite/src/Providers/FacebookProvider.php @@ -0,0 +1,168 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Overtrue\Socialite\Providers; + +use Overtrue\Socialite\AccessTokenInterface; +use Overtrue\Socialite\ProviderInterface; +use Overtrue\Socialite\User; + +/** + * Class FacebookProvider. + * + * @see https://developers.facebook.com/docs/graph-api [Facebook - Graph API] + */ +class FacebookProvider extends AbstractProvider implements ProviderInterface +{ + /** + * The base Facebook Graph URL. + * + * @var string + */ + protected $graphUrl = 'https://graph.facebook.com'; + + /** + * The Graph API version for the request. + * + * @var string + */ + protected $version = 'v3.3'; + + /** + * The user fields being requested. + * + * @var array + */ + protected $fields = ['first_name', 'last_name', 'email', 'gender', 'verified']; + + /** + * The scopes being requested. + * + * @var array + */ + protected $scopes = ['email']; + + /** + * Display the dialog in a popup view. + * + * @var bool + */ + protected $popup = false; + + /** + * {@inheritdoc} + */ + protected function getAuthUrl($state) + { + return $this->buildAuthUrlFromBase('https://www.facebook.com/'.$this->version.'/dialog/oauth', $state); + } + + /** + * {@inheritdoc} + */ + protected function getTokenUrl() + { + return $this->graphUrl.'/oauth/access_token'; + } + + /** + * Get the access token for the given code. + * + * @param string $code + * + * @return \Overtrue\Socialite\AccessToken + */ + public function getAccessToken($code) + { + $response = $this->getHttpClient()->get($this->getTokenUrl(), [ + 'query' => $this->getTokenFields($code), + ]); + + return $this->parseAccessToken($response->getBody()); + } + + /** + * {@inheritdoc} + */ + protected function getUserByToken(AccessTokenInterface $token) + { + $appSecretProof = hash_hmac('sha256', $token->getToken(), $this->getConfig()->get('client_secret')); + + $response = $this->getHttpClient()->get($this->graphUrl.'/'.$this->version.'/me?access_token='.$token.'&appsecret_proof='.$appSecretProof.'&fields='.implode(',', $this->fields), [ + 'headers' => [ + 'Accept' => 'application/json', + ], + ]); + + return json_decode($response->getBody(), true); + } + + /** + * {@inheritdoc} + */ + protected function mapUserToObject(array $user) + { + $userId = $this->arrayItem($user, 'id'); + $avatarUrl = $this->graphUrl.'/'.$this->version.'/'.$userId.'/picture'; + + $firstName = $this->arrayItem($user, 'first_name'); + $lastName = $this->arrayItem($user, 'last_name'); + + return new User([ + 'id' => $this->arrayItem($user, 'id'), + 'nickname' => null, + 'name' => $firstName.' '.$lastName, + 'email' => $this->arrayItem($user, 'email'), + 'avatar' => $userId ? $avatarUrl.'?type=normal' : null, + 'avatar_original' => $userId ? $avatarUrl.'?width=1920' : null, + ]); + } + + /** + * {@inheritdoc} + */ + protected function getCodeFields($state = null) + { + $fields = parent::getCodeFields($state); + + if ($this->popup) { + $fields['display'] = 'popup'; + } + + return $fields; + } + + /** + * Set the user fields to request from Facebook. + * + * @param array $fields + * + * @return $this + */ + public function fields(array $fields) + { + $this->fields = $fields; + + return $this; + } + + /** + * Set the dialog to be displayed as a popup. + * + * @return $this + */ + public function asPopup() + { + $this->popup = true; + + return $this; + } +} diff --git a/vendor/overtrue/socialite/src/Providers/FeiShuProvider.php b/vendor/overtrue/socialite/src/Providers/FeiShuProvider.php new file mode 100644 index 0000000..2d61d07 --- /dev/null +++ b/vendor/overtrue/socialite/src/Providers/FeiShuProvider.php @@ -0,0 +1,192 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Overtrue\Socialite\Providers; + +use Overtrue\Socialite\AccessToken; +use Overtrue\Socialite\AccessTokenInterface; +use Overtrue\Socialite\AuthorizeFailedException; +use Overtrue\Socialite\InvalidStateException; +use Overtrue\Socialite\ProviderInterface; +use Overtrue\Socialite\User; + +/** + * Class FeiShuProvider. + * + * @author qijian.song@show.world + * + * @see https://open.feishu.cn/ + */ +class FeiShuProvider extends AbstractProvider implements ProviderInterface +{ + /** + * 飞书接口域名. + * + * @var string + */ + protected $baseUrl = 'https://open.feishu.cn'; + + /** + * 应用授权作用域. + * + * @var array + */ + protected $scopes = ['user_info']; + + /** + * 获取登录页面地址. + * + * {@inheritdoc} + */ + protected function getAuthUrl($state) + { + return $this->buildAuthUrlFromBase($this->baseUrl.'/open-apis/authen/v1/index', $state); + } + + /** + * 获取授权码接口参数. + * + * @param string|null $state + * + * @return array + */ + protected function getCodeFields($state = null) + { + $fields = [ + 'redirect_uri' => $this->redirectUrl, + 'app_id' => $this->getConfig()->get('client_id'), + ]; + + if ($this->usesState()) { + $fields['state'] = $state; + } + + return $fields; + } + + /** + * 获取 app_access_token 地址. + * + * {@inheritdoc} + */ + protected function getTokenUrl() + { + return $this->baseUrl.'/open-apis/auth/v3/app_access_token/internal'; + } + + /** + * 获取 app_access_token. + * + * @return \Overtrue\Socialite\AccessToken + */ + public function getAccessToken($code = '') + { + $response = $this->getHttpClient()->post($this->getTokenUrl(), [ + 'headers' => ['Content-Type' => 'application/json'], + 'json' => $this->getTokenFields($code), + ]); + + return $this->parseAccessToken($response->getBody()->getContents()); + } + + /** + * 获取 app_access_token 接口参数. + * + * @return array + */ + protected function getTokenFields($code) + { + return [ + 'app_id' => $this->getConfig()->get('client_id'), + 'app_secret' => $this->getConfig()->get('client_secret'), + ]; + } + + /** + * 格式化 token. + * + * @param \Psr\Http\Message\StreamInterface|array $body + * + * @return \Overtrue\Socialite\AccessTokenInterface + */ + protected function parseAccessToken($body) + { + if (!is_array($body)) { + $body = json_decode($body, true); + } + + if (empty($body['app_access_token'])) { + throw new AuthorizeFailedException('Authorize Failed: '.json_encode($body, JSON_UNESCAPED_UNICODE), $body); + } + $data['access_token'] = $body['app_access_token']; + + return new AccessToken($data); + } + + /** + * 获取用户信息. + * + * @return array|mixed + */ + public function user(AccessTokenInterface $token = null) + { + if (is_null($token) && $this->hasInvalidState()) { + throw new InvalidStateException(); + } + + $token = $token ?: $this->getAccessToken(); + + $user = $this->getUserByToken($token, $this->getCode()); + $user = $this->mapUserToObject($user)->merge(['original' => $user]); + + return $user->setToken($token)->setProviderName($this->getName()); + } + + /** + * 通过 token 获取用户信息. + * + * @return array|mixed + */ + protected function getUserByToken(AccessTokenInterface $token) + { + $userUrl = $this->baseUrl.'/open-apis/authen/v1/access_token'; + + $response = $this->getHttpClient()->post( + $userUrl, + [ + 'json' => [ + 'app_access_token' => $token->getToken(), + 'code' => $this->getCode(), + 'grant_type' => 'authorization_code', + ], + ] + ); + + $result = json_decode($response->getBody(), true); + + return $result['data']; + } + + /** + * 格式化用户信息. + * + * @return User + */ + protected function mapUserToObject(array $user) + { + return new User([ + 'id' => $this->arrayItem($user, 'open_id'), + 'username' => $this->arrayItem($user, 'name'), + 'nickname' => $this->arrayItem($user, 'name'), + 'avatar' => $this->arrayItem($user, 'avatar_url'), + ]); + } +} diff --git a/vendor/overtrue/socialite/src/Providers/GitHubProvider.php b/vendor/overtrue/socialite/src/Providers/GitHubProvider.php new file mode 100644 index 0000000..955fa9d --- /dev/null +++ b/vendor/overtrue/socialite/src/Providers/GitHubProvider.php @@ -0,0 +1,126 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Overtrue\Socialite\Providers; + +use Exception; +use Overtrue\Socialite\AccessTokenInterface; +use Overtrue\Socialite\ProviderInterface; +use Overtrue\Socialite\User; + +/** + * Class GitHubProvider. + */ +class GitHubProvider extends AbstractProvider implements ProviderInterface +{ + /** + * The scopes being requested. + * + * @var array + */ + protected $scopes = ['user:email']; + + /** + * {@inheritdoc} + */ + protected function getAuthUrl($state) + { + return $this->buildAuthUrlFromBase('https://github.com/login/oauth/authorize', $state); + } + + /** + * {@inheritdoc} + */ + protected function getTokenUrl() + { + return 'https://github.com/login/oauth/access_token'; + } + + /** + * {@inheritdoc} + */ + protected function getUserByToken(AccessTokenInterface $token) + { + $userUrl = 'https://api.github.com/user'; + + $response = $this->getHttpClient()->get( + $userUrl, + $this->createAuthorizationHeaders($token) + ); + + $user = json_decode($response->getBody(), true); + + if (in_array('user:email', $this->scopes)) { + $user['email'] = $this->getEmailByToken($token); + } + + return $user; + } + + /** + * Get the email for the given access token. + * + * @param string $token + * + * @return string|null + */ + protected function getEmailByToken($token) + { + $emailsUrl = 'https://api.github.com/user/emails'; + + try { + $response = $this->getHttpClient()->get( + $emailsUrl, + $this->createAuthorizationHeaders($token) + ); + } catch (Exception $e) { + return; + } + + foreach (json_decode($response->getBody(), true) as $email) { + if ($email['primary'] && $email['verified']) { + return $email['email']; + } + } + } + + /** + * {@inheritdoc} + */ + protected function mapUserToObject(array $user) + { + return new User([ + 'id' => $this->arrayItem($user, 'id'), + 'username' => $this->arrayItem($user, 'login'), + 'nickname' => $this->arrayItem($user, 'login'), + 'name' => $this->arrayItem($user, 'name'), + 'email' => $this->arrayItem($user, 'email'), + 'avatar' => $this->arrayItem($user, 'avatar_url'), + ]); + } + + /** + * Get the default options for an HTTP request. + * + * @param string $token + * + * @return array + */ + protected function createAuthorizationHeaders(string $token) + { + return [ + 'headers' => [ + 'Accept' => 'application/vnd.github.v3+json', + 'Authorization' => sprintf('token %s', $token), + ], + ]; + } +} diff --git a/vendor/overtrue/socialite/src/Providers/GoogleProvider.php b/vendor/overtrue/socialite/src/Providers/GoogleProvider.php new file mode 100644 index 0000000..c702aff --- /dev/null +++ b/vendor/overtrue/socialite/src/Providers/GoogleProvider.php @@ -0,0 +1,119 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Overtrue\Socialite\Providers; + +use GuzzleHttp\ClientInterface; +use Overtrue\Socialite\AccessTokenInterface; +use Overtrue\Socialite\ProviderInterface; +use Overtrue\Socialite\User; + +/** + * Class GoogleProvider. + * + * @see https://developers.google.com/identity/protocols/OpenIDConnect [OpenID Connect] + */ +class GoogleProvider extends AbstractProvider implements ProviderInterface +{ + /** + * The separating character for the requested scopes. + * + * @var string + */ + protected $scopeSeparator = ' '; + + /** + * The scopes being requested. + * + * @var array + */ + protected $scopes = [ + 'https://www.googleapis.com/auth/userinfo.email', + 'https://www.googleapis.com/auth/userinfo.profile', + ]; + + /** + * {@inheritdoc} + */ + protected function getAuthUrl($state) + { + return $this->buildAuthUrlFromBase('https://accounts.google.com/o/oauth2/v2/auth', $state); + } + + /** + * {@inheritdoc} + */ + protected function getTokenUrl() + { + return 'https://www.googleapis.com/oauth2/v4/token'; + } + + /** + * Get the access token for the given code. + * + * @param string $code + * + * @return string + */ + public function getAccessToken($code) + { + $guzzleVersion = \defined(ClientInterface::class.'::VERSION') ? \constant(ClientInterface::class.'::VERSION') : 7; + $postKey = (1 === version_compare($guzzleVersion, '6')) ? 'form_params' : 'body'; + + $response = $this->getHttpClient()->post($this->getTokenUrl(), [ + $postKey => $this->getTokenFields($code), + ]); + + return $this->parseAccessToken($response->getBody()); + } + + /** + * Get the POST fields for the token request. + * + * @param string $code + * + * @return array + */ + protected function getTokenFields($code) + { + return parent::getTokenFields($code) + ['grant_type' => 'authorization_code']; + } + + /** + * {@inheritdoc} + */ + protected function getUserByToken(AccessTokenInterface $token) + { + $response = $this->getHttpClient()->get('https://www.googleapis.com/userinfo/v2/me', [ + 'headers' => [ + 'Accept' => 'application/json', + 'Authorization' => 'Bearer '.$token->getToken(), + ], + ]); + + return json_decode($response->getBody(), true); + } + + /** + * {@inheritdoc} + */ + protected function mapUserToObject(array $user) + { + return new User([ + 'id' => $this->arrayItem($user, 'id'), + 'username' => $this->arrayItem($user, 'email'), + 'nickname' => $this->arrayItem($user, 'name'), + 'name' => $this->arrayItem($user, 'name'), + 'email' => $this->arrayItem($user, 'email'), + 'avatar' => $this->arrayItem($user, 'picture'), + ]); + } +} diff --git a/vendor/overtrue/socialite/src/Providers/LinkedinProvider.php b/vendor/overtrue/socialite/src/Providers/LinkedinProvider.php new file mode 100644 index 0000000..019167a --- /dev/null +++ b/vendor/overtrue/socialite/src/Providers/LinkedinProvider.php @@ -0,0 +1,181 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Overtrue\Socialite\Providers; + +use Overtrue\Socialite\AccessTokenInterface; +use Overtrue\Socialite\ProviderInterface; +use Overtrue\Socialite\User; + +/** + * Class LinkedinProvider. + * + * @see https://developer.linkedin.com/docs/oauth2 [Authenticating with OAuth 2.0] + */ +class LinkedinProvider extends AbstractProvider implements ProviderInterface +{ + /** + * The scopes being requested. + * + * @var array + */ + protected $scopes = ['r_liteprofile', 'r_emailaddress']; + + /** + * {@inheritdoc} + */ + protected function getAuthUrl($state) + { + return $this->buildAuthUrlFromBase('https://www.linkedin.com/oauth/v2/authorization', $state); + } + + /** + * Get the access token for the given code. + * + * @param string $code + * + * @return \Overtrue\Socialite\AccessToken + */ + public function getAccessToken($code) + { + $response = $this->getHttpClient() + ->post($this->getTokenUrl(), ['form_params' => $this->getTokenFields($code)]); + + return $this->parseAccessToken($response->getBody()); + } + + /** + * {@inheritdoc} + */ + protected function getTokenUrl() + { + return 'https://www.linkedin.com/oauth/v2/accessToken'; + } + + /** + * Get the POST fields for the token request. + * + * @param string $code + * + * @return array + */ + protected function getTokenFields($code) + { + return parent::getTokenFields($code) + ['grant_type' => 'authorization_code']; + } + + /** + * {@inheritdoc} + */ + protected function getUserByToken(AccessTokenInterface $token) + { + $basicProfile = $this->getBasicProfile($token); + $emailAddress = $this->getEmailAddress($token); + + return array_merge($basicProfile, $emailAddress); + } + + /** + * Get the basic profile fields for the user. + * + * @param string $token + * + * @return array + */ + protected function getBasicProfile($token) + { + $url = 'https://api.linkedin.com/v2/me?projection=(id,firstName,lastName,profilePicture(displayImage~:playableStreams))'; + + $response = $this->getHttpClient()->get($url, [ + 'headers' => [ + 'Authorization' => 'Bearer '.$token, + 'X-RestLi-Protocol-Version' => '2.0.0', + ], + ]); + + return (array) json_decode($response->getBody(), true); + } + + /** + * Get the email address for the user. + * + * @param string $token + * + * @return array + */ + protected function getEmailAddress($token) + { + $url = 'https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))'; + + $response = $this->getHttpClient()->get($url, [ + 'headers' => [ + 'Authorization' => 'Bearer '.$token, + 'X-RestLi-Protocol-Version' => '2.0.0', + ], + ]); + + return (array) $this->arrayItem(json_decode($response->getBody(), true), 'elements.0.handle~'); + } + + /** + * {@inheritdoc} + */ + protected function mapUserToObject(array $user) + { + $preferredLocale = $this->arrayItem($user, 'firstName.preferredLocale.language').'_'.$this->arrayItem($user, 'firstName.preferredLocale.country'); + $firstName = $this->arrayItem($user, 'firstName.localized.'.$preferredLocale); + $lastName = $this->arrayItem($user, 'lastName.localized.'.$preferredLocale); + $name = $firstName.' '.$lastName; + + $images = (array) $this->arrayItem($user, 'profilePicture.displayImage~.elements', []); + $avatars = array_filter($images, function ($image) { + return $image['data']['com.linkedin.digitalmedia.mediaartifact.StillImage']['storageSize']['width'] === 100; + }); + $avatar = array_shift($avatars); + $originalAvatars = array_filter($images, function ($image) { + return $image['data']['com.linkedin.digitalmedia.mediaartifact.StillImage']['storageSize']['width'] === 800; + }); + $originalAvatar = array_shift($originalAvatars); + + return new User([ + 'id' => $this->arrayItem($user, 'id'), + 'nickname' => $name, + 'name' => $name, + 'email' => $this->arrayItem($user, 'emailAddress'), + 'avatar' => $avatar ? $this->arrayItem($avatar, 'identifiers.0.identifier') : null, + 'avatar_original' => $originalAvatar ? $this->arrayItem($originalAvatar, 'identifiers.0.identifier') : null, + ]); + } + + /** + * Set the user fields to request from LinkedIn. + * + * @param array $fields + * + * @return $this + */ + public function fields(array $fields) + { + $this->fields = $fields; + + return $this; + } + + /** + * Determine if the provider is operating as stateless. + * + * @return bool + */ + protected function isStateless() + { + return true; + } +} diff --git a/vendor/overtrue/socialite/src/Providers/OutlookProvider.php b/vendor/overtrue/socialite/src/Providers/OutlookProvider.php new file mode 100644 index 0000000..18d9fd7 --- /dev/null +++ b/vendor/overtrue/socialite/src/Providers/OutlookProvider.php @@ -0,0 +1,89 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Overtrue\Socialite\Providers; + +use Overtrue\Socialite\AccessTokenInterface; +use Overtrue\Socialite\ProviderInterface; +use Overtrue\Socialite\User; + +/** + * Class OutlookProvider. + */ +class OutlookProvider extends AbstractProvider implements ProviderInterface +{ + /** + * {@inheritdoc} + */ + protected $scopes = ['User.Read']; + + /** + * {@inheritdoc} + */ + protected $scopeSeparator = ' '; + + /** + * {@inheritdoc} + */ + protected function getAuthUrl($state) + { + return $this->buildAuthUrlFromBase('https://login.microsoftonline.com/common/oauth2/v2.0/authorize', $state); + } + + /** + * {@inheritdoc} + */ + protected function getTokenUrl() + { + return 'https://login.microsoftonline.com/common/oauth2/v2.0/token'; + } + + /** + * {@inheritdoc} + */ + protected function getUserByToken(AccessTokenInterface $token) + { + $response = $this->getHttpClient()->get( + 'https://graph.microsoft.com/v1.0/me', + ['headers' => [ + 'Accept' => 'application/json', + 'Authorization' => 'Bearer '.$token->getToken(), + ], + ] + ); + + return json_decode($response->getBody()->getContents(), true); + } + + /** + * {@inheritdoc} + */ + protected function mapUserToObject(array $user) + { + return new User([ + 'id' => $this->arrayItem($user, 'id'), + 'nickname' => null, + 'name' => $this->arrayItem($user, 'displayName'), + 'email' => $this->arrayItem($user, 'userPrincipalName'), + 'avatar' => null, + ]); + } + + /** + * {@inheritdoc} + */ + protected function getTokenFields($code) + { + return array_merge(parent::getTokenFields($code), [ + 'grant_type' => 'authorization_code', + ]); + } +} diff --git a/vendor/overtrue/socialite/src/Providers/QQProvider.php b/vendor/overtrue/socialite/src/Providers/QQProvider.php new file mode 100644 index 0000000..124357e --- /dev/null +++ b/vendor/overtrue/socialite/src/Providers/QQProvider.php @@ -0,0 +1,206 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Overtrue\Socialite\Providers; + +use Overtrue\Socialite\AccessTokenInterface; +use Overtrue\Socialite\ProviderInterface; +use Overtrue\Socialite\User; + +/** + * Class QQProvider. + * + * @see http://wiki.connect.qq.com/oauth2-0%E7%AE%80%E4%BB%8B [QQ - OAuth 2.0 登录QQ] + */ +class QQProvider extends AbstractProvider implements ProviderInterface +{ + /** + * The base url of QQ API. + * + * @var string + */ + protected $baseUrl = 'https://graph.qq.com'; + + /** + * User openid. + * + * @var string + */ + protected $openId; + + /** + * get token(openid) with unionid. + * + * @var bool + */ + protected $withUnionId = false; + + /** + * User unionid. + * + * @var string + */ + protected $unionId; + + /** + * The scopes being requested. + * + * @var array + */ + protected $scopes = ['get_user_info']; + + /** + * The uid of user authorized. + * + * @var int + */ + protected $uid; + + /** + * Get the authentication URL for the provider. + * + * @param string $state + * + * @return string + */ + protected function getAuthUrl($state) + { + return $this->buildAuthUrlFromBase($this->baseUrl.'/oauth2.0/authorize', $state); + } + + /** + * Get the token URL for the provider. + * + * @return string + */ + protected function getTokenUrl() + { + return $this->baseUrl.'/oauth2.0/token'; + } + + /** + * Get the Post fields for the token request. + * + * @param string $code + * + * @return array + */ + protected function getTokenFields($code) + { + return parent::getTokenFields($code) + ['grant_type' => 'authorization_code']; + } + + /** + * Get the access token for the given code. + * + * @param string $code + * + * @return \Overtrue\Socialite\AccessToken + */ + public function getAccessToken($code) + { + $response = $this->getHttpClient()->get($this->getTokenUrl(), [ + 'query' => $this->getTokenFields($code), + ]); + + return $this->parseAccessToken($response->getBody()->getContents()); + } + + /** + * Get the access token from the token response body. + * + * @param string $body + * + * @return \Overtrue\Socialite\AccessToken + */ + public function parseAccessToken($body) + { + parse_str($body, $token); + + return parent::parseAccessToken($token); + } + + /** + * @return self + */ + public function withUnionId() + { + $this->withUnionId = true; + + return $this; + } + + /** + * Get the raw user for the given access token. + * + * @param \Overtrue\Socialite\AccessTokenInterface $token + * + * @return array + */ + protected function getUserByToken(AccessTokenInterface $token) + { + $url = $this->baseUrl.'/oauth2.0/me?access_token='.$token->getToken(); + $this->withUnionId && $url .= '&unionid=1'; + + $response = $this->getHttpClient()->get($url); + + $me = json_decode($this->removeCallback($response->getBody()->getContents()), true); + $this->openId = $me['openid']; + $this->unionId = isset($me['unionid']) ? $me['unionid'] : ''; + + $queries = [ + 'access_token' => $token->getToken(), + 'openid' => $this->openId, + 'oauth_consumer_key' => $this->getConfig()->get('client_id'), + ]; + + $response = $this->getHttpClient()->get($this->baseUrl.'/user/get_user_info?'.http_build_query($queries)); + + return json_decode($this->removeCallback($response->getBody()->getContents()), true); + } + + /** + * Map the raw user array to a Socialite User instance. + * + * @param array $user + * + * @return \Overtrue\Socialite\User + */ + protected function mapUserToObject(array $user) + { + return new User([ + 'id' => $this->openId, + 'unionid' => $this->unionId, + 'nickname' => $this->arrayItem($user, 'nickname'), + 'name' => $this->arrayItem($user, 'nickname'), + 'email' => $this->arrayItem($user, 'email'), + 'avatar' => $this->arrayItem($user, 'figureurl_qq_2'), + ]); + } + + /** + * Remove the fucking callback parentheses. + * + * @param string $response + * + * @return string + */ + protected function removeCallback($response) + { + if (false !== strpos($response, 'callback')) { + $lpos = strpos($response, '('); + $rpos = strrpos($response, ')'); + $response = substr($response, $lpos + 1, $rpos - $lpos - 1); + } + + return $response; + } +} diff --git a/vendor/overtrue/socialite/src/Providers/TaobaoProvider.php b/vendor/overtrue/socialite/src/Providers/TaobaoProvider.php new file mode 100644 index 0000000..4daacd9 --- /dev/null +++ b/vendor/overtrue/socialite/src/Providers/TaobaoProvider.php @@ -0,0 +1,242 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Overtrue\Socialite\Providers; + +use Overtrue\Socialite\AccessTokenInterface; +use Overtrue\Socialite\ProviderInterface; +use Overtrue\Socialite\User; + +/** + * Class TaobaoProvider. + * + * @author mechono + * + * @see https://open.taobao.com/doc.htm?docId=102635&docType=1&source=search [Taobao - OAuth 2.0 授权登录] + */ +class TaobaoProvider extends AbstractProvider implements ProviderInterface +{ + /** + * The base url of Taobao API. + * + * @var string + */ + protected $baseUrl = 'https://oauth.taobao.com'; + + /** + * Taobao API service URL address. + * + * @var string + */ + protected $gatewayUrl = 'https://eco.taobao.com/router/rest'; + + /** + * The API version for the request. + * + * @var string + */ + protected $version = '2.0'; + + /** + * @var string + */ + protected $format = 'json'; + + /** + * @var string + */ + protected $signMethod = 'md5'; + + /** + * Web 对应 PC 端(淘宝 logo )浏览器页面样式;Tmall 对应天猫的浏览器页面样式;Wap 对应无线端的浏览器页面样式。 + */ + protected $view = 'web'; + + /** + * The scopes being requested. + * + * @var array + */ + protected $scopes = ['user_info']; + + /** + * Get the authentication URL for the provider. + * + * @param string $state + * + * @return string + */ + protected function getAuthUrl($state) + { + return $this->buildAuthUrlFromBase($this->baseUrl.'/authorize', $state); + } + + /** + * 获取授权码接口参数. + * + * @param string|null $state + * + * @return array + */ + public function getCodeFields($state = null) + { + $fields = [ + 'client_id' => $this->getConfig()->get('client_id'), + 'redirect_uri' => $this->redirectUrl, + 'view' => $this->view, + 'response_type' => 'code', + ]; + + if ($this->usesState()) { + $fields['state'] = $state; + } + + return $fields; + } + + /** + * Get the token URL for the provider. + * + * @return string + */ + protected function getTokenUrl() + { + return $this->baseUrl.'/token'; + } + + /** + * Get the Post fields for the token request. + * + * @param string $code + * + * @return array + */ + protected function getTokenFields($code) + { + return parent::getTokenFields($code) + ['grant_type' => 'authorization_code', 'view' => $this->view]; + } + + /** + * Get the access token for the given code. + * + * @param string $code + * + * @return \Overtrue\Socialite\AccessToken + */ + public function getAccessToken($code) + { + $response = $this->getHttpClient()->post($this->getTokenUrl(), [ + 'query' => $this->getTokenFields($code), + ]); + + return $this->parseAccessToken($response->getBody()->getContents()); + } + + /** + * Get the access token from the token response body. + * + * @param string $body + * + * @return \Overtrue\Socialite\AccessToken + */ + public function parseAccessToken($body) + { + return parent::parseAccessToken($body); + } + + /** + * Get the raw user for the given access token. + * + * @param \Overtrue\Socialite\AccessTokenInterface $token + * + * @return array + */ + protected function getUserByToken(AccessTokenInterface $token) + { + $response = $this->getHttpClient()->post($this->getUserInfoUrl($this->gatewayUrl, $token)); + + return json_decode($response->getBody(), true); + } + + /** + * Map the raw user array to a Socialite User instance. + * + * @param array $user + * + * @return \Overtrue\Socialite\User + */ + protected function mapUserToObject(array $user) + { + return new User([ + 'id' => $this->arrayItem($user, 'open_id'), + 'nickname' => $this->arrayItem($user, 'nick'), + 'name' => $this->arrayItem($user, 'nick'), + 'avatar' => $this->arrayItem($user, 'avatar'), + ]); + } + + /** + * @param $params + * + * @return string + */ + protected function generateSign($params) + { + ksort($params); + + $stringToBeSigned = $this->getConfig()->get('client_secret'); + + foreach ($params as $k => $v) { + if (!is_array($v) && '@' != substr($v, 0, 1)) { + $stringToBeSigned .= "$k$v"; + } + } + + $stringToBeSigned .= $this->getConfig()->get('client_secret'); + + return strtoupper(md5($stringToBeSigned)); + } + + /** + * @param \Overtrue\Socialite\AccessTokenInterface $token + * @param array $apiFields + * + * @return array + */ + protected function getPublicFields(AccessTokenInterface $token, array $apiFields = []) + { + $fields = [ + 'app_key' => $this->getConfig()->get('client_id'), + 'sign_method' => $this->signMethod, + 'session' => $token->getToken(), + 'timestamp' => date('Y-m-d H:i:s'), + 'v' => $this->version, + 'format' => $this->format, + ]; + + $fields = array_merge($apiFields, $fields); + $fields['sign'] = $this->generateSign($fields); + + return $fields; + } + + /** + * {@inheritdoc}. + */ + protected function getUserInfoUrl($url, AccessTokenInterface $token) + { + $apiFields = ['method' => 'taobao.miniapp.userInfo.get']; + + $query = http_build_query($this->getPublicFields($token, $apiFields), '', '&', $this->encodingType); + + return $url.'?'.$query; + } +} diff --git a/vendor/overtrue/socialite/src/Providers/WeChatProvider.php b/vendor/overtrue/socialite/src/Providers/WeChatProvider.php new file mode 100644 index 0000000..0b76532 --- /dev/null +++ b/vendor/overtrue/socialite/src/Providers/WeChatProvider.php @@ -0,0 +1,234 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Overtrue\Socialite\Providers; + +use Overtrue\Socialite\AccessTokenInterface; +use Overtrue\Socialite\InvalidArgumentException; +use Overtrue\Socialite\ProviderInterface; +use Overtrue\Socialite\User; +use Overtrue\Socialite\WeChatComponentInterface; + +/** + * Class WeChatProvider. + * + * @see http://mp.weixin.qq.com/wiki/9/01f711493b5a02f24b04365ac5d8fd95.html [WeChat - 公众平台OAuth文档] + * @see https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419316505&token=&lang=zh_CN [网站应用微信登录开发指南] + */ +class WeChatProvider extends AbstractProvider implements ProviderInterface +{ + /** + * The base url of WeChat API. + * + * @var string + */ + protected $baseUrl = 'https://api.weixin.qq.com/sns'; + + /** + * {@inheritdoc}. + */ + protected $openId; + + /** + * {@inheritdoc}. + */ + protected $scopes = ['snsapi_login']; + + /** + * Indicates if the session state should be utilized. + * + * @var bool + */ + protected $stateless = true; + + /** + * Return country code instead of country name. + * + * @var bool + */ + protected $withCountryCode = false; + + /** + * @var WeChatComponentInterface + */ + protected $component; + + /** + * Return country code instead of country name. + * + * @return $this + */ + public function withCountryCode() + { + $this->withCountryCode = true; + + return $this; + } + + /** + * WeChat OpenPlatform 3rd component. + * + * @param WeChatComponentInterface $component + * + * @return $this + */ + public function component(WeChatComponentInterface $component) + { + $this->scopes = ['snsapi_base']; + + $this->component = $component; + + return $this; + } + + /** + * {@inheritdoc}. + */ + public function getAccessToken($code) + { + $response = $this->getHttpClient()->get($this->getTokenUrl(), [ + 'headers' => ['Accept' => 'application/json'], + 'query' => $this->getTokenFields($code), + ]); + + return $this->parseAccessToken($response->getBody()); + } + + /** + * {@inheritdoc}. + */ + protected function getAuthUrl($state) + { + $path = 'oauth2/authorize'; + + if (in_array('snsapi_login', $this->scopes)) { + $path = 'qrconnect'; + } + + return $this->buildAuthUrlFromBase("https://open.weixin.qq.com/connect/{$path}", $state); + } + + /** + * {@inheritdoc}. + */ + protected function buildAuthUrlFromBase($url, $state) + { + $query = http_build_query($this->getCodeFields($state), '', '&', $this->encodingType); + + return $url.'?'.$query.'#wechat_redirect'; + } + + /** + * {@inheritdoc}. + */ + protected function getCodeFields($state = null) + { + if ($this->component) { + $this->with(array_merge($this->parameters, ['component_appid' => $this->component->getAppId()])); + } + + return array_merge([ + 'appid' => $this->getConfig()->get('client_id'), + 'redirect_uri' => $this->redirectUrl, + 'response_type' => 'code', + 'scope' => $this->formatScopes($this->scopes, $this->scopeSeparator), + 'state' => $state ?: md5(time()), + 'connect_redirect' => 1, + ], $this->parameters); + } + + /** + * {@inheritdoc}. + */ + protected function getTokenUrl() + { + if ($this->component) { + return $this->baseUrl.'/oauth2/component/access_token'; + } + + return $this->baseUrl.'/oauth2/access_token'; + } + + /** + * {@inheritdoc}. + */ + protected function getUserByToken(AccessTokenInterface $token) + { + $scopes = explode(',', $token->getAttribute('scope', '')); + + if (in_array('snsapi_base', $scopes)) { + return $token->toArray(); + } + + if (empty($token['openid'])) { + throw new InvalidArgumentException('openid of AccessToken is required.'); + } + + $language = $this->withCountryCode ? null : (isset($this->parameters['lang']) ? $this->parameters['lang'] : 'zh_CN'); + + $response = $this->getHttpClient()->get($this->baseUrl.'/userinfo', [ + 'query' => array_filter([ + 'access_token' => $token->getToken(), + 'openid' => $token['openid'], + 'lang' => $language, + ]), + ]); + + return json_decode($response->getBody(), true); + } + + /** + * {@inheritdoc}. + */ + protected function mapUserToObject(array $user) + { + return new User([ + 'id' => $this->arrayItem($user, 'openid'), + 'name' => $this->arrayItem($user, 'nickname'), + 'nickname' => $this->arrayItem($user, 'nickname'), + 'avatar' => $this->arrayItem($user, 'headimgurl'), + 'email' => null, + ]); + } + + /** + * {@inheritdoc}. + */ + protected function getTokenFields($code) + { + return array_filter([ + 'appid' => $this->getConfig()->get('client_id'), + 'secret' => $this->getConfig()->get('client_secret'), + 'component_appid' => $this->component ? $this->component->getAppId() : null, + 'component_access_token' => $this->component ? $this->component->getToken() : null, + 'code' => $code, + 'grant_type' => 'authorization_code', + ]); + } + + /** + * Remove the fucking callback parentheses. + * + * @param mixed $response + * + * @return string + */ + protected function removeCallback($response) + { + if (false !== strpos($response, 'callback')) { + $lpos = strpos($response, '('); + $rpos = strrpos($response, ')'); + $response = substr($response, $lpos + 1, $rpos - $lpos - 1); + } + + return $response; + } +} diff --git a/vendor/overtrue/socialite/src/Providers/WeWorkProvider.php b/vendor/overtrue/socialite/src/Providers/WeWorkProvider.php new file mode 100644 index 0000000..7efde33 --- /dev/null +++ b/vendor/overtrue/socialite/src/Providers/WeWorkProvider.php @@ -0,0 +1,214 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Overtrue\Socialite\Providers; + +use Overtrue\Socialite\AccessTokenInterface; +use Overtrue\Socialite\ProviderInterface; +use Overtrue\Socialite\User; + +/** + * Class WeWorkProvider. + * + * @author mingyoung + */ +class WeWorkProvider extends AbstractProvider implements ProviderInterface +{ + /** + * @var string + */ + protected $agentId; + + /** + * @var bool + */ + protected $detailed = false; + + /** + * Set agent id. + * + * @param string $agentId + * + * @return $this + */ + public function setAgentId($agentId) + { + $this->agentId = $agentId; + + return $this; + } + + /** + * @param string $agentId + * + * @return $this + */ + public function agent($agentId) + { + return $this->setAgentId($agentId); + } + + /** + * Return user details. + * + * @return $this + */ + public function detailed() + { + $this->detailed = true; + + return $this; + } + + /** + * @param string $state + * + * @return string + */ + protected function getAuthUrl($state) + { + // 网页授权登录 + if (!empty($this->scopes)) { + return $this->getOAuthUrl($state); + } + + // 第三方网页应用登录(扫码登录) + return $this->getQrConnectUrl($state); + } + + /** + * OAuth url. + * + * @param string $state + * + * @return string + */ + protected function getOAuthUrl($state) + { + $queries = [ + 'appid' => $this->getConfig()->get('client_id'), + 'redirect_uri' => $this->redirectUrl, + 'response_type' => 'code', + 'scope' => $this->formatScopes($this->scopes, $this->scopeSeparator), + 'agentid' => $this->agentId, + 'state' => $state, + ]; + + return sprintf('https://open.weixin.qq.com/connect/oauth2/authorize?%s#wechat_redirect', http_build_query($queries)); + } + + /** + * Qr connect url. + * + * @param string $state + * + * @return string + */ + protected function getQrConnectUrl($state) + { + $queries = [ + 'appid' => $this->getConfig()->get('client_id'), + 'agentid' => $this->agentId, + 'redirect_uri' => $this->redirectUrl, + 'state' => $state, + ]; + + return 'https://open.work.weixin.qq.com/wwopen/sso/qrConnect?'.http_build_query($queries); + } + + protected function getTokenUrl() + { + return null; + } + + /** + * @param \Overtrue\Socialite\AccessTokenInterface $token + * + * @return mixed + */ + protected function getUserByToken(AccessTokenInterface $token) + { + $userInfo = $this->getUserInfo($token); + + if ($this->detailed && isset($userInfo['user_ticket'])) { + return $this->getUserDetail($token, $userInfo['user_ticket']); + } + + $this->detailed = false; + + return $userInfo; + } + + /** + * Get user base info. + * + * @param \Overtrue\Socialite\AccessTokenInterface $token + * + * @return mixed + */ + protected function getUserInfo(AccessTokenInterface $token) + { + $response = $this->getHttpClient()->get('https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo', [ + 'query' => array_filter([ + 'access_token' => $token->getToken(), + 'code' => $this->getCode(), + ]), + ]); + + return json_decode($response->getBody(), true); + } + + /** + * Get user detail info. + * + * @param \Overtrue\Socialite\AccessTokenInterface $token + * @param $ticket + * + * @return mixed + */ + protected function getUserDetail(AccessTokenInterface $token, $ticket) + { + $response = $this->getHttpClient()->post('https://qyapi.weixin.qq.com/cgi-bin/user/getuserdetail', [ + 'query' => [ + 'access_token' => $token->getToken(), + ], + 'json' => [ + 'user_ticket' => $ticket, + ], + ]); + + return json_decode($response->getBody(), true); + } + + /** + * @param array $user + * + * @return \Overtrue\Socialite\User + */ + protected function mapUserToObject(array $user) + { + if ($this->detailed) { + return new User([ + 'id' => $this->arrayItem($user, 'userid'), + 'name' => $this->arrayItem($user, 'name'), + 'avatar' => $this->arrayItem($user, 'avatar'), + 'email' => $this->arrayItem($user, 'email'), + ]); + } + + return new User(array_filter([ + 'id' => $this->arrayItem($user, 'UserId') ?: $this->arrayItem($user, 'OpenId'), + 'userId' => $this->arrayItem($user, 'UserId'), + 'openid' => $this->arrayItem($user, 'OpenId'), + 'deviceId' => $this->arrayItem($user, 'DeviceId'), + ])); + } +} diff --git a/vendor/overtrue/socialite/src/Providers/WeiboProvider.php b/vendor/overtrue/socialite/src/Providers/WeiboProvider.php new file mode 100644 index 0000000..47ec56d --- /dev/null +++ b/vendor/overtrue/socialite/src/Providers/WeiboProvider.php @@ -0,0 +1,126 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Overtrue\Socialite\Providers; + +use Overtrue\Socialite\AccessTokenInterface; +use Overtrue\Socialite\ProviderInterface; +use Overtrue\Socialite\User; + +/** + * Class WeiboProvider. + * + * @see http://open.weibo.com/wiki/%E6%8E%88%E6%9D%83%E6%9C%BA%E5%88%B6%E8%AF%B4%E6%98%8E [OAuth 2.0 授权机制说明] + */ +class WeiboProvider extends AbstractProvider implements ProviderInterface +{ + /** + * The base url of Weibo API. + * + * @var string + */ + protected $baseUrl = 'https://api.weibo.com'; + + /** + * The API version for the request. + * + * @var string + */ + protected $version = '2'; + + /** + * The scopes being requested. + * + * @var array + */ + protected $scopes = ['email']; + + /** + * The uid of user authorized. + * + * @var int + */ + protected $uid; + + /** + * Get the authentication URL for the provider. + * + * @param string $state + * + * @return string + */ + protected function getAuthUrl($state) + { + return $this->buildAuthUrlFromBase($this->baseUrl.'/oauth2/authorize', $state); + } + + /** + * Get the token URL for the provider. + * + * @return string + */ + protected function getTokenUrl() + { + return $this->baseUrl.'/'.$this->version.'/oauth2/access_token'; + } + + /** + * Get the Post fields for the token request. + * + * @param string $code + * + * @return array + */ + protected function getTokenFields($code) + { + return parent::getTokenFields($code) + ['grant_type' => 'authorization_code']; + } + + /** + * Get the raw user for the given access token. + * + * @param \Overtrue\Socialite\AccessTokenInterface $token + * + * @return array + */ + protected function getUserByToken(AccessTokenInterface $token) + { + $response = $this->getHttpClient()->get($this->baseUrl.'/'.$this->version.'/users/show.json', [ + 'query' => [ + 'uid' => $token['uid'], + 'access_token' => $token->getToken(), + ], + 'headers' => [ + 'Accept' => 'application/json', + ], + ]); + + return json_decode($response->getBody(), true); + } + + /** + * Map the raw user array to a Socialite User instance. + * + * @param array $user + * + * @return \Overtrue\Socialite\User + */ + protected function mapUserToObject(array $user) + { + return new User([ + 'id' => $this->arrayItem($user, 'id'), + 'nickname' => $this->arrayItem($user, 'screen_name'), + 'name' => $this->arrayItem($user, 'name'), + 'email' => $this->arrayItem($user, 'email'), + 'avatar' => $this->arrayItem($user, 'avatar_large'), + ]); + } +} diff --git a/vendor/overtrue/socialite/src/SocialiteManager.php b/vendor/overtrue/socialite/src/SocialiteManager.php new file mode 100644 index 0000000..cc109ab --- /dev/null +++ b/vendor/overtrue/socialite/src/SocialiteManager.php @@ -0,0 +1,251 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Overtrue\Socialite; + +use Closure; +use InvalidArgumentException; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Session\Session; + +/** + * Class SocialiteManager. + */ +class SocialiteManager implements FactoryInterface +{ + /** + * The configuration. + * + * @var \Overtrue\Socialite\Config + */ + protected $config; + + /** + * The request instance. + * + * @var Request + */ + protected $request; + + /** + * The registered custom driver creators. + * + * @var array + */ + protected $customCreators = []; + + /** + * The initial drivers. + * + * @var array + */ + protected $initialDrivers = [ + 'facebook' => 'Facebook', + 'github' => 'GitHub', + 'google' => 'Google', + 'linkedin' => 'Linkedin', + 'weibo' => 'Weibo', + 'qq' => 'QQ', + 'wechat' => 'WeChat', + 'douban' => 'Douban', + 'wework' => 'WeWork', + 'outlook' => 'Outlook', + 'douyin' => 'DouYin', + 'taobao' => 'Taobao', + 'feishu' => 'FeiShu', + ]; + + /** + * The array of created "drivers". + * + * @var ProviderInterface[] + */ + protected $drivers = []; + + /** + * SocialiteManager constructor. + * + * @param array $config + * @param Request|null $request + */ + public function __construct(array $config, Request $request = null) + { + $this->config = new Config($config); + + if ($this->config->has('guzzle')) { + Providers\AbstractProvider::setGuzzleOptions($this->config->get('guzzle')); + } + + if ($request) { + $this->setRequest($request); + } + } + + /** + * Set config instance. + * + * @param \Overtrue\Socialite\Config $config + * + * @return $this + */ + public function config(Config $config) + { + $this->config = $config; + + return $this; + } + + /** + * Get a driver instance. + * + * @param string $driver + * + * @return ProviderInterface + */ + public function driver($driver) + { + $driver = strtolower($driver); + + if (!isset($this->drivers[$driver])) { + $this->drivers[$driver] = $this->createDriver($driver); + } + + return $this->drivers[$driver]; + } + + /** + * @param \Symfony\Component\HttpFoundation\Request $request + * + * @return $this + */ + public function setRequest(Request $request) + { + $this->request = $request; + + return $this; + } + + /** + * @return \Symfony\Component\HttpFoundation\Request + */ + public function getRequest() + { + return $this->request ?: $this->createDefaultRequest(); + } + + /** + * Create a new driver instance. + * + * @param string $driver + * + * @throws \InvalidArgumentException + * + * @return ProviderInterface + */ + protected function createDriver($driver) + { + if (isset($this->customCreators[$driver])) { + return $this->callCustomCreator($driver); + } + + if (isset($this->initialDrivers[$driver])) { + $provider = $this->initialDrivers[$driver]; + $provider = __NAMESPACE__.'\\Providers\\'.$provider.'Provider'; + + return $this->buildProvider($provider, $this->formatConfig($this->config->get($driver))); + } + + throw new InvalidArgumentException("Driver [$driver] not supported."); + } + + /** + * Call a custom driver creator. + * + * @param string $driver + * + * @return ProviderInterface + */ + protected function callCustomCreator($driver) + { + return $this->customCreators[$driver]($this->config); + } + + /** + * Create default request instance. + * + * @return Request + */ + protected function createDefaultRequest() + { + $request = Request::createFromGlobals(); + $session = new Session(); + + $request->setSession($session); + + return $request; + } + + /** + * Register a custom driver creator Closure. + * + * @param string $driver + * @param \Closure $callback + * + * @return $this + */ + public function extend($driver, Closure $callback) + { + $driver = strtolower($driver); + + $this->customCreators[$driver] = $callback; + + return $this; + } + + /** + * Get all of the created "drivers". + * + * @return ProviderInterface[] + */ + public function getDrivers() + { + return $this->drivers; + } + + /** + * Build an OAuth 2 provider instance. + * + * @param string $provider + * @param array $config + * + * @return ProviderInterface + */ + public function buildProvider($provider, $config) + { + return new $provider($this->getRequest(), $config); + } + + /** + * Format the server configuration. + * + * @param array $config + * + * @return array + */ + public function formatConfig(array $config) + { + return array_merge([ + 'identifier' => $config['client_id'], + 'secret' => $config['client_secret'], + 'callback_uri' => $config['redirect'], + ], $config); + } +} diff --git a/vendor/overtrue/socialite/src/User.php b/vendor/overtrue/socialite/src/User.php new file mode 100644 index 0000000..761ac8f --- /dev/null +++ b/vendor/overtrue/socialite/src/User.php @@ -0,0 +1,204 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Overtrue\Socialite; + +use ArrayAccess; +use JsonSerializable; + +/** + * Class User. + */ +class User implements ArrayAccess, UserInterface, JsonSerializable, \Serializable +{ + use HasAttributes; + + /** + * User constructor. + * + * @param array $attributes + */ + public function __construct(array $attributes) + { + $this->attributes = $attributes; + } + + /** + * Get the unique identifier for the user. + * + * @return string + */ + public function getId() + { + return $this->getAttribute('id'); + } + + /** + * Get the username for the user. + * + * @return string + */ + public function getUsername() + { + return $this->getAttribute('username', $this->getId()); + } + + /** + * Get the nickname / username for the user. + * + * @return string + */ + public function getNickname() + { + return $this->getAttribute('nickname'); + } + + /** + * Get the full name of the user. + * + * @return string + */ + public function getName() + { + return $this->getAttribute('name'); + } + + /** + * Get the e-mail address of the user. + * + * @return string + */ + public function getEmail() + { + return $this->getAttribute('email'); + } + + /** + * Get the avatar / image URL for the user. + * + * @return string + */ + public function getAvatar() + { + return $this->getAttribute('avatar'); + } + + /** + * Set the token on the user. + * + * @param \Overtrue\Socialite\AccessTokenInterface $token + * + * @return $this + */ + public function setToken(AccessTokenInterface $token) + { + $this->setAttribute('token', $token->getToken()); + $this->setAttribute('access_token', $token->getToken()); + + if (\is_callable([$token, 'getRefreshToken'])) { + $this->setAttribute('refresh_token', $token->getRefreshToken()); + } + + return $this; + } + + /** + * @param string $provider + * + * @return $this + */ + public function setProviderName($provider) + { + $this->setAttribute('provider', $provider); + + return $this; + } + + /** + * @return string + */ + public function getProviderName() + { + return $this->getAttribute('provider'); + } + + /** + * Get the authorized token. + * + * @return \Overtrue\Socialite\AccessToken + */ + public function getToken() + { + return new AccessToken([ + 'access_token' => $this->getAccessToken(), + 'refresh_token' => $this->getAttribute('refresh_token') + ]); + } + + /** + * Get user access token. + * + * @return string + */ + public function getAccessToken() + { + return $this->getAttribute('token') ?: $this->getAttribute('access_token'); + } + + /** + * Get user refresh token. + * + * @return string + */ + public function getRefreshToken() + { + return $this->getAttribute('refresh_token'); + } + + /** + * Get the original attributes. + * + * @return array + */ + public function getOriginal() + { + return $this->getAttribute('original'); + } + + /** + * {@inheritdoc} + */ + public function jsonSerialize() + { + return $this->attributes; + } + + public function serialize() + { + return serialize($this->attributes); + } + + /** + * Constructs the object. + * + * @see https://php.net/manual/en/serializable.unserialize.php + * + * @param string $serialized

    + * The string representation of the object. + *

    + * + * @since 5.1.0 + */ + public function unserialize($serialized) + { + $this->attributes = unserialize($serialized) ?: []; + } +} diff --git a/vendor/overtrue/socialite/src/UserInterface.php b/vendor/overtrue/socialite/src/UserInterface.php new file mode 100644 index 0000000..1403339 --- /dev/null +++ b/vendor/overtrue/socialite/src/UserInterface.php @@ -0,0 +1,53 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Overtrue\Socialite; + +/** + * Interface UserInterface. + */ +interface UserInterface +{ + /** + * Get the unique identifier for the user. + * + * @return string + */ + public function getId(); + + /** + * Get the nickname / username for the user. + * + * @return string + */ + public function getNickname(); + + /** + * Get the full name of the user. + * + * @return string + */ + public function getName(); + + /** + * Get the e-mail address of the user. + * + * @return string + */ + public function getEmail(); + + /** + * Get the avatar / image URL for the user. + * + * @return string + */ + public function getAvatar(); +} diff --git a/vendor/overtrue/socialite/src/WeChatComponentInterface.php b/vendor/overtrue/socialite/src/WeChatComponentInterface.php new file mode 100644 index 0000000..1754521 --- /dev/null +++ b/vendor/overtrue/socialite/src/WeChatComponentInterface.php @@ -0,0 +1,32 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Overtrue\Socialite; + +/** + * Interface WeChatComponentInterface. + */ +interface WeChatComponentInterface +{ + /** + * Return the open-platform component app id. + * + * @return string + */ + public function getAppId(); + + /** + * Return the open-platform component access token string. + * + * @return string + */ + public function getToken(); +} diff --git a/vendor/overtrue/socialite/tests/OAuthTest.php b/vendor/overtrue/socialite/tests/OAuthTest.php new file mode 100644 index 0000000..64d360b --- /dev/null +++ b/vendor/overtrue/socialite/tests/OAuthTest.php @@ -0,0 +1,243 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +use Mockery as m; +use Overtrue\Socialite\AccessTokenInterface; +use Overtrue\Socialite\Providers\AbstractProvider; +use Overtrue\Socialite\User; +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; + +class OAuthTest extends TestCase +{ + public function tearDown() + { + m::close(); + } + + public function testAbstractProviderBackwardCompatible() + { + $request = Request::create('foo'); + $request->setSession($session = m::mock('Symfony\Component\HttpFoundation\Session\SessionInterface')); + $session->shouldReceive('put')->once(); + $provider = new OAuthTwoTestProviderStub($request, 'client_id', 'client_secret', 'redirect'); + + $this->assertSame('client_id', $provider->getConfig()['client_id']); + $this->assertSame('client_secret', $provider->getConfig()['client_secret']); + $this->assertSame('redirect', $provider->getConfig()['redirect']); + + $response = $provider->redirect(); + + $this->assertInstanceOf('Symfony\Component\HttpFoundation\RedirectResponse', $response); + $this->assertSame('http://auth.url', $response->getTargetUrl()); + } + + public function testRedirectGeneratesTheProperSymfonyRedirectResponse() + { + $request = Request::create('foo'); + $request->setSession($session = m::mock('Symfony\Component\HttpFoundation\Session\SessionInterface')); + $session->shouldReceive('put')->once(); + $provider = new OAuthTwoTestProviderStub( + $request, [ + 'client_id' => 'client_id', + 'client_secret' => 'client_secret', + 'redirect' => 'redirect', + ] + ); + $response = $provider->redirect(); + + $this->assertInstanceOf('Symfony\Component\HttpFoundation\RedirectResponse', $response); + $this->assertSame('http://auth.url', $response->getTargetUrl()); + } + + public function testRedirectUrl() + { + $request = Request::create('foo', 'GET', ['state' => str_repeat('A', 40), 'code' => 'code']); + $request->setSession($session = m::mock('Symfony\Component\HttpFoundation\Session\SessionInterface')); + + $provider = new OAuthTwoTestProviderStub( + $request, [ + 'client_id' => 'client_id', + 'client_secret' => 'client_secret', + ] + ); + $this->assertNull($provider->getRedirectUrl()); + + $provider = new OAuthTwoTestProviderStub( + $request, [ + 'client_id' => 'client_id', + 'client_secret' => 'client_secret', + 'redirect' => 'redirect_uri', + ] + ); + $this->assertSame('redirect_uri', $provider->getRedirectUrl()); + $provider->setRedirectUrl('overtrue.me'); + $this->assertSame('overtrue.me', $provider->getRedirectUrl()); + + $provider->withRedirectUrl('http://overtrue.me'); + $this->assertSame('http://overtrue.me', $provider->getRedirectUrl()); + } + + public function testUserReturnsAUserInstanceForTheAuthenticatedRequest() + { + $request = Request::create('foo', 'GET', ['state' => str_repeat('A', 40), 'code' => 'code']); + $request->setSession($session = m::mock('Symfony\Component\HttpFoundation\Session\SessionInterface')); + + $session->shouldReceive('get')->once()->with('state')->andReturn(str_repeat('A', 40)); + $provider = new OAuthTwoTestProviderStub( + $request, [ + 'client_id' => 'client_id', + 'client_secret' => 'client_secret', + 'redirect' => 'redirect_uri', + ] + ); + $provider->http = m::mock('StdClass'); + $provider->http->shouldReceive('post')->once()->with( + 'http://token.url', + [ + 'headers' => ['Accept' => 'application/json'], + 'form_params' => [ + 'client_id' => 'client_id', + 'client_secret' => 'client_secret', + 'code' => 'code', + 'redirect_uri' => 'redirect_uri', + ], + ] + )->andReturn($response = m::mock('StdClass')); + $response->shouldReceive('getBody')->once()->andReturn('{"access_token":"access_token"}'); + $user = $provider->user(); + + $this->assertInstanceOf('Overtrue\Socialite\User', $user); + $this->assertSame('foo', $user->getId()); + } + + /** + * @expectedException \Overtrue\Socialite\InvalidStateException + */ + public function testExceptionIsThrownIfStateIsInvalid() + { + $request = Request::create('foo', 'GET', ['state' => str_repeat('B', 40), 'code' => 'code']); + $request->setSession($session = m::mock('Symfony\Component\HttpFoundation\Session\SessionInterface')); + $session->shouldReceive('get')->once()->with('state')->andReturn(str_repeat('A', 40)); + $provider = new OAuthTwoTestProviderStub( + $request, [ + 'client_id' => 'client_id', + 'client_secret' => 'client_secret', + 'redirect' => 'redirect', + ] + ); + $user = $provider->user(); + } + + /** + * @expectedException \Overtrue\Socialite\AuthorizeFailedException + * @expectedExceptionMessage Authorize Failed: {"error":"scope is invalid"} + */ + public function testExceptionisThrownIfAuthorizeFailed() + { + $request = Request::create('foo', 'GET', ['state' => str_repeat('A', 40), 'code' => 'code']); + $request->setSession($session = m::mock('Symfony\Component\HttpFoundation\Session\SessionInterface')); + $session->shouldReceive('get')->once()->with('state')->andReturn(str_repeat('A', 40)); + $provider = new OAuthTwoTestProviderStub( + $request, [ + 'client_id' => 'client_id', + 'client_secret' => 'client_secret', + 'redirect' => 'redirect_uri', + ] + ); + $provider->http = m::mock('StdClass'); + $provider->http->shouldReceive('post')->once()->with( + 'http://token.url', + [ + 'headers' => ['Accept' => 'application/json'], + 'form_params' => [ + 'client_id' => 'client_id', + 'client_secret' => 'client_secret', + 'code' => 'code', + 'redirect_uri' => 'redirect_uri', + ], + ] + )->andReturn($response = m::mock('StdClass')); + $response->shouldReceive('getBody')->once()->andReturn('{"error":"scope is invalid"}'); + $user = $provider->user(); + } + + /** + * @expectedException \Overtrue\Socialite\InvalidStateException + */ + public function testExceptionIsThrownIfStateIsNotSet() + { + $request = Request::create('foo', 'GET', ['state' => 'state', 'code' => 'code']); + $request->setSession($session = m::mock('Symfony\Component\HttpFoundation\Session\SessionInterface')); + $session->shouldReceive('get')->once()->with('state'); + $provider = new OAuthTwoTestProviderStub( + $request, [ + 'client_id' => 'client_id', + 'client_secret' => 'client_secret', + 'redirect' => 'redirect', + ] + ); + $user = $provider->user(); + } + + public function testDriverName() + { + $request = Request::create('foo', 'GET', ['state' => 'state', 'code' => 'code']); + $provider = new OAuthTwoTestProviderStub( + $request, [ + 'client_id' => 'client_id', + 'client_secret' => 'client_secret', + 'redirect' => 'redirect', + ] + ); + + $this->assertSame('OAuthTwoTest', $provider->getName()); + } +} + +class OAuthTwoTestProviderStub extends AbstractProvider +{ + public $http; + + protected function getAuthUrl($state) + { + return 'http://auth.url'; + } + + protected function getTokenUrl() + { + return 'http://token.url'; + } + + protected function getUserByToken(AccessTokenInterface $token) + { + return ['id' => 'foo']; + } + + protected function mapUserToObject(array $user) + { + return new User(['id' => $user['id']]); + } + + /** + * Get a fresh instance of the Guzzle HTTP client. + * + * @return \GuzzleHttp\Client + */ + protected function getHttpClient() + { + if ($this->http) { + return $this->http; + } + + return $this->http = m::mock('StdClass'); + } +} diff --git a/vendor/overtrue/socialite/tests/Providers/WeWorkProviderTest.php b/vendor/overtrue/socialite/tests/Providers/WeWorkProviderTest.php new file mode 100644 index 0000000..9780930 --- /dev/null +++ b/vendor/overtrue/socialite/tests/Providers/WeWorkProviderTest.php @@ -0,0 +1,60 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +use Overtrue\Socialite\Providers\WeWorkProvider; +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; + +class WeWorkProviderTest extends TestCase +{ + public function testQrConnect() + { + $response = (new WeWorkProvider(Request::create('foo'), [ + 'client_id' => 'ww100000a5f2191', + 'client_secret' => 'client_secret', + 'redirect' => 'http://www.oa.com', + ])) + ->setAgentId('1000000') + ->stateless() + ->redirect(); + + $this->assertSame('https://open.work.weixin.qq.com/wwopen/sso/qrConnect?appid=ww100000a5f2191&agentid=1000000&redirect_uri=http%3A%2F%2Fwww.oa.com', $response->getTargetUrl()); + } + + public function testOAuthWithAgentId() + { + $response = (new WeWorkProvider(Request::create('foo'), [ + 'client_id' => 'CORPID', + 'client_secret' => 'client_secret', + 'redirect' => 'REDIRECT_URI', + ])) + ->scopes(['snsapi_base']) + ->setAgentId('1000000') + ->stateless() + ->redirect(); + + $this->assertSame('https://open.weixin.qq.com/connect/oauth2/authorize?appid=CORPID&redirect_uri=REDIRECT_URI&response_type=code&scope=snsapi_base&agentid=1000000#wechat_redirect', $response->getTargetUrl()); + } + + public function testOAuthWithoutAgentId() + { + $response = (new WeWorkProvider(Request::create('foo'), [ + 'client_id' => 'CORPID', + 'client_secret' => 'client_secret', + 'redirect' => 'REDIRECT_URI', + ])) + ->scopes(['snsapi_base']) + ->stateless() + ->redirect(); + + $this->assertSame('https://open.weixin.qq.com/connect/oauth2/authorize?appid=CORPID&redirect_uri=REDIRECT_URI&response_type=code&scope=snsapi_base#wechat_redirect', $response->getTargetUrl()); + } +} diff --git a/vendor/overtrue/socialite/tests/UserTest.php b/vendor/overtrue/socialite/tests/UserTest.php new file mode 100644 index 0000000..49adf43 --- /dev/null +++ b/vendor/overtrue/socialite/tests/UserTest.php @@ -0,0 +1,45 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +use Overtrue\Socialite\AccessToken; +use Overtrue\Socialite\User; +use PHPUnit\Framework\TestCase; + +class UserTest extends TestCase +{ + public function testJsonserialize() + { + $this->assertSame('[]', json_encode(new User([]))); + + $this->assertSame('{"token":"mock-token"}', json_encode(new User(['token' => new AccessToken(['access_token' => 'mock-token'])]))); + } + + public function test_it_can_get_refresh_token() + { + $user = new User([ + 'access_token' => 'mock-token', + 'refresh_token' => 'fake_refresh', + ]); + + // 能通过用 User 对象获取 refresh token + $this->assertSame('fake_refresh', $user->getRefreshToken()); + // json 序列化只有 token 字段 + $this->assertSame('{"access_token":"mock-token","refresh_token":"fake_refresh"}', json_encode($user)); + + $user = new User([]); + $user->setToken(new AccessToken([ + 'access_token' => 'mock-token', + 'refresh_token' => 'fake_refresh', + ])); + $this->assertSame('fake_refresh', $user->getRefreshToken()); + $this->assertSame('{"token":"mock-token","access_token":"mock-token","refresh_token":"fake_refresh"}', json_encode($user)); + } +} diff --git a/vendor/overtrue/socialite/tests/WechatProviderTest.php b/vendor/overtrue/socialite/tests/WechatProviderTest.php new file mode 100644 index 0000000..74621df --- /dev/null +++ b/vendor/overtrue/socialite/tests/WechatProviderTest.php @@ -0,0 +1,137 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +use Overtrue\Socialite\Providers\WeChatProvider as RealWeChatProvider; +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; + +class WechatProviderTest extends TestCase +{ + public function testWeChatProviderHasCorrectlyRedirectResponse() + { + $response = (new WeChatProvider(Request::create('foo'), [ + 'client_id' => 'client_id', + 'client_secret' => 'client_secret', + 'redirect' => 'http://localhost/socialite/callback.php', + ]))->redirect(); + + $this->assertInstanceOf('Symfony\Component\HttpFoundation\RedirectResponse', $response); + $this->assertStringStartsWith('https://open.weixin.qq.com/connect/qrconnect', $response->getTargetUrl()); + $this->assertRegExp('/redirect_uri=http%3A%2F%2Flocalhost%2Fsocialite%2Fcallback.php/', $response->getTargetUrl()); + } + + public function testWeChatProviderTokenUrlAndRequestFields() + { + $provider = new WeChatProvider(Request::create('foo'), [ + 'client_id' => 'client_id', + 'client_secret' => 'client_secret', + 'redirect' => 'http://localhost/socialite/callback.php', + ]); + + $this->assertSame('https://api.weixin.qq.com/sns/oauth2/access_token', $provider->tokenUrl()); + $this->assertSame([ + 'appid' => 'client_id', + 'secret' => 'client_secret', + 'code' => 'iloveyou', + 'grant_type' => 'authorization_code', + ], $provider->tokenFields('iloveyou')); + + $this->assertSame([ + 'appid' => 'client_id', + 'redirect_uri' => 'http://localhost/socialite/callback.php', + 'response_type' => 'code', + 'scope' => 'snsapi_login', + 'state' => 'wechat-state', + 'connect_redirect' => 1, + ], $provider->codeFields('wechat-state')); + } + + public function testOpenPlatformComponent() + { + $provider = new WeChatProvider(Request::create('foo'), [ + 'client_id' => 'client_id', + 'client_secret' => null, + 'redirect' => 'redirect-url', + ]); + $provider->component(new WeChatComponent()); + $this->assertSame([ + 'appid' => 'client_id', + 'redirect_uri' => 'redirect-url', + 'response_type' => 'code', + 'scope' => 'snsapi_base', + 'state' => 'state', + 'connect_redirect' => 1, + 'component_appid' => 'component-app-id', + ], $provider->codeFields('state')); + + $this->assertSame([ + 'appid' => 'client_id', + 'component_appid' => 'component-app-id', + 'component_access_token' => 'token', + 'code' => 'simcode', + 'grant_type' => 'authorization_code', + ], $provider->tokenFields('simcode')); + + $this->assertSame('https://api.weixin.qq.com/sns/oauth2/component/access_token', $provider->tokenUrl()); + } + + public function testOpenPlatformComponentWithCustomParameters() + { + $provider = new WeChatProvider(Request::create('foo'), [ + 'client_id' => 'client_id', + 'client_secret' => null, + 'redirect' => 'redirect-url', + ]); + $provider->component(new WeChatComponent()); + $provider->with(['foo' => 'bar']); + + $fields = $provider->codeFields('wechat-state'); + + $this->assertArrayHasKey('foo', $fields); + $this->assertSame('bar', $fields['foo']); + } +} + +trait ProviderTrait +{ + public function tokenUrl() + { + return $this->getTokenUrl(); + } + + public function tokenFields($code) + { + return $this->getTokenFields($code); + } + + public function codeFields($state = null) + { + return $this->getCodeFields($state); + } +} + +class WeChatProvider extends RealWeChatProvider +{ + use ProviderTrait; +} + +class WeChatComponent implements \Overtrue\Socialite\WeChatComponentInterface +{ + public function getAppId() + { + return 'component-app-id'; + } + + public function getToken() + { + return 'token'; + } +} diff --git a/vendor/overtrue/wechat/CHANGELOG.md b/vendor/overtrue/wechat/CHANGELOG.md new file mode 100644 index 0000000..df42aa6 --- /dev/null +++ b/vendor/overtrue/wechat/CHANGELOG.md @@ -0,0 +1,1401 @@ +# Change Log + +## [Unreleased](https://github.com/overtrue/wechat/tree/HEAD) + +[Full Changelog](https://github.com/overtrue/wechat/compare/4.0.0...HEAD) + +**Closed issues:** + +- 能否增加对symfony4的支持 [\#1044](https://github.com/overtrue/wechat/issues/1044) + +## [4.0.0](https://github.com/overtrue/wechat/tree/4.0.0) (2017-12-11) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.21...4.0.0) + +**Closed issues:** + +- filecache.php 文件 createPathIfNeeded\(string $path\) : bool [\#1046](https://github.com/overtrue/wechat/issues/1046) +- 沙箱模式的Notify总是出错:Invalid request payloads. [\#1045](https://github.com/overtrue/wechat/issues/1045) +- 你好我是SwooleDistributed框架的作者 [\#1040](https://github.com/overtrue/wechat/issues/1040) + +## [3.3.21](https://github.com/overtrue/wechat/tree/3.3.21) (2017-12-10) +[Full Changelog](https://github.com/overtrue/wechat/compare/4.0.0-beta.4...3.3.21) + +**Closed issues:** + +- 开启开放平台自动路由后报错 [\#1042](https://github.com/overtrue/wechat/issues/1042) +- 关于3.x升级4.x的问题 [\#1041](https://github.com/overtrue/wechat/issues/1041) +- 获取不了unionid [\#1038](https://github.com/overtrue/wechat/issues/1038) +- authorizer\_refresh\_token刷新问题 [\#1033](https://github.com/overtrue/wechat/issues/1033) +- lumen+swoole无法获取request信息。 [\#1032](https://github.com/overtrue/wechat/issues/1032) +- 上传素材报错, empty post data hint [\#1031](https://github.com/overtrue/wechat/issues/1031) +- 开放平台不支持全网发布接入检测(第三方) [\#1029](https://github.com/overtrue/wechat/issues/1029) +- 公众号模板消息不兼容跳转小程序 [\#1025](https://github.com/overtrue/wechat/issues/1025) +- swoole下无法使用 [\#1017](https://github.com/overtrue/wechat/issues/1017) +- 请教有没有高清素材下载方法? [\#997](https://github.com/overtrue/wechat/issues/997) +- 自动回复多图文素材,错误 [\#996](https://github.com/overtrue/wechat/issues/996) +- xml解释失败 [\#989](https://github.com/overtrue/wechat/issues/989) +- Curl error 77 [\#982](https://github.com/overtrue/wechat/issues/982) +- 3.1.10 H5支付不晓得算不算BUG的BUG [\#968](https://github.com/overtrue/wechat/issues/968) +- 请问是否有遇到微信扫码或内部打开外部网站出现请求2次的情况 [\#963](https://github.com/overtrue/wechat/issues/963) +- 请问4.0何时正式发布? [\#962](https://github.com/overtrue/wechat/issues/962) +- dev-master 不能用于laravel5.1 [\#952](https://github.com/overtrue/wechat/issues/952) +- 请教小程序的模板消息是否支持 [\#920](https://github.com/overtrue/wechat/issues/920) +- 模板消息的颜色设置问题 [\#914](https://github.com/overtrue/wechat/issues/914) +- 英文文档跳转问题 [\#854](https://github.com/overtrue/wechat/issues/854) +- \[4.0\] 功能测试 [\#849](https://github.com/overtrue/wechat/issues/849) +- \[4.0\] 命名变更 [\#743](https://github.com/overtrue/wechat/issues/743) + +**Merged pull requests:** + +- Scrutinizer Auto-Fixes [\#1043](https://github.com/overtrue/wechat/pull/1043) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer)) +- 修复解密小程序转发信息数据\(wx.getShareInfo\)失败的问题 [\#1037](https://github.com/overtrue/wechat/pull/1037) ([yyqqing](https://github.com/yyqqing)) +- 修復微信支付沙盒模式的通知結果本地校驗失敗錯誤。 [\#1036](https://github.com/overtrue/wechat/pull/1036) ([amyuki](https://github.com/amyuki)) +- 修复 verifyTicket 使用不了自定义缓存的问题 [\#1034](https://github.com/overtrue/wechat/pull/1034) ([mingyoung](https://github.com/mingyoung)) +- 🚧 Auto discover extensions. [\#1027](https://github.com/overtrue/wechat/pull/1027) ([mingyoung](https://github.com/mingyoung)) + +## [4.0.0-beta.4](https://github.com/overtrue/wechat/tree/4.0.0-beta.4) (2017-11-21) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.20...4.0.0-beta.4) + +**Closed issues:** + +- Order对象的$attributes中不能传入device\_info参数 [\#1030](https://github.com/overtrue/wechat/issues/1030) +- 默认文件缓存的路径是否可以简单修改? [\#1023](https://github.com/overtrue/wechat/issues/1023) +- 3.3.17 版本获取 token 的问题 [\#1022](https://github.com/overtrue/wechat/issues/1022) +- \[V3\] AccessToken.php:243 [\#1021](https://github.com/overtrue/wechat/issues/1021) + +**Merged pull requests:** + +- more detailed cache key. [\#1028](https://github.com/overtrue/wechat/pull/1028) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#1026](https://github.com/overtrue/wechat/pull/1026) ([mingyoung](https://github.com/mingyoung)) +- Specify the request instance. [\#1024](https://github.com/overtrue/wechat/pull/1024) ([mingyoung](https://github.com/mingyoung)) + +## [3.3.20](https://github.com/overtrue/wechat/tree/3.3.20) (2017-11-13) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.18...3.3.20) + +## [3.3.18](https://github.com/overtrue/wechat/tree/3.3.18) (2017-11-11) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.17...3.3.18) + +**Closed issues:** + +- 临时二维码接口无法生成以字符串为参数的二维码 [\#1020](https://github.com/overtrue/wechat/issues/1020) +- 现金红包出现了500错误,跟进显示http\_error:true [\#1016](https://github.com/overtrue/wechat/issues/1016) +- 4.0 企业微信agent OA的错误 [\#1015](https://github.com/overtrue/wechat/issues/1015) +- 求thinkphp框架demo [\#1010](https://github.com/overtrue/wechat/issues/1010) +- 沙箱模式获取验签 key 时产生无限循环 , 无法正常获取 [\#1009](https://github.com/overtrue/wechat/issues/1009) +- JSSDK里面url导致的invalid signature错误 [\#1002](https://github.com/overtrue/wechat/issues/1002) +- 微信支付沙箱模式下,回调验签错误 [\#998](https://github.com/overtrue/wechat/issues/998) +- 有微信退款回调接口吗? [\#985](https://github.com/overtrue/wechat/issues/985) +- 希望兼容新出的微信H5支付 [\#966](https://github.com/overtrue/wechat/issues/966) +- 小程序生成无限量二维码接口缺少参数 [\#965](https://github.com/overtrue/wechat/issues/965) + +**Merged pull requests:** + +- 查询企业付款接口参数调整,加入企业付款到银行卡接口(RSA 参数加密待完成) [\#1019](https://github.com/overtrue/wechat/pull/1019) ([tianyong90](https://github.com/tianyong90)) +- Token AESKey can be null. [\#1013](https://github.com/overtrue/wechat/pull/1013) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#1012](https://github.com/overtrue/wechat/pull/1012) ([mingyoung](https://github.com/mingyoung)) +- Add Mini-program tester's binding/unbinding feature [\#1011](https://github.com/overtrue/wechat/pull/1011) ([caikeal](https://github.com/caikeal)) +- Apply fixes from StyleCI [\#1008](https://github.com/overtrue/wechat/pull/1008) ([overtrue](https://github.com/overtrue)) +- Apply fixes from StyleCI [\#1007](https://github.com/overtrue/wechat/pull/1007) ([overtrue](https://github.com/overtrue)) +- Added open-platform's mini-program code management [\#1003](https://github.com/overtrue/wechat/pull/1003) ([caikeal](https://github.com/caikeal)) +- Cleanup payment [\#1001](https://github.com/overtrue/wechat/pull/1001) ([mingyoung](https://github.com/mingyoung)) +- Unify get stream. [\#995](https://github.com/overtrue/wechat/pull/995) ([mingyoung](https://github.com/mingyoung)) +- Add appCode `page` param. [\#991](https://github.com/overtrue/wechat/pull/991) ([mingyoung](https://github.com/mingyoung)) + +## [3.3.17](https://github.com/overtrue/wechat/tree/3.3.17) (2017-10-27) +[Full Changelog](https://github.com/overtrue/wechat/compare/4.0.0-beta.3...3.3.17) + +**Closed issues:** + +- open platform component\_verify\_ticket 错误 [\#984](https://github.com/overtrue/wechat/issues/984) +- 请教下载语音后的文件不完整怎么处理? [\#980](https://github.com/overtrue/wechat/issues/980) +- 微信支付 API 调用下单解析缓缓 [\#977](https://github.com/overtrue/wechat/issues/977) +- 是否可以加入微信收款(个人转账版)服务接口 [\#970](https://github.com/overtrue/wechat/issues/970) +- 微信公众号消息加解密方式‘兼容模式’也需要填写‘aes\_key’参数,不能为空 [\#967](https://github.com/overtrue/wechat/issues/967) +- 第三方平台 接收消息一直报错 但是能回复消息 也会提示错误 [\#961](https://github.com/overtrue/wechat/issues/961) +- 中文官网无法访问 [\#960](https://github.com/overtrue/wechat/issues/960) +- laravel队列中使用了SDK报Component verify ticket does not exists. [\#958](https://github.com/overtrue/wechat/issues/958) +- 接口调用次数每日限额清零方法没有? [\#953](https://github.com/overtrue/wechat/issues/953) +- 获取access\_toekn失败之后抛出异常的地方,能够与其他地方统一使用下述这个 resolveResponse 返回数据 [\#951](https://github.com/overtrue/wechat/issues/951) +- 官网挂了 [\#950](https://github.com/overtrue/wechat/issues/950) +- 无法接收到菜单点击事件推送的消息 [\#949](https://github.com/overtrue/wechat/issues/949) +- 请教这个sdk是否可用于android 或者ios 登录? [\#948](https://github.com/overtrue/wechat/issues/948) +- 关于access token 后端分布式部署的中控服务器的问题 [\#947](https://github.com/overtrue/wechat/issues/947) +- 4.0 不支持laravel 5.2? [\#946](https://github.com/overtrue/wechat/issues/946) +- log不能打印出来 [\#945](https://github.com/overtrue/wechat/issues/945) +- EasyWeChat.org域名挂了?? [\#940](https://github.com/overtrue/wechat/issues/940) +- 微信静默授权的时候,页面上老是会显示一段很长的英文Redirecting to http://xxxx,很影响用户体验,有没有什么方法可以去掉,保留空白页,或者允许自定义显示内容 [\#939](https://github.com/overtrue/wechat/issues/939) +- 微信小程序生成二维码(接口B)微信扫描不出来结果 [\#938](https://github.com/overtrue/wechat/issues/938) +- 官网可否支持看老版本的文档? [\#937](https://github.com/overtrue/wechat/issues/937) +- 客服发送消息 收到的中文信息被unicode 编码 [\#935](https://github.com/overtrue/wechat/issues/935) +- 有多个商户时,订单通知的 $payment 怎么创建 [\#934](https://github.com/overtrue/wechat/issues/934) +- console中使用$app-\>user-\>get报错 [\#932](https://github.com/overtrue/wechat/issues/932) +- PC端扫描登录的问题 [\#930](https://github.com/overtrue/wechat/issues/930) +- 关于小程序支付的疑问 [\#912](https://github.com/overtrue/wechat/issues/912) +- 服务商api模式使用可以更加详细吗 [\#653](https://github.com/overtrue/wechat/issues/653) + +**Merged pull requests:** + +- 修正 微信公众号要求 所有接口使用 HTTPS 方式访问 [\#988](https://github.com/overtrue/wechat/pull/988) ([drogjh](https://github.com/drogjh)) +- Apply fixes from StyleCI [\#987](https://github.com/overtrue/wechat/pull/987) ([mingyoung](https://github.com/mingyoung)) +- 修复微信收款(个人转账版)商户添加、查询含有多余字段导致签名失败的问题 [\#986](https://github.com/overtrue/wechat/pull/986) ([chenhaizano](https://github.com/chenhaizano)) +- Add merchant client. [\#983](https://github.com/overtrue/wechat/pull/983) ([mingyoung](https://github.com/mingyoung)) +- Fix PKCS7 unpad issue. [\#981](https://github.com/overtrue/wechat/pull/981) ([mingyoung](https://github.com/mingyoung)) +- 💯 Add unit tests. [\#979](https://github.com/overtrue/wechat/pull/979) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#978](https://github.com/overtrue/wechat/pull/978) ([overtrue](https://github.com/overtrue)) +- Add sub-merchant support. [\#976](https://github.com/overtrue/wechat/pull/976) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#974](https://github.com/overtrue/wechat/pull/974) ([overtrue](https://github.com/overtrue)) +- Apply fixes from StyleCI [\#973](https://github.com/overtrue/wechat/pull/973) ([mingyoung](https://github.com/mingyoung)) +- Refactoring payment [\#972](https://github.com/overtrue/wechat/pull/972) ([mingyoung](https://github.com/mingyoung)) +- Fix request method. [\#964](https://github.com/overtrue/wechat/pull/964) ([mingyoung](https://github.com/mingyoung)) +- MiniProgram template. [\#959](https://github.com/overtrue/wechat/pull/959) ([mingyoung](https://github.com/mingyoung)) +- 企业微信 jssdk ticket [\#954](https://github.com/overtrue/wechat/pull/954) ([mingyoung](https://github.com/mingyoung)) +- Scrutinizer Auto-Fixes [\#944](https://github.com/overtrue/wechat/pull/944) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer)) +- 简化子商户js config [\#943](https://github.com/overtrue/wechat/pull/943) ([HanSon](https://github.com/HanSon)) +- Apply fixes from StyleCI [\#942](https://github.com/overtrue/wechat/pull/942) ([overtrue](https://github.com/overtrue)) +- 支持子商户JS CONFIG生成 [\#941](https://github.com/overtrue/wechat/pull/941) ([HanSon](https://github.com/HanSon)) + +## [4.0.0-beta.3](https://github.com/overtrue/wechat/tree/4.0.0-beta.3) (2017-09-23) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.16...4.0.0-beta.3) + +**Closed issues:** + +- 退款结果通知 [\#858](https://github.com/overtrue/wechat/issues/858) + +**Merged pull requests:** + +- Update Application.php [\#936](https://github.com/overtrue/wechat/pull/936) ([HanSon](https://github.com/HanSon)) + +## [3.3.16](https://github.com/overtrue/wechat/tree/3.3.16) (2017-09-20) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.15...3.3.16) + +**Closed issues:** + +- 希望能增加获取回复数据的方法 [\#929](https://github.com/overtrue/wechat/issues/929) +- 3.3 版本 数据类型不对导致无法运行 [\#928](https://github.com/overtrue/wechat/issues/928) + +**Merged pull requests:** + +- 增加退款回调处理 [\#931](https://github.com/overtrue/wechat/pull/931) ([leo108](https://github.com/leo108)) + +## [3.3.15](https://github.com/overtrue/wechat/tree/3.3.15) (2017-09-13) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.14...3.3.15) + +**Closed issues:** + +- 微信 for windows 发送文件的时候报错 [\#927](https://github.com/overtrue/wechat/issues/927) + +## [3.3.14](https://github.com/overtrue/wechat/tree/3.3.14) (2017-09-13) +[Full Changelog](https://github.com/overtrue/wechat/compare/4.0.0-beta.2...3.3.14) + +**Closed issues:** + +- 请教授权的时候什么方法拿到用户是否关注了本公众号? [\#926](https://github.com/overtrue/wechat/issues/926) + +## [4.0.0-beta.2](https://github.com/overtrue/wechat/tree/4.0.0-beta.2) (2017-09-12) +[Full Changelog](https://github.com/overtrue/wechat/compare/4.0.0-beta.1...4.0.0-beta.2) + +**Closed issues:** + +- readme.md写错了? [\#923](https://github.com/overtrue/wechat/issues/923) +- token验证成功,但还是回复暂时不可用,困扰1个星期多了,真心求助!!!有偿都可以!! [\#922](https://github.com/overtrue/wechat/issues/922) +- 条件判断错了,stripos返回的是“返回在字符串 haystack 中 needle 首次出现的数字位置。”,所以不能直接作为条件判断 [\#915](https://github.com/overtrue/wechat/issues/915) +- README中的链接是否错误 [\#913](https://github.com/overtrue/wechat/issues/913) +- 测试公众号无法接受用户信息 [\#911](https://github.com/overtrue/wechat/issues/911) +- ReadMe文件过期 [\#910](https://github.com/overtrue/wechat/issues/910) +- 开放平台服务,取消授权会有哪些参数过来? [\#909](https://github.com/overtrue/wechat/issues/909) +- token无法验证 [\#908](https://github.com/overtrue/wechat/issues/908) +- laravel 5.4 composer 失败 [\#907](https://github.com/overtrue/wechat/issues/907) +- 开放平台:组件ticket无法通过 [\#904](https://github.com/overtrue/wechat/issues/904) +- 官方网站一直登陆不了,浙江丽水地区 [\#903](https://github.com/overtrue/wechat/issues/903) +- \[4.0\] Pimple\Exception\UnknownIdentifierException [\#901](https://github.com/overtrue/wechat/issues/901) +- 4.0 报错“Your requirements could not be resolved to an installable set of packages.” [\#898](https://github.com/overtrue/wechat/issues/898) + +**Merged pull requests:** + +- 修改通过ticket换取二维码图片地址的逻辑 [\#925](https://github.com/overtrue/wechat/pull/925) ([Gwill](https://github.com/Gwill)) +- make domain more flexible [\#924](https://github.com/overtrue/wechat/pull/924) ([HanSon](https://github.com/HanSon)) +- add code & domain comment [\#921](https://github.com/overtrue/wechat/pull/921) ([HanSon](https://github.com/HanSon)) +- Apply fixes from StyleCI [\#919](https://github.com/overtrue/wechat/pull/919) ([overtrue](https://github.com/overtrue)) +- \[3.1\] Custom PreAuthCode Support [\#918](https://github.com/overtrue/wechat/pull/918) ([freyo](https://github.com/freyo)) +- 修改acess\_token无效时微信返回错误码的判断 [\#916](https://github.com/overtrue/wechat/pull/916) ([blackjune](https://github.com/blackjune)) +- \[4.0\] Add optional 'request' parameter to notify handler methods [\#905](https://github.com/overtrue/wechat/pull/905) ([edwardaa](https://github.com/edwardaa)) +- Apply fixes from StyleCI [\#902](https://github.com/overtrue/wechat/pull/902) ([overtrue](https://github.com/overtrue)) +- Apply fixes from StyleCI [\#897](https://github.com/overtrue/wechat/pull/897) ([overtrue](https://github.com/overtrue)) +- 增加OAuth中Guzzle\Client的配置项的设置 [\#893](https://github.com/overtrue/wechat/pull/893) ([khsing](https://github.com/khsing)) +- Apply fixes from StyleCI [\#887](https://github.com/overtrue/wechat/pull/887) ([overtrue](https://github.com/overtrue)) +- Scrutinizer Auto-Fixes [\#884](https://github.com/overtrue/wechat/pull/884) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer)) + +## [4.0.0-beta.1](https://github.com/overtrue/wechat/tree/4.0.0-beta.1) (2017-08-31) +[Full Changelog](https://github.com/overtrue/wechat/compare/4.0.0-alpha.2...4.0.0-beta.1) + +**Closed issues:** + +- http://easywechat.org/ 网站访问不了了? [\#896](https://github.com/overtrue/wechat/issues/896) +- 关于缓存,请问为什么key中包含appId \* 2,有什么讲究吗? [\#892](https://github.com/overtrue/wechat/issues/892) +- 小程序调用解密程序报-41003错误 [\#891](https://github.com/overtrue/wechat/issues/891) +- 小程序调用加密数据解密时报错,不存在方法 [\#890](https://github.com/overtrue/wechat/issues/890) +- 有关4.0使用文档的问题 [\#883](https://github.com/overtrue/wechat/issues/883) +- \[4.0\] PHP最低版本能否降到7.0 [\#880](https://github.com/overtrue/wechat/issues/880) + +**Merged pull requests:** + +- \[4.0\] Pass proper arguments to the Response constructor [\#895](https://github.com/overtrue/wechat/pull/895) ([edwardaa](https://github.com/edwardaa)) +- Fix baseUrl and json issues. [\#894](https://github.com/overtrue/wechat/pull/894) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#889](https://github.com/overtrue/wechat/pull/889) ([overtrue](https://github.com/overtrue)) +- Scrutinizer Auto-Fixes [\#885](https://github.com/overtrue/wechat/pull/885) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer)) +- Apply fixes from StyleCI [\#882](https://github.com/overtrue/wechat/pull/882) ([overtrue](https://github.com/overtrue)) +- 补充通用卡接口 [\#881](https://github.com/overtrue/wechat/pull/881) ([XiaoLer](https://github.com/XiaoLer)) +- Apply fixes from StyleCI [\#879](https://github.com/overtrue/wechat/pull/879) ([overtrue](https://github.com/overtrue)) +- \[3.1\] Payment/API 没有使用全局的 cache [\#878](https://github.com/overtrue/wechat/pull/878) ([edwardaa](https://github.com/edwardaa)) +- Add JSON\_UNESCAPED\_UNICODE option. [\#874](https://github.com/overtrue/wechat/pull/874) ([mingyoung](https://github.com/mingyoung)) +- update \_\_set\_state magic method to static [\#872](https://github.com/overtrue/wechat/pull/872) ([8090Lambert](https://github.com/8090Lambert)) + +## [4.0.0-alpha.2](https://github.com/overtrue/wechat/tree/4.0.0-alpha.2) (2017-08-20) +[Full Changelog](https://github.com/overtrue/wechat/compare/4.0.0-alpha.1...4.0.0-alpha.2) + +**Closed issues:** + +- 你好,怎么用的 [\#869](https://github.com/overtrue/wechat/issues/869) + +**Merged pull requests:** + +- Tweak dir [\#871](https://github.com/overtrue/wechat/pull/871) ([mingyoung](https://github.com/mingyoung)) +- Fix mini-program guard. [\#870](https://github.com/overtrue/wechat/pull/870) ([mingyoung](https://github.com/mingyoung)) + +## [4.0.0-alpha.1](https://github.com/overtrue/wechat/tree/4.0.0-alpha.1) (2017-08-14) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.13...4.0.0-alpha.1) + +**Closed issues:** + +- 对doctrine/cache依赖的版本锁定 [\#867](https://github.com/overtrue/wechat/issues/867) + +## [3.3.13](https://github.com/overtrue/wechat/tree/3.3.13) (2017-08-13) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.12...3.3.13) + +**Closed issues:** + +- 文档中网页授权实例写的不明确 [\#850](https://github.com/overtrue/wechat/issues/850) +- \[意见\]作者能否提供getTokenFromServer方法扩展从外部第三方获取access\_token [\#837](https://github.com/overtrue/wechat/issues/837) +- invalid credential, access\_token is invalid or not latest [\#808](https://github.com/overtrue/wechat/issues/808) +- \[4.0\] 重构卡券 [\#806](https://github.com/overtrue/wechat/issues/806) +- \[4.0\] 重构 Broadcasting [\#805](https://github.com/overtrue/wechat/issues/805) +- \[4.0\] 变更日志 [\#746](https://github.com/overtrue/wechat/issues/746) + +**Merged pull requests:** + +- Fixed open-platform authorizer server token. [\#866](https://github.com/overtrue/wechat/pull/866) ([mingyoung](https://github.com/mingyoung)) +- payment\ClientTest 优化 [\#865](https://github.com/overtrue/wechat/pull/865) ([tianyong90](https://github.com/tianyong90)) +- Apply fixes from StyleCI [\#864](https://github.com/overtrue/wechat/pull/864) ([overtrue](https://github.com/overtrue)) +- 退款通知处理及相关单元测试 [\#863](https://github.com/overtrue/wechat/pull/863) ([tianyong90](https://github.com/tianyong90)) +- Apply fixes from StyleCI [\#862](https://github.com/overtrue/wechat/pull/862) ([overtrue](https://github.com/overtrue)) +- Update dependence version. [\#861](https://github.com/overtrue/wechat/pull/861) ([mingyoung](https://github.com/mingyoung)) +- Add tests. [\#859](https://github.com/overtrue/wechat/pull/859) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#857](https://github.com/overtrue/wechat/pull/857) ([overtrue](https://github.com/overtrue)) +- Payment 单元测试优化 [\#856](https://github.com/overtrue/wechat/pull/856) ([tianyong90](https://github.com/tianyong90)) +- Apply fixes from StyleCI [\#855](https://github.com/overtrue/wechat/pull/855) ([overtrue](https://github.com/overtrue)) +- lists 方法重命名为 list,相关单元测试调整 [\#853](https://github.com/overtrue/wechat/pull/853) ([tianyong90](https://github.com/tianyong90)) +- Apply fixes from StyleCI [\#852](https://github.com/overtrue/wechat/pull/852) ([overtrue](https://github.com/overtrue)) +- Payment 单元测试及部分问题修复 [\#851](https://github.com/overtrue/wechat/pull/851) ([tianyong90](https://github.com/tianyong90)) +- Apply fixes from StyleCI [\#848](https://github.com/overtrue/wechat/pull/848) ([overtrue](https://github.com/overtrue)) +- 调整 Payment\BaseClient 注入的 $app 类型 [\#847](https://github.com/overtrue/wechat/pull/847) ([tianyong90](https://github.com/tianyong90)) +- array\_merge 方法参数类型转换, type hints [\#846](https://github.com/overtrue/wechat/pull/846) ([tianyong90](https://github.com/tianyong90)) +- Fix oauth. [\#845](https://github.com/overtrue/wechat/pull/845) ([mingyoung](https://github.com/mingyoung)) +- Text message. [\#844](https://github.com/overtrue/wechat/pull/844) ([mingyoung](https://github.com/mingyoung)) +- Rename BaseService -\> BasicService. [\#843](https://github.com/overtrue/wechat/pull/843) ([overtrue](https://github.com/overtrue)) +- Apply fixes from StyleCI [\#842](https://github.com/overtrue/wechat/pull/842) ([overtrue](https://github.com/overtrue)) +- Apply fixes from StyleCI [\#841](https://github.com/overtrue/wechat/pull/841) ([overtrue](https://github.com/overtrue)) +- phpdoc types。 [\#840](https://github.com/overtrue/wechat/pull/840) ([tianyong90](https://github.com/tianyong90)) +- Apply fixes from StyleCI [\#839](https://github.com/overtrue/wechat/pull/839) ([overtrue](https://github.com/overtrue)) +- Apply fixes from StyleCI [\#836](https://github.com/overtrue/wechat/pull/836) ([overtrue](https://github.com/overtrue)) +- Apply fixes from StyleCI [\#835](https://github.com/overtrue/wechat/pull/835) ([overtrue](https://github.com/overtrue)) +- Apply fixes from StyleCI [\#833](https://github.com/overtrue/wechat/pull/833) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#831](https://github.com/overtrue/wechat/pull/831) ([overtrue](https://github.com/overtrue)) + +## [3.3.12](https://github.com/overtrue/wechat/tree/3.3.12) (2017-08-01) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.11...3.3.12) + +**Closed issues:** + +- 能否整合微信开放平台在给出一套demo [\#816](https://github.com/overtrue/wechat/issues/816) +- 请教这个项目的支付部分,尤其是签名和结果回调,是否支持小程序? [\#814](https://github.com/overtrue/wechat/issues/814) +- 微信意图识别接口返回invalid param [\#804](https://github.com/overtrue/wechat/issues/804) +- 返回param invalid [\#803](https://github.com/overtrue/wechat/issues/803) + +**Merged pull requests:** + +- change comment word [\#830](https://github.com/overtrue/wechat/pull/830) ([tianyong90](https://github.com/tianyong90)) +- Fix getTicket. [\#829](https://github.com/overtrue/wechat/pull/829) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#827](https://github.com/overtrue/wechat/pull/827) ([overtrue](https://github.com/overtrue)) +- 修正 HasAttributes Trait 引用错误 [\#825](https://github.com/overtrue/wechat/pull/825) ([tianyong90](https://github.com/tianyong90)) +- Apply fixes from StyleCI [\#824](https://github.com/overtrue/wechat/pull/824) ([overtrue](https://github.com/overtrue)) +- Apply fixes from StyleCI [\#822](https://github.com/overtrue/wechat/pull/822) ([overtrue](https://github.com/overtrue)) +- Apply fixes from StyleCI [\#820](https://github.com/overtrue/wechat/pull/820) ([mingyoung](https://github.com/mingyoung)) +- Add subscribe message. [\#819](https://github.com/overtrue/wechat/pull/819) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#818](https://github.com/overtrue/wechat/pull/818) ([mingyoung](https://github.com/mingyoung)) +- 微信开放平台帐号管理 [\#817](https://github.com/overtrue/wechat/pull/817) ([XiaoLer](https://github.com/XiaoLer)) +- add method in comment [\#813](https://github.com/overtrue/wechat/pull/813) ([HanSon](https://github.com/HanSon)) +- fixed guzzle version [\#812](https://github.com/overtrue/wechat/pull/812) ([HanSon](https://github.com/HanSon)) +- Apply fixes from StyleCI [\#811](https://github.com/overtrue/wechat/pull/811) ([mingyoung](https://github.com/mingyoung)) +- Downgrade to php 7.0 [\#809](https://github.com/overtrue/wechat/pull/809) ([HanSon](https://github.com/HanSon)) + +## [3.3.11](https://github.com/overtrue/wechat/tree/3.3.11) (2017-07-17) +[Full Changelog](https://github.com/overtrue/wechat/compare/4.0.0-alpha1...3.3.11) + +**Closed issues:** + +- 请添加 「退款原因」 参数 [\#802](https://github.com/overtrue/wechat/issues/802) + +## [4.0.0-alpha1](https://github.com/overtrue/wechat/tree/4.0.0-alpha1) (2017-07-17) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.10...4.0.0-alpha1) + +**Closed issues:** + +- Overtrue\Wechat\Media not found [\#801](https://github.com/overtrue/wechat/issues/801) +- 在微信的接口配置时Token 无效,可任意输入 [\#800](https://github.com/overtrue/wechat/issues/800) + +## [3.3.10](https://github.com/overtrue/wechat/tree/3.3.10) (2017-07-13) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.9...3.3.10) + +**Closed issues:** + +- 第三方平台refresh\_token的保存问题 [\#798](https://github.com/overtrue/wechat/issues/798) +- 网页授权共享session已晚 [\#792](https://github.com/overtrue/wechat/issues/792) + +**Merged pull requests:** + +- 临时二维码也是支持scene\_str的,这里补充上 [\#797](https://github.com/overtrue/wechat/pull/797) ([lornewang](https://github.com/lornewang)) +- Apply fixes from StyleCI [\#795](https://github.com/overtrue/wechat/pull/795) ([overtrue](https://github.com/overtrue)) +- add card message type [\#794](https://github.com/overtrue/wechat/pull/794) ([IanGely](https://github.com/IanGely)) +- add staff message type wxcard [\#793](https://github.com/overtrue/wechat/pull/793) ([IanGely](https://github.com/IanGely)) + +## [3.3.9](https://github.com/overtrue/wechat/tree/3.3.9) (2017-07-07) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.8...3.3.9) + +**Closed issues:** + +- \[4.0\] Http 模块 [\#678](https://github.com/overtrue/wechat/issues/678) +- \[4.0\] Http 请求类 [\#582](https://github.com/overtrue/wechat/issues/582) + +**Merged pull requests:** + +- Apply fixes from StyleCI [\#791](https://github.com/overtrue/wechat/pull/791) ([overtrue](https://github.com/overtrue)) +- Add get user portrait method. [\#790](https://github.com/overtrue/wechat/pull/790) ([getive](https://github.com/getive)) +- \[Feature\] Move directories [\#789](https://github.com/overtrue/wechat/pull/789) ([overtrue](https://github.com/overtrue)) +- \[Feature\] Move traits to kernel. [\#788](https://github.com/overtrue/wechat/pull/788) ([overtrue](https://github.com/overtrue)) +- Apply fixes from StyleCI [\#787](https://github.com/overtrue/wechat/pull/787) ([overtrue](https://github.com/overtrue)) +- Apply fixes from StyleCI [\#786](https://github.com/overtrue/wechat/pull/786) ([overtrue](https://github.com/overtrue)) + +## [3.3.8](https://github.com/overtrue/wechat/tree/3.3.8) (2017-07-07) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.7...3.3.8) + +**Closed issues:** + +- $temporary-\>getStream\($media\_id\) 与 file\_get\_contents\(\) 有区别??? [\#742](https://github.com/overtrue/wechat/issues/742) + +## [3.3.7](https://github.com/overtrue/wechat/tree/3.3.7) (2017-07-06) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.6...3.3.7) + +**Closed issues:** + +- 多添加一个$option [\#772](https://github.com/overtrue/wechat/issues/772) +- 消息群发,指定openid群发视频时,微信报错invalid message type hint: \[JUs0Oa0779ge25\] [\#757](https://github.com/overtrue/wechat/issues/757) + +## [3.3.6](https://github.com/overtrue/wechat/tree/3.3.6) (2017-07-06) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.5...3.3.6) + +**Fixed bugs:** + +- 素材管理,如果media\_id不存在会保存网页返回的错误代码 [\#592](https://github.com/overtrue/wechat/issues/592) + +**Closed issues:** + +- https://easywechat.org网站证书刚过期了,知会作者一声 [\#781](https://github.com/overtrue/wechat/issues/781) +- access\_token 是否能不内部主动请求微信 [\#778](https://github.com/overtrue/wechat/issues/778) +- 门店创建API \($poi-\>create\) 建议返回 poi\_id / exception [\#774](https://github.com/overtrue/wechat/issues/774) +- 扩展门店小程序错误 [\#762](https://github.com/overtrue/wechat/issues/762) +- \[4.0\] jssdk 抽出独立模块 [\#754](https://github.com/overtrue/wechat/issues/754) +- \[4.0\] 消息加密解密模块提取到 Kernel [\#753](https://github.com/overtrue/wechat/issues/753) +- 网页能授权但无法获取用户信息,代码跟官方文档一样。 [\#713](https://github.com/overtrue/wechat/issues/713) + +**Merged pull requests:** + +- Feature: BaseService. [\#785](https://github.com/overtrue/wechat/pull/785) ([overtrue](https://github.com/overtrue)) +- Apply fixes from StyleCI [\#784](https://github.com/overtrue/wechat/pull/784) ([overtrue](https://github.com/overtrue)) +- Apply fixes from StyleCI [\#783](https://github.com/overtrue/wechat/pull/783) ([mingyoung](https://github.com/mingyoung)) + +## [3.3.5](https://github.com/overtrue/wechat/tree/3.3.5) (2017-07-04) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.4...3.3.5) + +**Implemented enhancements:** + +- 并发下access\_token存在脏写隐患 [\#696](https://github.com/overtrue/wechat/issues/696) + +**Merged pull requests:** + +- Apply fixes from StyleCI [\#780](https://github.com/overtrue/wechat/pull/780) ([overtrue](https://github.com/overtrue)) + +## [3.3.4](https://github.com/overtrue/wechat/tree/3.3.4) (2017-07-04) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.3...3.3.4) + +**Closed issues:** + +- 网页授权获取用户信息无法打开授权页面 [\#773](https://github.com/overtrue/wechat/issues/773) +- Class 'EasyWechat\Foundation\Application' not found [\#769](https://github.com/overtrue/wechat/issues/769) +- 获取小程序二维码报错 [\#766](https://github.com/overtrue/wechat/issues/766) +- Call to undefined method EasyWeChat\Server\Guard::setRequest\(\) [\#765](https://github.com/overtrue/wechat/issues/765) +- 网页授权问题,提示scopes类型错误 [\#764](https://github.com/overtrue/wechat/issues/764) +- 门店小程序扩展错误问题 [\#763](https://github.com/overtrue/wechat/issues/763) +- 微信开发者平台,全网发布怎么通过 [\#761](https://github.com/overtrue/wechat/issues/761) +- 微信网页授权重复请求报code无效 [\#714](https://github.com/overtrue/wechat/issues/714) + +**Merged pull requests:** + +- 新版客服功能-获取聊天记录 [\#775](https://github.com/overtrue/wechat/pull/775) ([wuwenbao](https://github.com/wuwenbao)) +- Fix mini-program qrcode. [\#768](https://github.com/overtrue/wechat/pull/768) ([mingyoung](https://github.com/mingyoung)) +- Add code comments [\#756](https://github.com/overtrue/wechat/pull/756) ([daxiong123](https://github.com/daxiong123)) + +## [3.3.3](https://github.com/overtrue/wechat/tree/3.3.3) (2017-06-22) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.2...3.3.3) + +**Implemented enhancements:** + +- \[4.0\] Trait HasHttpRequests [\#671](https://github.com/overtrue/wechat/issues/671) +- \[4.0\] 缓存抽象成 trait: InteractsWithCache [\#670](https://github.com/overtrue/wechat/issues/670) +- \[4.0\] 返回值类型可配置 [\#661](https://github.com/overtrue/wechat/issues/661) +- \[4.0\] 报错信息可选 [\#596](https://github.com/overtrue/wechat/issues/596) +- \[4.0\] 简化并完善开发者配置项 [\#584](https://github.com/overtrue/wechat/issues/584) + +**Fixed bugs:** + +- open\_platform.oauth 过早的获取 access token [\#701](https://github.com/overtrue/wechat/issues/701) + +**Closed issues:** + +- 微信网页支付配置生成 [\#751](https://github.com/overtrue/wechat/issues/751) +- configForJSSDKPayment [\#744](https://github.com/overtrue/wechat/issues/744) +- 发现微信上有管理公众号留言的接口,不知道是不是新出的 [\#721](https://github.com/overtrue/wechat/issues/721) +- oauth能获取用户信息,再通过access\_token与用户openid去获取信息,部分用户的信息为空 [\#720](https://github.com/overtrue/wechat/issues/720) +- 接入多个公众号 [\#718](https://github.com/overtrue/wechat/issues/718) +- guzzle curl error28 - 去哪设置默认timeout ? [\#715](https://github.com/overtrue/wechat/issues/715) +- 使用$server-\>getMessage\(\);报错 [\#712](https://github.com/overtrue/wechat/issues/712) +- 怎样从数据库中调取配置 [\#711](https://github.com/overtrue/wechat/issues/711) +- \[4.0\] 支持企业微信 [\#707](https://github.com/overtrue/wechat/issues/707) +- defaultColor does not work. [\#703](https://github.com/overtrue/wechat/issues/703) +- 是否支持H5支付 [\#694](https://github.com/overtrue/wechat/issues/694) +- 生成AccessToken时,似乎没有调用自定义缓存的delete方法 [\#693](https://github.com/overtrue/wechat/issues/693) +- \[4.0\] PSR-6 缓存接口 [\#692](https://github.com/overtrue/wechat/issues/692) +- 微信支付沙盒模式支持配置文件配置 [\#690](https://github.com/overtrue/wechat/issues/690) +- \[4.0\] 优化服务提供器结构 [\#689](https://github.com/overtrue/wechat/issues/689) +- 强制项目不要自动获取AccessToken [\#688](https://github.com/overtrue/wechat/issues/688) +- 小程序解密$encryptedData数据 [\#687](https://github.com/overtrue/wechat/issues/687) +- 微信坑爹timestamp已经解决不需要configForJSSDKPayment改变timestamp中s大小写 [\#686](https://github.com/overtrue/wechat/issues/686) +- \[4.0\] 所有 API 改名为 Client. [\#677](https://github.com/overtrue/wechat/issues/677) +- sandbox\_signkey 过期 [\#675](https://github.com/overtrue/wechat/issues/675) +- 接口配置失败 [\#672](https://github.com/overtrue/wechat/issues/672) +- 下载语音文件偶尔报错:ErrorException: is\_readable\(\) expects parameter 1 to be a valid path [\#667](https://github.com/overtrue/wechat/issues/667) +- 微信支付沙箱地址混乱 [\#665](https://github.com/overtrue/wechat/issues/665) +- 开放平台自动回复出错,提示“该服务号暂时无法提供服务” [\#654](https://github.com/overtrue/wechat/issues/654) +- \[4.0\]自定义微信API的区域接入点 [\#636](https://github.com/overtrue/wechat/issues/636) +- 在命令行使用easywechat如何关闭日志 [\#601](https://github.com/overtrue/wechat/issues/601) +- \[4.0\] PHP 版本最低要求 7.1 [\#586](https://github.com/overtrue/wechat/issues/586) +- \[4.0\] 简化微信 API 请求 [\#583](https://github.com/overtrue/wechat/issues/583) +- \[4.0\] 自定义 endpoint [\#521](https://github.com/overtrue/wechat/issues/521) + +**Merged pull requests:** + +- Apply fixes from StyleCI [\#750](https://github.com/overtrue/wechat/pull/750) ([overtrue](https://github.com/overtrue)) +- Apply fixes from StyleCI [\#749](https://github.com/overtrue/wechat/pull/749) ([overtrue](https://github.com/overtrue)) +- Apply fixes from StyleCI [\#747](https://github.com/overtrue/wechat/pull/747) ([overtrue](https://github.com/overtrue)) +- Apply fixes from StyleCI [\#745](https://github.com/overtrue/wechat/pull/745) ([overtrue](https://github.com/overtrue)) +- Apply fixes from StyleCI [\#740](https://github.com/overtrue/wechat/pull/740) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#737](https://github.com/overtrue/wechat/pull/737) ([mingyoung](https://github.com/mingyoung)) +- 分模块静态调用 [\#734](https://github.com/overtrue/wechat/pull/734) ([mingyoung](https://github.com/mingyoung)) +- Revert "Apply fixes from StyleCI" [\#731](https://github.com/overtrue/wechat/pull/731) ([overtrue](https://github.com/overtrue)) +- Apply fixes from StyleCI [\#730](https://github.com/overtrue/wechat/pull/730) ([overtrue](https://github.com/overtrue)) +- Apply fixes from StyleCI [\#729](https://github.com/overtrue/wechat/pull/729) ([overtrue](https://github.com/overtrue)) +- Revert "Apply fixes from StyleCI" [\#728](https://github.com/overtrue/wechat/pull/728) ([overtrue](https://github.com/overtrue)) +- Apply fixes from StyleCI [\#727](https://github.com/overtrue/wechat/pull/727) ([overtrue](https://github.com/overtrue)) +- 修复Https 请求判断不准 [\#726](https://github.com/overtrue/wechat/pull/726) ([xutl](https://github.com/xutl)) +- Apply fixes from StyleCI [\#725](https://github.com/overtrue/wechat/pull/725) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#724](https://github.com/overtrue/wechat/pull/724) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#723](https://github.com/overtrue/wechat/pull/723) ([mingyoung](https://github.com/mingyoung)) +- Correction notes [\#722](https://github.com/overtrue/wechat/pull/722) ([PersiLiao](https://github.com/PersiLiao)) +- Apply fixes from StyleCI [\#717](https://github.com/overtrue/wechat/pull/717) ([mingyoung](https://github.com/mingyoung)) +- 新增图文消息留言管理接口 [\#716](https://github.com/overtrue/wechat/pull/716) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#710](https://github.com/overtrue/wechat/pull/710) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#709](https://github.com/overtrue/wechat/pull/709) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#708](https://github.com/overtrue/wechat/pull/708) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#706](https://github.com/overtrue/wechat/pull/706) ([overtrue](https://github.com/overtrue)) +- 命令行下不打印日志 [\#705](https://github.com/overtrue/wechat/pull/705) ([mingyoung](https://github.com/mingyoung)) +- add defaultColor [\#704](https://github.com/overtrue/wechat/pull/704) ([damonto](https://github.com/damonto)) +- Fix [\#702](https://github.com/overtrue/wechat/pull/702) ([mingyoung](https://github.com/mingyoung)) +- Add api. [\#700](https://github.com/overtrue/wechat/pull/700) ([mingyoung](https://github.com/mingyoung)) +- Rename method. [\#699](https://github.com/overtrue/wechat/pull/699) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#698](https://github.com/overtrue/wechat/pull/698) ([mingyoung](https://github.com/mingyoung)) +- 修正素材管理中的返回值文档注释,正确的类型应该是集合,而不是字符串。 [\#695](https://github.com/overtrue/wechat/pull/695) ([starlight36](https://github.com/starlight36)) +- Payment sandbox config. [\#691](https://github.com/overtrue/wechat/pull/691) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#684](https://github.com/overtrue/wechat/pull/684) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#683](https://github.com/overtrue/wechat/pull/683) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#682](https://github.com/overtrue/wechat/pull/682) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#681](https://github.com/overtrue/wechat/pull/681) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#680](https://github.com/overtrue/wechat/pull/680) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#679](https://github.com/overtrue/wechat/pull/679) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#676](https://github.com/overtrue/wechat/pull/676) ([mingyoung](https://github.com/mingyoung)) +- checks via composer. [\#673](https://github.com/overtrue/wechat/pull/673) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#668](https://github.com/overtrue/wechat/pull/668) ([overtrue](https://github.com/overtrue)) +- Correct payment sandbox endpoint and add a method to get sandbox sign key [\#666](https://github.com/overtrue/wechat/pull/666) ([skyred](https://github.com/skyred)) + +## [3.3.2](https://github.com/overtrue/wechat/tree/3.3.2) (2017-04-27) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.1...3.3.2) + +**Implemented enhancements:** + +- \[4.0\] Open Platform 模块 [\#587](https://github.com/overtrue/wechat/issues/587) +- \[4.0\] 微信支付 sandbox模式 [\#507](https://github.com/overtrue/wechat/issues/507) + +**Closed issues:** + +- \[4.0\] staff 模块改名为 customer service [\#585](https://github.com/overtrue/wechat/issues/585) + +**Merged pull requests:** + +- Module rename. [\#664](https://github.com/overtrue/wechat/pull/664) ([mingyoung](https://github.com/mingyoung)) +- Merge branch master into branch develop. [\#663](https://github.com/overtrue/wechat/pull/663) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#662](https://github.com/overtrue/wechat/pull/662) ([mingyoung](https://github.com/mingyoung)) +- Fix payment tools API [\#660](https://github.com/overtrue/wechat/pull/660) ([mingyoung](https://github.com/mingyoung)) +- Avoid ambiguity [\#659](https://github.com/overtrue/wechat/pull/659) ([mingyoung](https://github.com/mingyoung)) +- Support Payment Sandbox mode [\#658](https://github.com/overtrue/wechat/pull/658) ([skyred](https://github.com/skyred)) +- Apply fixes from StyleCI [\#656](https://github.com/overtrue/wechat/pull/656) ([overtrue](https://github.com/overtrue)) +- Mini program datacube. [\#655](https://github.com/overtrue/wechat/pull/655) ([mingyoung](https://github.com/mingyoung)) + +## [3.3.1](https://github.com/overtrue/wechat/tree/3.3.1) (2017-04-16) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.0...3.3.1) + +**Closed issues:** + +- 微信第三方平台缓存位置,是否可以在配置文件中自定义 [\#648](https://github.com/overtrue/wechat/issues/648) +- 微信开放平台authorizer token缓存问题 [\#644](https://github.com/overtrue/wechat/issues/644) +- 微信开放平台发起网页授权bug [\#638](https://github.com/overtrue/wechat/issues/638) +- 微信公众号不能回复接收到的消息,日志无报错 [\#637](https://github.com/overtrue/wechat/issues/637) +- \[4.0\]黑名单管理 [\#538](https://github.com/overtrue/wechat/issues/538) + +**Merged pull requests:** + +- optimizes [\#652](https://github.com/overtrue/wechat/pull/652) ([mingyoung](https://github.com/mingyoung)) + +## [3.3.0](https://github.com/overtrue/wechat/tree/3.3.0) (2017-04-13) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.2.7...3.3.0) + +**Closed issues:** + +- 微信接口获取openid是怎么排序的? [\#650](https://github.com/overtrue/wechat/issues/650) +- 缺少网页扫码支付接口 [\#647](https://github.com/overtrue/wechat/issues/647) +- 微信下的单的默认过期时间是多少啊 [\#645](https://github.com/overtrue/wechat/issues/645) +- 在获取用户信息是出错 [\#643](https://github.com/overtrue/wechat/issues/643) +- 调用$app =app\('wechat'\);时报错Use of undefined constant CURLOPT\_IPRESOLVE - assumed 'CURLOPT\_IPRESOLVE' [\#633](https://github.com/overtrue/wechat/issues/633) +- 提示找不到EasyWeChat\Server\Guard::setRequest\(\)方法 [\#626](https://github.com/overtrue/wechat/issues/626) +- 开放平台接收ComponentVerifyTicket,会出现Undefined index: FromUserName [\#623](https://github.com/overtrue/wechat/issues/623) +- 美国移动网络获取不到accessToken [\#610](https://github.com/overtrue/wechat/issues/610) +- 开放平台 APP 微信登录 [\#604](https://github.com/overtrue/wechat/issues/604) + +**Merged pull requests:** + +- Merge from open-platform branch. [\#651](https://github.com/overtrue/wechat/pull/651) ([mingyoung](https://github.com/mingyoung)) +- Update code for open-platform [\#649](https://github.com/overtrue/wechat/pull/649) ([mingyoung](https://github.com/mingyoung)) +- Code cleanup & refactoring. [\#646](https://github.com/overtrue/wechat/pull/646) ([mingyoung](https://github.com/mingyoung)) +- support cash coupon [\#642](https://github.com/overtrue/wechat/pull/642) ([HanSon](https://github.com/HanSon)) +- ♻️ All tests have been namespaced. [\#641](https://github.com/overtrue/wechat/pull/641) ([mingyoung](https://github.com/mingyoung)) +- tweak code. [\#640](https://github.com/overtrue/wechat/pull/640) ([mingyoung](https://github.com/mingyoung)) +- modify oauth property [\#639](https://github.com/overtrue/wechat/pull/639) ([jekst](https://github.com/jekst)) +- Apply fixes from StyleCI [\#635](https://github.com/overtrue/wechat/pull/635) ([overtrue](https://github.com/overtrue)) +- ✨ Blacklist. [\#634](https://github.com/overtrue/wechat/pull/634) ([mingyoung](https://github.com/mingyoung)) +- 🔨 Refactoring for mini-program. [\#632](https://github.com/overtrue/wechat/pull/632) ([mingyoung](https://github.com/mingyoung)) + +## [3.2.7](https://github.com/overtrue/wechat/tree/3.2.7) (2017-03-31) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.2.6...3.2.7) + +**Closed issues:** + +- 不管哪个公众号,只要填写 这个接口地址,都能配置或应用成功,实际上是不成功的,不到怎么找错。。 [\#611](https://github.com/overtrue/wechat/issues/611) + +**Merged pull requests:** + +- 修复一个创建卡券时的 bug, 添加获取微信门店类目表的api [\#631](https://github.com/overtrue/wechat/pull/631) ([Hexor](https://github.com/Hexor)) + +## [3.2.6](https://github.com/overtrue/wechat/tree/3.2.6) (2017-03-31) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.2.5...3.2.6) + +**Closed issues:** + +- 我想大量发模板消息,但send每次都等待返回太慢,有啥解决办法吗? [\#630](https://github.com/overtrue/wechat/issues/630) +- 3.2开放平台缺少authorizer\_token和authorization [\#629](https://github.com/overtrue/wechat/issues/629) +- 微信开发平台接受消息报Invalid request signature bug [\#625](https://github.com/overtrue/wechat/issues/625) +- 图文上传thumb\_media\_id 返回 {"errcode":40007,"errmsg":"invalid media\_id hint: \[\]"} [\#622](https://github.com/overtrue/wechat/issues/622) +- Encryptor基类hack导致小程序的sessionKey base64\_decode失败 [\#614](https://github.com/overtrue/wechat/issues/614) +- 是否有 2.1 升级到最新版的方案? [\#609](https://github.com/overtrue/wechat/issues/609) +- laravel5.3 安装 "overtrue/wechat:~3.1 失败 [\#607](https://github.com/overtrue/wechat/issues/607) +- overtrue/wechat和phpdoc包依赖冲突。 [\#605](https://github.com/overtrue/wechat/issues/605) +- \[bug\]2个问题 [\#597](https://github.com/overtrue/wechat/issues/597) +- 微信第三方平台开发是否只做了一部分? [\#594](https://github.com/overtrue/wechat/issues/594) +- \[4.0\] ServiceProvider 移动到各自模块里 [\#588](https://github.com/overtrue/wechat/issues/588) +- Cannot use EasyWeChat\OpenPlatform\Traits\VerifyTicket as VerifyTicket because the name is already in use [\#579](https://github.com/overtrue/wechat/issues/579) +- 授权state值怎么设置 [\#573](https://github.com/overtrue/wechat/issues/573) +- mini\_app get jscode problem, report appid & secret value is null [\#569](https://github.com/overtrue/wechat/issues/569) +- 小程序生成二维码问题 [\#568](https://github.com/overtrue/wechat/issues/568) + +**Merged pull requests:** + +- Update OpenPlatform AppId [\#624](https://github.com/overtrue/wechat/pull/624) ([jeftom](https://github.com/jeftom)) +- Apply fixes from StyleCI [\#621](https://github.com/overtrue/wechat/pull/621) ([overtrue](https://github.com/overtrue)) +- Apply fixes from StyleCI [\#618](https://github.com/overtrue/wechat/pull/618) ([overtrue](https://github.com/overtrue)) +- Compatible with php5.5 [\#617](https://github.com/overtrue/wechat/pull/617) ([mingyoung](https://github.com/mingyoung)) +- Make the testcase works. [\#616](https://github.com/overtrue/wechat/pull/616) ([mingyoung](https://github.com/mingyoung)) +- Fix mini-program decryptor [\#615](https://github.com/overtrue/wechat/pull/615) ([mingyoung](https://github.com/mingyoung)) +- Missing message handling [\#613](https://github.com/overtrue/wechat/pull/613) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#612](https://github.com/overtrue/wechat/pull/612) ([overtrue](https://github.com/overtrue)) +- 添加卡券创建二维码接口 [\#608](https://github.com/overtrue/wechat/pull/608) ([forecho](https://github.com/forecho)) +- 开放平台大幅重构并且添加测试 [\#606](https://github.com/overtrue/wechat/pull/606) ([tsunamilx](https://github.com/tsunamilx)) +- Update MessageBuilder.php [\#603](https://github.com/overtrue/wechat/pull/603) ([U2Fsd](https://github.com/U2Fsd)) +- 生成 js添加到卡包接口 增加fixed\_begintimestamp、outer\_str字段 [\#602](https://github.com/overtrue/wechat/pull/602) ([gychg](https://github.com/gychg)) +- tests for speed [\#600](https://github.com/overtrue/wechat/pull/600) ([mingyoung](https://github.com/mingyoung)) +- Update test files [\#599](https://github.com/overtrue/wechat/pull/599) ([mingyoung](https://github.com/mingyoung)) +- 允许自定义ticket缓存key [\#598](https://github.com/overtrue/wechat/pull/598) ([XiaoLer](https://github.com/XiaoLer)) +- delete top color [\#595](https://github.com/overtrue/wechat/pull/595) ([HanSon](https://github.com/HanSon)) +- Add payment scan notify handler [\#593](https://github.com/overtrue/wechat/pull/593) ([acgrid](https://github.com/acgrid)) +- Apply fixes from StyleCI [\#591](https://github.com/overtrue/wechat/pull/591) ([overtrue](https://github.com/overtrue)) +- Upgrade packages version to 4.0 [\#590](https://github.com/overtrue/wechat/pull/590) ([reatang](https://github.com/reatang)) +- Move providers to module dir. \#588 [\#589](https://github.com/overtrue/wechat/pull/589) ([overtrue](https://github.com/overtrue)) +- 把OpenPlatform中的组件依赖解耦 [\#581](https://github.com/overtrue/wechat/pull/581) ([reatang](https://github.com/reatang)) + +## [3.2.5](https://github.com/overtrue/wechat/tree/3.2.5) (2017-02-04) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.2.4...3.2.5) + +**Merged pull requests:** + +- fix naming [\#580](https://github.com/overtrue/wechat/pull/580) ([mingyoung](https://github.com/mingyoung)) +- Allow client code configure its own GuzzleHTTP handler [\#578](https://github.com/overtrue/wechat/pull/578) ([acgrid](https://github.com/acgrid)) + +## [3.2.4](https://github.com/overtrue/wechat/tree/3.2.4) (2017-01-24) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.2.3...3.2.4) + +**Closed issues:** + +- 如何在其他框架下使用$app-\>payment-\>handleNotify [\#574](https://github.com/overtrue/wechat/issues/574) +- 前后端分离单页下获取的config,认证失败 [\#565](https://github.com/overtrue/wechat/issues/565) +- 支付签名错误 [\#563](https://github.com/overtrue/wechat/issues/563) + +**Merged pull requests:** + +- Update Authorizer.php [\#577](https://github.com/overtrue/wechat/pull/577) ([ww380459000](https://github.com/ww380459000)) +- 补全通用卡接口 [\#575](https://github.com/overtrue/wechat/pull/575) ([XiaoLer](https://github.com/XiaoLer)) +- require ext-SimpleXML [\#572](https://github.com/overtrue/wechat/pull/572) ([garveen](https://github.com/garveen)) +- fix README Contribution link [\#571](https://github.com/overtrue/wechat/pull/571) ([zhwei](https://github.com/zhwei)) +- Add user data decryption. [\#570](https://github.com/overtrue/wechat/pull/570) ([mingyoung](https://github.com/mingyoung)) +- change request parameter [\#567](https://github.com/overtrue/wechat/pull/567) ([cloudsthere](https://github.com/cloudsthere)) +- 完善小程序代码 [\#566](https://github.com/overtrue/wechat/pull/566) ([mingyoung](https://github.com/mingyoung)) +- 添加小程序支持 [\#564](https://github.com/overtrue/wechat/pull/564) ([mingyoung](https://github.com/mingyoung)) + +## [3.2.3](https://github.com/overtrue/wechat/tree/3.2.3) (2017-01-04) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.2.2...3.2.3) + +**Closed issues:** + +- 文档里的自定义菜单中,group\_id是否为tag\_id的误写? [\#561](https://github.com/overtrue/wechat/issues/561) +- Open Platform有简明的使用文档吗?3ks [\#560](https://github.com/overtrue/wechat/issues/560) +- 刷新access\_token有效期,未发现有相关的封装 [\#540](https://github.com/overtrue/wechat/issues/540) + +**Merged pull requests:** + +- Update Card.php [\#562](https://github.com/overtrue/wechat/pull/562) ([XiaoLer](https://github.com/XiaoLer)) +- Apply fixes from StyleCI [\#559](https://github.com/overtrue/wechat/pull/559) ([overtrue](https://github.com/overtrue)) +- Update API.php [\#558](https://github.com/overtrue/wechat/pull/558) ([drogjh](https://github.com/drogjh)) +- optimized code [\#557](https://github.com/overtrue/wechat/pull/557) ([mingyoung](https://github.com/mingyoung)) + +## [3.2.2](https://github.com/overtrue/wechat/tree/3.2.2) (2016-12-27) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.2.1...3.2.2) + +**Closed issues:** + +- How to get authorize url? [\#555](https://github.com/overtrue/wechat/issues/555) + +**Merged pull requests:** + +- fixed downloadBill method result [\#556](https://github.com/overtrue/wechat/pull/556) ([hidehalo](https://github.com/hidehalo)) +- add config:log.permission for monolog [\#554](https://github.com/overtrue/wechat/pull/554) ([woshizoufeng](https://github.com/woshizoufeng)) +- Improve open platform support. [\#553](https://github.com/overtrue/wechat/pull/553) ([mingyoung](https://github.com/mingyoung)) +- Improve. [\#552](https://github.com/overtrue/wechat/pull/552) ([mingyoung](https://github.com/mingyoung)) +- add $forceRefresh param to js-\>ticket\(\) method [\#551](https://github.com/overtrue/wechat/pull/551) ([leo108](https://github.com/leo108)) + +## [3.2.1](https://github.com/overtrue/wechat/tree/3.2.1) (2016-12-20) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.2.0...3.2.1) + +**Merged pull requests:** + +- 增加小程序用jscode获取用户信息的接口 [\#550](https://github.com/overtrue/wechat/pull/550) ([soone](https://github.com/soone)) + +## [3.2.0](https://github.com/overtrue/wechat/tree/3.2.0) (2016-12-19) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.1.9...3.2.0) + +**Closed issues:** + +- 喵喵喵 [\#545](https://github.com/overtrue/wechat/issues/545) +- HttpException with uploadArticle API [\#544](https://github.com/overtrue/wechat/issues/544) +- 是否有接入小程序的计划 [\#543](https://github.com/overtrue/wechat/issues/543) +- "Call to undefined method Overtrue\Socialite\Providers\WeChat Provider::driver\(\) [\#536](https://github.com/overtrue/wechat/issues/536) +- 服务端Server模块回复音乐消息出错 [\#533](https://github.com/overtrue/wechat/issues/533) +- 用户授权出现The key "access\_token" could not be empty [\#527](https://github.com/overtrue/wechat/issues/527) + +**Merged pull requests:** + +- Apply fixes from StyleCI [\#549](https://github.com/overtrue/wechat/pull/549) ([overtrue](https://github.com/overtrue)) +- 添加摇一摇周边模块 [\#548](https://github.com/overtrue/wechat/pull/548) ([allen05ren](https://github.com/allen05ren)) +- Make some compatible. [\#542](https://github.com/overtrue/wechat/pull/542) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#541](https://github.com/overtrue/wechat/pull/541) ([overtrue](https://github.com/overtrue)) +- 改变了http 中 json 方法的接口, 从而支持 添加 添加 query参数 [\#539](https://github.com/overtrue/wechat/pull/539) ([shoaly](https://github.com/shoaly)) +- 提交 [\#537](https://github.com/overtrue/wechat/pull/537) ([shoaly](https://github.com/shoaly)) +- Apply fixes from StyleCI [\#535](https://github.com/overtrue/wechat/pull/535) ([overtrue](https://github.com/overtrue)) + +## [3.1.9](https://github.com/overtrue/wechat/tree/3.1.9) (2016-12-01) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.1.8...3.1.9) + +**Closed issues:** + +- 还是不懂怎么获取unionid [\#531](https://github.com/overtrue/wechat/issues/531) +- Scope 参数错误或没有 Scope 权限 [\#528](https://github.com/overtrue/wechat/issues/528) +- $\_SERVER\['SERVER\_ADDR'\] 在mac php7中获取不到 [\#520](https://github.com/overtrue/wechat/issues/520) +- 能否永久素材其他类型封装个download方法,跟临时一样 [\#505](https://github.com/overtrue/wechat/issues/505) +- V3.1 JSSDK使用疑惑 [\#503](https://github.com/overtrue/wechat/issues/503) +- 如何加入QQ群 [\#501](https://github.com/overtrue/wechat/issues/501) +- 能否在下一个版本把企业的相关接口整合集成进去 [\#496](https://github.com/overtrue/wechat/issues/496) +- 既然使用了monolog,那么在Application::initializeLogger只使用了文件流的特定形式来记录日志是否合理? [\#494](https://github.com/overtrue/wechat/issues/494) +- configForShareAddress [\#482](https://github.com/overtrue/wechat/issues/482) +- 更新微信文章的时候MatialEasyWeChat\Material,如果设置了show\_pic\_cover和content\_source\_url不会生效 [\#470](https://github.com/overtrue/wechat/issues/470) +- 请问 SDK 是否支持授权接入的公众号接口调用? [\#438](https://github.com/overtrue/wechat/issues/438) +- 通过unionid发送信息。 [\#411](https://github.com/overtrue/wechat/issues/411) +- 【新增】设备管理 [\#77](https://github.com/overtrue/wechat/issues/77) + +**Merged pull requests:** + +- Add support wechat open platform. [\#532](https://github.com/overtrue/wechat/pull/532) ([mingyoung](https://github.com/mingyoung)) +- Applied fixes from StyleCI [\#530](https://github.com/overtrue/wechat/pull/530) ([overtrue](https://github.com/overtrue)) +- 新增硬件设备api [\#529](https://github.com/overtrue/wechat/pull/529) ([soone](https://github.com/soone)) + +## [3.1.8](https://github.com/overtrue/wechat/tree/3.1.8) (2016-11-23) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.1.7...3.1.8) + +**Closed issues:** + +- SCAN 事件会出现无法提供服务 [\#525](https://github.com/overtrue/wechat/issues/525) + +## [3.1.7](https://github.com/overtrue/wechat/tree/3.1.7) (2016-10-26) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.1.6...3.1.7) + +**Closed issues:** + +- preg\_replace unicode 的兼容问题 [\#515](https://github.com/overtrue/wechat/issues/515) + +**Merged pull requests:** + +- support psr-http-message-bridge 1.0 [\#524](https://github.com/overtrue/wechat/pull/524) ([wppd](https://github.com/wppd)) +- Applied fixes from StyleCI [\#523](https://github.com/overtrue/wechat/pull/523) ([overtrue](https://github.com/overtrue)) +- for \#520 [\#522](https://github.com/overtrue/wechat/pull/522) ([jinchun](https://github.com/jinchun)) + +## [3.1.6](https://github.com/overtrue/wechat/tree/3.1.6) (2016-10-19) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.1.5...3.1.6) + +**Closed issues:** + +- PHP Fatal error: Uncaught HttpException [\#517](https://github.com/overtrue/wechat/issues/517) +- 微信支付回调出错 [\#514](https://github.com/overtrue/wechat/issues/514) + +**Merged pull requests:** + +- Fix xml preg replace [\#519](https://github.com/overtrue/wechat/pull/519) ([springjk](https://github.com/springjk)) +- fix the DOC [\#518](https://github.com/overtrue/wechat/pull/518) ([ac1982](https://github.com/ac1982)) + +## [3.1.5](https://github.com/overtrue/wechat/tree/3.1.5) (2016-10-13) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.1.4...3.1.5) + +**Closed issues:** + +- wechat 在 larave l5.3 使用 passport 包下无法安装 [\#513](https://github.com/overtrue/wechat/issues/513) + +**Merged pull requests:** + +- Applied fixes from StyleCI [\#512](https://github.com/overtrue/wechat/pull/512) ([overtrue](https://github.com/overtrue)) + +## [3.1.4](https://github.com/overtrue/wechat/tree/3.1.4) (2016-10-12) +[Full Changelog](https://github.com/overtrue/wechat/compare/2.1.39...3.1.4) + +**Closed issues:** + +- 微信卡券特殊票券创建之后为什么无法更新卡券信息一致提示code非法。 [\#511](https://github.com/overtrue/wechat/issues/511) +- 请添加 「退款方式」 参数 [\#509](https://github.com/overtrue/wechat/issues/509) +- 2.1.40命名空间巨变引发的重大问题\(疑似提错版本了\) [\#508](https://github.com/overtrue/wechat/issues/508) +- 卡券核销、查询建议 [\#506](https://github.com/overtrue/wechat/issues/506) +- 支付重复回调问题 [\#504](https://github.com/overtrue/wechat/issues/504) + +**Merged pull requests:** + +- Changed method doc to the right accepted param type [\#510](https://github.com/overtrue/wechat/pull/510) ([marianoasselborn](https://github.com/marianoasselborn)) +- 增加判断是否有人工客服帐号,避免出现无账号时候,头像为默认头像的情况 [\#502](https://github.com/overtrue/wechat/pull/502) ([hello2t](https://github.com/hello2t)) +- Applied fixes from StyleCI [\#500](https://github.com/overtrue/wechat/pull/500) ([overtrue](https://github.com/overtrue)) +- 为initializeLogger日志初始话函数添加判断分支 [\#499](https://github.com/overtrue/wechat/pull/499) ([403studio](https://github.com/403studio)) + +## [2.1.39](https://github.com/overtrue/wechat/tree/2.1.39) (2016-09-05) +[Full Changelog](https://github.com/overtrue/wechat/compare/2.1.41...2.1.39) + +## [2.1.41](https://github.com/overtrue/wechat/tree/2.1.41) (2016-09-05) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.1.3...2.1.41) + +**Closed issues:** + +- 调用接口次数超过最大限制问题 [\#493](https://github.com/overtrue/wechat/issues/493) +- 微信退款证书报错 Unable to set private key file [\#492](https://github.com/overtrue/wechat/issues/492) +- 微信支付存在问题 [\#489](https://github.com/overtrue/wechat/issues/489) +- 预支付下单 response body 为空 [\#488](https://github.com/overtrue/wechat/issues/488) +- https check issue [\#486](https://github.com/overtrue/wechat/issues/486) + +**Merged pull requests:** + +- update composer.json [\#498](https://github.com/overtrue/wechat/pull/498) ([ac1982](https://github.com/ac1982)) +- use openssl instead of mcrypt [\#497](https://github.com/overtrue/wechat/pull/497) ([ac1982](https://github.com/ac1982)) +- 修复 with 方法带数据的问题 [\#491](https://github.com/overtrue/wechat/pull/491) ([XiaoLer](https://github.com/XiaoLer)) + +## [3.1.3](https://github.com/overtrue/wechat/tree/3.1.3) (2016-08-08) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.1.2...3.1.3) + +**Closed issues:** + +- Laravel中写的最简单的例子在phpunit出错。 [\#485](https://github.com/overtrue/wechat/issues/485) +- 微信的消息回复的FromUserName和ToUserName是不是对调了 [\#484](https://github.com/overtrue/wechat/issues/484) +- 微信红包不能发给别的公众号的用户吗 [\#483](https://github.com/overtrue/wechat/issues/483) +- 用户授权登录问题 [\#481](https://github.com/overtrue/wechat/issues/481) +- cURL error 56: SSLRead\(\) return error -9806 [\#473](https://github.com/overtrue/wechat/issues/473) +- 会员卡开卡字段文档有错误 [\#471](https://github.com/overtrue/wechat/issues/471) +- Getting more done in GitHub with ZenHub [\#439](https://github.com/overtrue/wechat/issues/439) +- 微信支付下单错误 [\#376](https://github.com/overtrue/wechat/issues/376) + +**Merged pull requests:** + +- update the File class to recognize pdf file. [\#480](https://github.com/overtrue/wechat/pull/480) ([ac1982](https://github.com/ac1982)) +- update testActivateUserForm [\#478](https://github.com/overtrue/wechat/pull/478) ([wangniuniu](https://github.com/wangniuniu)) +- Scrutinizer Auto-Fixes [\#477](https://github.com/overtrue/wechat/pull/477) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer)) +- Applied fixes from StyleCI [\#476](https://github.com/overtrue/wechat/pull/476) ([overtrue](https://github.com/overtrue)) +- Scrutinizer Auto-Fixes [\#475](https://github.com/overtrue/wechat/pull/475) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer)) +- 开放自定义prefix和缓存键值方法 [\#474](https://github.com/overtrue/wechat/pull/474) ([XiaoLer](https://github.com/XiaoLer)) +- Applied fixes from StyleCI [\#469](https://github.com/overtrue/wechat/pull/469) ([overtrue](https://github.com/overtrue)) +- modify stats [\#468](https://github.com/overtrue/wechat/pull/468) ([wangniuniu](https://github.com/wangniuniu)) + +## [3.1.2](https://github.com/overtrue/wechat/tree/3.1.2) (2016-07-21) +[Full Changelog](https://github.com/overtrue/wechat/compare/2.1.38...3.1.2) + +**Closed issues:** + +- 素材管理中,上传图文下的上传图片,关于返回内容的差异 [\#466](https://github.com/overtrue/wechat/issues/466) +- spbill\_create\_ip参数设置 [\#461](https://github.com/overtrue/wechat/issues/461) + +**Merged pull requests:** + +- 更新获取标签下粉丝列表方法 [\#467](https://github.com/overtrue/wechat/pull/467) ([dingdayu](https://github.com/dingdayu)) +- Applied fixes from StyleCI [\#465](https://github.com/overtrue/wechat/pull/465) ([overtrue](https://github.com/overtrue)) +- card module. [\#464](https://github.com/overtrue/wechat/pull/464) ([wangniuniu](https://github.com/wangniuniu)) +- Applied fixes from StyleCI [\#463](https://github.com/overtrue/wechat/pull/463) ([overtrue](https://github.com/overtrue)) +- Scrutinizer Auto-Fixes [\#462](https://github.com/overtrue/wechat/pull/462) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer)) + +## [2.1.38](https://github.com/overtrue/wechat/tree/2.1.38) (2016-07-16) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.1.1...2.1.38) + +**Closed issues:** + +- 请问卡券管理功能整合上日程表了吗 [\#454](https://github.com/overtrue/wechat/issues/454) + +**Merged pull requests:** + +- Typo. [\#460](https://github.com/overtrue/wechat/pull/460) ([tianyong90](https://github.com/tianyong90)) +- Applied fixes from StyleCI [\#459](https://github.com/overtrue/wechat/pull/459) ([overtrue](https://github.com/overtrue)) +- add voice recognition [\#458](https://github.com/overtrue/wechat/pull/458) ([leniy](https://github.com/leniy)) +- Applied fixes from StyleCI [\#457](https://github.com/overtrue/wechat/pull/457) ([overtrue](https://github.com/overtrue)) +- Update API.php [\#456](https://github.com/overtrue/wechat/pull/456) ([marvin8212](https://github.com/marvin8212)) +- Update XML.php [\#455](https://github.com/overtrue/wechat/pull/455) ([canon4ever](https://github.com/canon4ever)) + +## [3.1.1](https://github.com/overtrue/wechat/tree/3.1.1) (2016-07-12) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.1.0...3.1.1) + +**Closed issues:** + +- 拿到code=CODE&state=STATE之后怎么拿到openid? [\#452](https://github.com/overtrue/wechat/issues/452) +- 安装出错 [\#450](https://github.com/overtrue/wechat/issues/450) +- 自定义菜单接口\(新版\)出错 [\#448](https://github.com/overtrue/wechat/issues/448) +- h5上没法打开微信app授权界面 [\#447](https://github.com/overtrue/wechat/issues/447) +- 重构卡券 [\#76](https://github.com/overtrue/wechat/issues/76) + +**Merged pull requests:** + +- typos. [\#453](https://github.com/overtrue/wechat/pull/453) ([tianye](https://github.com/tianye)) +- edit readme.md [\#451](https://github.com/overtrue/wechat/pull/451) ([tianyong90](https://github.com/tianyong90)) +- Add cache driver config. [\#449](https://github.com/overtrue/wechat/pull/449) ([dingdayu](https://github.com/dingdayu)) + +## [3.1.0](https://github.com/overtrue/wechat/tree/3.1.0) (2016-06-28) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.0.21...3.1.0) + +**Merged pull requests:** + +- Applied fixes from StyleCI [\#446](https://github.com/overtrue/wechat/pull/446) ([overtrue](https://github.com/overtrue)) +- New Staff API. [\#445](https://github.com/overtrue/wechat/pull/445) ([overtrue](https://github.com/overtrue)) +- 2.1 [\#444](https://github.com/overtrue/wechat/pull/444) ([dongnanyanhai](https://github.com/dongnanyanhai)) +- Fix path. [\#443](https://github.com/overtrue/wechat/pull/443) ([overtrue](https://github.com/overtrue)) + +## [3.0.21](https://github.com/overtrue/wechat/tree/3.0.21) (2016-06-17) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.0.1...3.0.21) + +**Closed issues:** + +- scan出现公众号暂时无法服务的消息 [\#436](https://github.com/overtrue/wechat/issues/436) +- scan出现公众号暂时无法服务的消息 [\#435](https://github.com/overtrue/wechat/issues/435) +- 用户标签接口无法使用 [\#433](https://github.com/overtrue/wechat/issues/433) +- WeChatProvider下的getAuthUrl个人觉得应该暴露出来 [\#432](https://github.com/overtrue/wechat/issues/432) +- 支持二维码扫描进入公众号推送的SCAN事件 [\#431](https://github.com/overtrue/wechat/issues/431) +- \[3.0\] EasyWeChat\Support\XML::parse方法会将空节点解析为空数组,而不是空字符串 [\#426](https://github.com/overtrue/wechat/issues/426) +- 下载二维码, $qrcode-\>download\($ticket,$paths\); 目录参数不可加入 中文 [\#420](https://github.com/overtrue/wechat/issues/420) +- \[help want\]Is hard to change default configuration of GuzzleHttp [\#415](https://github.com/overtrue/wechat/issues/415) +- PHP7.0 curl\_setopt 设置问题 [\#413](https://github.com/overtrue/wechat/issues/413) +- 无法通知微信支付完成 [\#412](https://github.com/overtrue/wechat/issues/412) +- 如何获取用户的unionid? [\#407](https://github.com/overtrue/wechat/issues/407) +- 是否支持多框架 [\#406](https://github.com/overtrue/wechat/issues/406) +- fuckTheWeChatInvalidJSON [\#405](https://github.com/overtrue/wechat/issues/405) +- Class 'GuzzleHttp\Middleware' not found [\#404](https://github.com/overtrue/wechat/issues/404) +- 支付统一下单接口签名错误 [\#402](https://github.com/overtrue/wechat/issues/402) +- payment里没有configForJSSDKPayment方法 [\#401](https://github.com/overtrue/wechat/issues/401) +- 查询支付的地址多了一个空格,导致查询失败,去掉最后的那个空格后就好了 [\#393](https://github.com/overtrue/wechat/issues/393) +- 网页授权过不了 [\#392](https://github.com/overtrue/wechat/issues/392) +- 微信AccessToken被动更新可能会有并发更新的情况出现 [\#390](https://github.com/overtrue/wechat/issues/390) +- 临时素材下载,文件名和扩展名之间会有2个\[.\] [\#389](https://github.com/overtrue/wechat/issues/389) +- 有一个地方变量名对不上 [\#380](https://github.com/overtrue/wechat/issues/380) +- 自定义缓存 [\#379](https://github.com/overtrue/wechat/issues/379) +- https://easywechat.org/ 底部 “开始使用” url拼错 [\#378](https://github.com/overtrue/wechat/issues/378) +- 在server.php里面调用yii的model,一直报错 [\#375](https://github.com/overtrue/wechat/issues/375) +- overture/wechat 2.1.36\(客服消息转发错误\) [\#374](https://github.com/overtrue/wechat/issues/374) +- 建议支持开发模式下禁用验证 [\#373](https://github.com/overtrue/wechat/issues/373) +- https://easywechat.org/ 导航 首页 about:blank [\#370](https://github.com/overtrue/wechat/issues/370) +- laravel 下session问题 [\#369](https://github.com/overtrue/wechat/issues/369) +- 关于Access——toekn [\#368](https://github.com/overtrue/wechat/issues/368) +- 返回支付页面时报错:"access\_token" could not be empty [\#367](https://github.com/overtrue/wechat/issues/367) +- xampp下js-\>config报错 [\#366](https://github.com/overtrue/wechat/issues/366) +- 官方文档有误 [\#360](https://github.com/overtrue/wechat/issues/360) +- \[BUG\] 微信收货地址无法成功 [\#359](https://github.com/overtrue/wechat/issues/359) +- 无法获取 $message-\>ScanCodeInfo-\>ScanType 对象 [\#358](https://github.com/overtrue/wechat/issues/358) +- \[Bugs\] 项目文档首页跳转问题 [\#357](https://github.com/overtrue/wechat/issues/357) +- Business和UnifiedOrder没有定义 [\#356](https://github.com/overtrue/wechat/issues/356) +- 你的网站访问不了。。。。https://easywechat.org/ [\#352](https://github.com/overtrue/wechat/issues/352) +- 连续多次执行微信支付退款报错 [\#348](https://github.com/overtrue/wechat/issues/348) +- 客服操作 都是 -1 错误 [\#344](https://github.com/overtrue/wechat/issues/344) +- 请使用openssl 而不是不安全的mcrypt来加密 [\#342](https://github.com/overtrue/wechat/issues/342) +- 文本类型的通知消息 [\#341](https://github.com/overtrue/wechat/issues/341) +- 服务器配置https 并且 通过阿里云 https cdn之后, 会出现 https 判断语句失效 [\#338](https://github.com/overtrue/wechat/issues/338) +- 作者请问者个sdk支持企业号吗? [\#336](https://github.com/overtrue/wechat/issues/336) +- laravel 5.1引入包报错 [\#331](https://github.com/overtrue/wechat/issues/331) +- 申请退款有问题 [\#328](https://github.com/overtrue/wechat/issues/328) +- 订单相关接口bug [\#327](https://github.com/overtrue/wechat/issues/327) +- 临时素材接口无法使用 [\#319](https://github.com/overtrue/wechat/issues/319) +- 使用sendNormal\(\),sendGroup\(\)发送红包时,报Undefined index: HTTP\_CLIENT\_IP [\#316](https://github.com/overtrue/wechat/issues/316) +- v3中微信卡券功能缺失? [\#307](https://github.com/overtrue/wechat/issues/307) +- 测试 [\#305](https://github.com/overtrue/wechat/issues/305) +- \[3.0\] 永久素材上传视频无法上传问题 [\#304](https://github.com/overtrue/wechat/issues/304) +- Cannot destroy active lambda function [\#296](https://github.com/overtrue/wechat/issues/296) +- 微信支付-》企业付款也可以增加个类上去,跟企业红包类似 [\#232](https://github.com/overtrue/wechat/issues/232) + +**Merged pull requests:** + +- Applied fixes from StyleCI [\#442](https://github.com/overtrue/wechat/pull/442) ([overtrue](https://github.com/overtrue)) +- NGINX HTTPS无法签名 [\#441](https://github.com/overtrue/wechat/pull/441) ([ares333](https://github.com/ares333)) +- Develop [\#440](https://github.com/overtrue/wechat/pull/440) ([overtrue](https://github.com/overtrue)) +- Develop [\#437](https://github.com/overtrue/wechat/pull/437) ([overtrue](https://github.com/overtrue)) +- Applied fixes from StyleCI [\#434](https://github.com/overtrue/wechat/pull/434) ([overtrue](https://github.com/overtrue)) +- 修改错误提示信息,方便跟踪错误 [\#430](https://github.com/overtrue/wechat/pull/430) ([zerozh](https://github.com/zerozh)) +- Develop [\#429](https://github.com/overtrue/wechat/pull/429) ([overtrue](https://github.com/overtrue)) +- Applied fixes from StyleCI [\#428](https://github.com/overtrue/wechat/pull/428) ([overtrue](https://github.com/overtrue)) +- Applied fixes from StyleCI [\#427](https://github.com/overtrue/wechat/pull/427) ([overtrue](https://github.com/overtrue)) +- Applied fixes from StyleCI [\#425](https://github.com/overtrue/wechat/pull/425) ([overtrue](https://github.com/overtrue)) +- update annotation [\#424](https://github.com/overtrue/wechat/pull/424) ([lilocon](https://github.com/lilocon)) +- Develop [\#421](https://github.com/overtrue/wechat/pull/421) ([overtrue](https://github.com/overtrue)) +- Set default timeout. [\#419](https://github.com/overtrue/wechat/pull/419) ([overtrue](https://github.com/overtrue)) +- Develop [\#418](https://github.com/overtrue/wechat/pull/418) ([overtrue](https://github.com/overtrue)) +- Develop [\#416](https://github.com/overtrue/wechat/pull/416) ([overtrue](https://github.com/overtrue)) +- better implementation for prepare oauth callback url [\#414](https://github.com/overtrue/wechat/pull/414) ([lichunqiang](https://github.com/lichunqiang)) +- Develop [\#410](https://github.com/overtrue/wechat/pull/410) ([overtrue](https://github.com/overtrue)) +- Applied fixes from StyleCI [\#409](https://github.com/overtrue/wechat/pull/409) ([overtrue](https://github.com/overtrue)) +- 增加微信支付服务商支持 [\#408](https://github.com/overtrue/wechat/pull/408) ([takatost](https://github.com/takatost)) +- Develop [\#403](https://github.com/overtrue/wechat/pull/403) ([overtrue](https://github.com/overtrue)) +- Applied fixes from StyleCI [\#400](https://github.com/overtrue/wechat/pull/400) ([overtrue](https://github.com/overtrue)) +- Scrutinizer Auto-Fixes [\#399](https://github.com/overtrue/wechat/pull/399) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer)) +- Develop [\#398](https://github.com/overtrue/wechat/pull/398) ([overtrue](https://github.com/overtrue)) +- Develop [\#397](https://github.com/overtrue/wechat/pull/397) ([overtrue](https://github.com/overtrue)) +- Applied fixes from StyleCI [\#396](https://github.com/overtrue/wechat/pull/396) ([overtrue](https://github.com/overtrue)) +- Typo & Improve code. [\#395](https://github.com/overtrue/wechat/pull/395) ([jinchun](https://github.com/jinchun)) +- Develop [\#394](https://github.com/overtrue/wechat/pull/394) ([overtrue](https://github.com/overtrue)) +- Bugfix close \#389 [\#391](https://github.com/overtrue/wechat/pull/391) ([overtrue](https://github.com/overtrue)) +- Update NoticeNoticeTest.php [\#388](https://github.com/overtrue/wechat/pull/388) ([xiabeifeng](https://github.com/xiabeifeng)) +- Update Notice.php [\#387](https://github.com/overtrue/wechat/pull/387) ([xiabeifeng](https://github.com/xiabeifeng)) +- Tests for \#384 [\#386](https://github.com/overtrue/wechat/pull/386) ([xiabeifeng](https://github.com/xiabeifeng)) +- Improve Notice API. [\#384](https://github.com/overtrue/wechat/pull/384) ([xiabeifeng](https://github.com/xiabeifeng)) +- 对应根 版本依赖 [\#382](https://github.com/overtrue/wechat/pull/382) ([parkshinhye](https://github.com/parkshinhye)) +- Develop [\#381](https://github.com/overtrue/wechat/pull/381) ([overtrue](https://github.com/overtrue)) +- Develop [\#377](https://github.com/overtrue/wechat/pull/377) ([overtrue](https://github.com/overtrue)) +- Fix test for \#371 [\#372](https://github.com/overtrue/wechat/pull/372) ([overtrue](https://github.com/overtrue)) +- 刷卡支付不需要notify\_url参数 [\#371](https://github.com/overtrue/wechat/pull/371) ([lilocon](https://github.com/lilocon)) +- Applied fixes from StyleCI [\#365](https://github.com/overtrue/wechat/pull/365) ([overtrue](https://github.com/overtrue)) +- Applied fixes from StyleCI [\#364](https://github.com/overtrue/wechat/pull/364) ([overtrue](https://github.com/overtrue)) +- Merge Develop [\#363](https://github.com/overtrue/wechat/pull/363) ([overtrue](https://github.com/overtrue)) +- Update composer.json [\#361](https://github.com/overtrue/wechat/pull/361) ([jaychan](https://github.com/jaychan)) +- Applied fixes from StyleCI [\#355](https://github.com/overtrue/wechat/pull/355) ([overtrue](https://github.com/overtrue)) +- \[ci skip\]fix document typo [\#354](https://github.com/overtrue/wechat/pull/354) ([lichunqiang](https://github.com/lichunqiang)) +- 自定义Logger [\#353](https://github.com/overtrue/wechat/pull/353) ([lilocon](https://github.com/lilocon)) +- Update Refund.php [\#351](https://github.com/overtrue/wechat/pull/351) ([jaring](https://github.com/jaring)) +- Applied fixes from StyleCI [\#350](https://github.com/overtrue/wechat/pull/350) ([overtrue](https://github.com/overtrue)) +- OpenSSL bugfix. [\#349](https://github.com/overtrue/wechat/pull/349) ([overtrue](https://github.com/overtrue)) +- Applied fixes from StyleCI [\#347](https://github.com/overtrue/wechat/pull/347) ([overtrue](https://github.com/overtrue)) +- Applied fixes from StyleCI [\#346](https://github.com/overtrue/wechat/pull/346) ([overtrue](https://github.com/overtrue)) +- Merge Develop [\#345](https://github.com/overtrue/wechat/pull/345) ([overtrue](https://github.com/overtrue)) +- 添加代码提示 [\#343](https://github.com/overtrue/wechat/pull/343) ([lilocon](https://github.com/lilocon)) +- Applied fixes from StyleCI [\#340](https://github.com/overtrue/wechat/pull/340) ([overtrue](https://github.com/overtrue)) +- Fix bug: Payment::downloadBill\(\) response error. [\#339](https://github.com/overtrue/wechat/pull/339) ([overtrue](https://github.com/overtrue)) +- change get\_client\_ip to get\_server\_ip [\#335](https://github.com/overtrue/wechat/pull/335) ([tianyong90](https://github.com/tianyong90)) +- Payment SSL. [\#334](https://github.com/overtrue/wechat/pull/334) ([overtrue](https://github.com/overtrue)) +- Add a helper to get correct client ip address. fixed \#316 [\#333](https://github.com/overtrue/wechat/pull/333) ([tianyong90](https://github.com/tianyong90)) +- Dependency Bugfix. overtrue/laravel-wechat\#24 [\#332](https://github.com/overtrue/wechat/pull/332) ([overtrue](https://github.com/overtrue)) +- Applied fixes from StyleCI [\#330](https://github.com/overtrue/wechat/pull/330) ([overtrue](https://github.com/overtrue)) +- Merge Develop [\#329](https://github.com/overtrue/wechat/pull/329) ([overtrue](https://github.com/overtrue)) +- Applied fixes from StyleCI [\#326](https://github.com/overtrue/wechat/pull/326) ([overtrue](https://github.com/overtrue)) +- Add order default notify\_url. [\#325](https://github.com/overtrue/wechat/pull/325) ([foreverglory](https://github.com/foreverglory)) +- Revert "Applied fixes from StyleCI" [\#323](https://github.com/overtrue/wechat/pull/323) ([overtrue](https://github.com/overtrue)) +- Applied fixes from StyleCI [\#322](https://github.com/overtrue/wechat/pull/322) ([overtrue](https://github.com/overtrue)) +- Develop [\#321](https://github.com/overtrue/wechat/pull/321) ([overtrue](https://github.com/overtrue)) +- Applied fixes from StyleCI [\#320](https://github.com/overtrue/wechat/pull/320) ([overtrue](https://github.com/overtrue)) +- 模板消息添加【 获取模板列表】和【 删除模板】接口 [\#318](https://github.com/overtrue/wechat/pull/318) ([forecho](https://github.com/forecho)) +- Applied fixes from StyleCI [\#314](https://github.com/overtrue/wechat/pull/314) ([overtrue](https://github.com/overtrue)) +- fix Temporary upload bug [\#313](https://github.com/overtrue/wechat/pull/313) ([mani95lisa](https://github.com/mani95lisa)) +- Applied fixes from StyleCI [\#312](https://github.com/overtrue/wechat/pull/312) ([overtrue](https://github.com/overtrue)) +- MerchantPay Class [\#311](https://github.com/overtrue/wechat/pull/311) ([ac1982](https://github.com/ac1982)) +- Applied fixes from StyleCI [\#309](https://github.com/overtrue/wechat/pull/309) ([overtrue](https://github.com/overtrue)) +- Merge Develop [\#308](https://github.com/overtrue/wechat/pull/308) ([overtrue](https://github.com/overtrue)) +- 删除裂变红包接口中的ip参数 [\#306](https://github.com/overtrue/wechat/pull/306) ([xjchengo](https://github.com/xjchengo)) +- fix code style and some spelling mistakes [\#303](https://github.com/overtrue/wechat/pull/303) ([jinchun](https://github.com/jinchun)) +- Merge Develop [\#302](https://github.com/overtrue/wechat/pull/302) ([overtrue](https://github.com/overtrue)) +- Add method for app payment [\#301](https://github.com/overtrue/wechat/pull/301) ([lichunqiang](https://github.com/lichunqiang)) +- Removed the return syntax [\#300](https://github.com/overtrue/wechat/pull/300) ([lichunqiang](https://github.com/lichunqiang)) +- add return tag [\#299](https://github.com/overtrue/wechat/pull/299) ([lichunqiang](https://github.com/lichunqiang)) +- Merge Develop [\#298](https://github.com/overtrue/wechat/pull/298) ([overtrue](https://github.com/overtrue)) +- Applied fixes from StyleCI [\#297](https://github.com/overtrue/wechat/pull/297) ([overtrue](https://github.com/overtrue)) +- \[ci skip\]Update .gitattributes [\#295](https://github.com/overtrue/wechat/pull/295) ([lichunqiang](https://github.com/lichunqiang)) +- Merge Develop [\#294](https://github.com/overtrue/wechat/pull/294) ([overtrue](https://github.com/overtrue)) + +## [3.0.1](https://github.com/overtrue/wechat/tree/3.0.1) (2016-02-19) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.0...3.0.1) + +**Closed issues:** + +- composer 安装 3.0版本,报错如下: [\#291](https://github.com/overtrue/wechat/issues/291) +- \[3.0\] 下载永久素材时,微信返回的Content-Type不正确,导致出错。 [\#290](https://github.com/overtrue/wechat/issues/290) +- 挖个坑,自己跳 [\#147](https://github.com/overtrue/wechat/issues/147) + +**Merged pull requests:** + +- Applied fixes from StyleCI [\#293](https://github.com/overtrue/wechat/pull/293) ([overtrue](https://github.com/overtrue)) +- Merge Develop [\#292](https://github.com/overtrue/wechat/pull/292) ([overtrue](https://github.com/overtrue)) + +## [3.0](https://github.com/overtrue/wechat/tree/3.0) (2016-02-17) +[Full Changelog](https://github.com/overtrue/wechat/compare/2.1.0...3.0) + +**Implemented enhancements:** + +- MIME json 格式检查优化 [\#49](https://github.com/overtrue/wechat/issues/49) +- 获取 refresh\_token,access\_token [\#43](https://github.com/overtrue/wechat/issues/43) +- 关于API\_TOKEN\_REFRESH [\#20](https://github.com/overtrue/wechat/issues/20) + +**Closed issues:** + +- \[3.0\] 无法获取用户分组信息 [\#285](https://github.com/overtrue/wechat/issues/285) +- 新的laravel 5.2 不能兼容了 [\#284](https://github.com/overtrue/wechat/issues/284) +- \[3.0\]Message/Article类的$properties内的source\_url没有正常转换为content\_source\_url. [\#281](https://github.com/overtrue/wechat/issues/281) +- 3.0删除个性菜单失败 [\#280](https://github.com/overtrue/wechat/issues/280) +- 也许你该给一个代码贡献规范 [\#277](https://github.com/overtrue/wechat/issues/277) +- 3.0网页授权时scope为snsapi\_base得不到openid [\#276](https://github.com/overtrue/wechat/issues/276) +- wechat3.0中 有2个地方的js调用参数不一样,超哥没有提供 [\#272](https://github.com/overtrue/wechat/issues/272) +- 我想知道2.X和3.0有什么大的区别! [\#270](https://github.com/overtrue/wechat/issues/270) +- 2.1: Link 消息类型没有实现 [\#269](https://github.com/overtrue/wechat/issues/269) +- 关于模板消息换行的问题 [\#266](https://github.com/overtrue/wechat/issues/266) +- easywechat Invalid request [\#265](https://github.com/overtrue/wechat/issues/265) +- 40029不合法的oauth\_code [\#264](https://github.com/overtrue/wechat/issues/264) +- 下载素材的一个小问题 [\#263](https://github.com/overtrue/wechat/issues/263) +- \[2.1\] 微信自定义菜单结构变更导致`Menu::get\(\)` 无法读取个性化菜单 [\#262](https://github.com/overtrue/wechat/issues/262) +- payment中是不是不包含H5和JS的生成配置文件的方法了? [\#261](https://github.com/overtrue/wechat/issues/261) +- payment下prepare方法bug [\#260](https://github.com/overtrue/wechat/issues/260) +- UserServiceProvider中似乎忘记注册user.group了 [\#256](https://github.com/overtrue/wechat/issues/256) +- 2.1.X版媒体下载没有扩展名 [\#252](https://github.com/overtrue/wechat/issues/252) +- 为什么所有的子模块在自己的库都是develop分支 [\#247](https://github.com/overtrue/wechat/issues/247) +- 网页授权使用跳转的bug [\#246](https://github.com/overtrue/wechat/issues/246) +- typo of variable [\#245](https://github.com/overtrue/wechat/issues/245) +- The implementation class of ServerServiceProvider missing an important [\#244](https://github.com/overtrue/wechat/issues/244) +- \[3.0\]\[payment\] 两个可能的bug [\#235](https://github.com/overtrue/wechat/issues/235) +- 发送多图文 [\#233](https://github.com/overtrue/wechat/issues/233) +- 自定义菜单返回应该把个性化自定义菜单也一起返回 [\#231](https://github.com/overtrue/wechat/issues/231) +- 发送模板消息 CRUL 错误 [\#223](https://github.com/overtrue/wechat/issues/223) +- 客服接口暂时测到有3个bug,麻烦修复 [\#222](https://github.com/overtrue/wechat/issues/222) +- JSSDK access\_token missing [\#211](https://github.com/overtrue/wechat/issues/211) +- Js.php/ticket [\#210](https://github.com/overtrue/wechat/issues/210) +- 微信支付里有一个收货地址共享 ,超哥你这里没有,可以加一下不? [\#204](https://github.com/overtrue/wechat/issues/204) +- 小问题 [\#203](https://github.com/overtrue/wechat/issues/203) +- 网页授权 跳转 [\#202](https://github.com/overtrue/wechat/issues/202) +- access token 重复添加的问题 [\#201](https://github.com/overtrue/wechat/issues/201) +- authorize snsapi\_base 下可以获取unionid [\#198](https://github.com/overtrue/wechat/issues/198) +- 网页授权 [\#189](https://github.com/overtrue/wechat/issues/189) +- 一点建议 [\#188](https://github.com/overtrue/wechat/issues/188) +- 接口更新-新增临时素材接口变动 [\#186](https://github.com/overtrue/wechat/issues/186) +- 接入多个公众号不用id [\#185](https://github.com/overtrue/wechat/issues/185) +- \[Insight\] Files should not be executable [\#184](https://github.com/overtrue/wechat/issues/184) +- 建议不要写死Http [\#183](https://github.com/overtrue/wechat/issues/183) +- laravel4.2安装不成功 [\#182](https://github.com/overtrue/wechat/issues/182) +- 是否支持laravel4.2 [\#181](https://github.com/overtrue/wechat/issues/181) +- 微信出个性化菜单了,希望支持 [\#180](https://github.com/overtrue/wechat/issues/180) +- 3.0 composer依赖Symfony2.7。能不能支持Symfony3.0? [\#179](https://github.com/overtrue/wechat/issues/179) +- 发送链接类消息错误 [\#175](https://github.com/overtrue/wechat/issues/175) +- Throw Exception的时候 Intel server status 设置为200是不是好一些 [\#174](https://github.com/overtrue/wechat/issues/174) +- 生成临时二维码时,返回EventKey不是传递的值 [\#173](https://github.com/overtrue/wechat/issues/173) +- 关于素材获取的一个建议 [\#172](https://github.com/overtrue/wechat/issues/172) +- 能否增加微信APP支付相关方法 [\#171](https://github.com/overtrue/wechat/issues/171) +- 微信回调URL回调不到 [\#170](https://github.com/overtrue/wechat/issues/170) +- 素材管理添加永久素材返回JSON/XML内容错误 [\#169](https://github.com/overtrue/wechat/issues/169) +- \[消息的使用\] 中 \[上传素材文件\] 的文档示例貌似有误 [\#168](https://github.com/overtrue/wechat/issues/168) +- 素材管理里的download方法不是很符合sdk一站式的解决. [\#165](https://github.com/overtrue/wechat/issues/165) +- \[Wechat\]不合法的oauth\_code' in /src/Wechat/Http.php:124 [\#164](https://github.com/overtrue/wechat/issues/164) +- AccessToken Expired Error Code [\#163](https://github.com/overtrue/wechat/issues/163) +- 素材管理接口出错 [\#162](https://github.com/overtrue/wechat/issues/162) +- 两处代码php5.4才能运行 [\#158](https://github.com/overtrue/wechat/issues/158) +- extension is null when calling `download video` in wechat.media [\#157](https://github.com/overtrue/wechat/issues/157) +- Payment/UnifiedOrder does not support serialize or create by array [\#155](https://github.com/overtrue/wechat/issues/155) +- 没有找到"微信支付-\>查询订单"相关功能 [\#150](https://github.com/overtrue/wechat/issues/150) +- 请教,Cache::setter中your\_custom\_set\_cache怎么使用 [\#149](https://github.com/overtrue/wechat/issues/149) +- 发生异常时, 希望能把发送和接收的原始数据记录下来. [\#148](https://github.com/overtrue/wechat/issues/148) +- 发送红包,证书错误 [\#144](https://github.com/overtrue/wechat/issues/144) +- 发视频消息总返回 -1 [\#143](https://github.com/overtrue/wechat/issues/143) +- 关于PHP版本 [\#141](https://github.com/overtrue/wechat/issues/141) +- Server消息回复必须以事件方式吗? [\#140](https://github.com/overtrue/wechat/issues/140) +- 微信支付相关文档细化 [\#138](https://github.com/overtrue/wechat/issues/138) +- 好奇地问个问题,这项目的测试用例放在哪? [\#135](https://github.com/overtrue/wechat/issues/135) +- 试了两次,真的不会用 [\#134](https://github.com/overtrue/wechat/issues/134) +- 不知道这算不算是个BUG [\#133](https://github.com/overtrue/wechat/issues/133) +- 微信小店 [\#130](https://github.com/overtrue/wechat/issues/130) +- 多次遇到 accesstoken 无效的问题 [\#129](https://github.com/overtrue/wechat/issues/129) +- MCH\_KEY 微信支付 [\#128](https://github.com/overtrue/wechat/issues/128) +- 使用flightphp框架,验证URL的时候,在Apache下接入成功,在Nginx接入失败 [\#126](https://github.com/overtrue/wechat/issues/126) +- 好东西!可惜没有我需要的微信红包 [\#125](https://github.com/overtrue/wechat/issues/125) +- Cache存储部件可定制 [\#120](https://github.com/overtrue/wechat/issues/120) +- 关于Bag [\#119](https://github.com/overtrue/wechat/issues/119) +- 将代码部署到负载均衡上如何管理access token [\#118](https://github.com/overtrue/wechat/issues/118) +- 消息接受和回复时,如果不对消息做回复,该如何做? [\#117](https://github.com/overtrue/wechat/issues/117) +- 请教一个问题 [\#116](https://github.com/overtrue/wechat/issues/116) +- 关于 Cache [\#115](https://github.com/overtrue/wechat/issues/115) +- 如何才能获取普通的access\_token [\#113](https://github.com/overtrue/wechat/issues/113) +- $HTTP\_RAW\_POST\_DATA DEPRECATED [\#111](https://github.com/overtrue/wechat/issues/111) +- App支付缺少错误码 [\#109](https://github.com/overtrue/wechat/issues/109) +- 当用户信息有 " 字符时系统出错 \(用户与用户组管理接口\) [\#107](https://github.com/overtrue/wechat/issues/107) +- 提示错误 [\#106](https://github.com/overtrue/wechat/issues/106) +- 使用企业号的时候 接入失败啊,在验证url的时候 [\#104](https://github.com/overtrue/wechat/issues/104) +- 支付签名错误 [\#101](https://github.com/overtrue/wechat/issues/101) +- 微信支付.$payment-\>getConfig\(\)调用时候\[Wechat\]系统繁忙,此时请开发者稍候再试. [\#96](https://github.com/overtrue/wechat/issues/96) +- wechat/src/Wechat/Payment/UnifiedOrder.php 小问题 [\#94](https://github.com/overtrue/wechat/issues/94) +- 请教laravel中如何在微信支付中 catch UnifiedOrder 抛出的异常? [\#93](https://github.com/overtrue/wechat/issues/93) +- 是否可以增加一个第三方接口融合功能 [\#91](https://github.com/overtrue/wechat/issues/91) +- 订单查询 [\#90](https://github.com/overtrue/wechat/issues/90) +- 如何不下载图片,通过mediaId获取图片存储的URL [\#89](https://github.com/overtrue/wechat/issues/89) +- 'Undefined index: HTTP\_HOST' [\#88](https://github.com/overtrue/wechat/issues/88) +- Undefined index: HTTP\_HOST [\#87](https://github.com/overtrue/wechat/issues/87) +- 不能上传gif格式的图片素材 [\#84](https://github.com/overtrue/wechat/issues/84) +- OAuth重构 [\#74](https://github.com/overtrue/wechat/issues/74) +- \[3.0\] Tasks [\#50](https://github.com/overtrue/wechat/issues/50) +- appId 和 appSecret不要作为各个类的构造参数 [\#114](https://github.com/overtrue/wechat/issues/114) +- 增加debug相关的选项 [\#112](https://github.com/overtrue/wechat/issues/112) +- 好像没有获取自动回复数据接口 [\#108](https://github.com/overtrue/wechat/issues/108) +- js端查看微信卡券接口 chooseCard [\#79](https://github.com/overtrue/wechat/issues/79) +- 【新增】支付 [\#78](https://github.com/overtrue/wechat/issues/78) +- 模板消息重构 [\#75](https://github.com/overtrue/wechat/issues/75) +- 素材下载自动识别MIME生成后缀 [\#54](https://github.com/overtrue/wechat/issues/54) +- \[建议\] 深度结合微信多图文与素材管理 [\#46](https://github.com/overtrue/wechat/issues/46) +- 群发功能 [\#18](https://github.com/overtrue/wechat/issues/18) + +**Merged pull requests:** + +- 3.0 [\#289](https://github.com/overtrue/wechat/pull/289) ([overtrue](https://github.com/overtrue)) +- Merge Develop [\#288](https://github.com/overtrue/wechat/pull/288) ([overtrue](https://github.com/overtrue)) +- Applied fixes from StyleCI [\#287](https://github.com/overtrue/wechat/pull/287) ([overtrue](https://github.com/overtrue)) +- Applied fixes from StyleCI [\#286](https://github.com/overtrue/wechat/pull/286) ([overtrue](https://github.com/overtrue)) +- Fix bug in batchGet method. [\#283](https://github.com/overtrue/wechat/pull/283) ([tianyong90](https://github.com/tianyong90)) +- Typo. [\#279](https://github.com/overtrue/wechat/pull/279) ([overtrue](https://github.com/overtrue)) +- Add contribution guide. resolves \#277 [\#278](https://github.com/overtrue/wechat/pull/278) ([overtrue](https://github.com/overtrue)) +- Develop [\#274](https://github.com/overtrue/wechat/pull/274) ([overtrue](https://github.com/overtrue)) +- Applied fixes from StyleCI [\#273](https://github.com/overtrue/wechat/pull/273) ([overtrue](https://github.com/overtrue)) +- Develop [\#271](https://github.com/overtrue/wechat/pull/271) ([overtrue](https://github.com/overtrue)) +- Merge Develop [\#268](https://github.com/overtrue/wechat/pull/268) ([overtrue](https://github.com/overtrue)) +- Applied fixes from StyleCI [\#267](https://github.com/overtrue/wechat/pull/267) ([overtrue](https://github.com/overtrue)) +- Update QRCode.php [\#258](https://github.com/overtrue/wechat/pull/258) ([webshiyue](https://github.com/webshiyue)) +- Add tests for LuckyMoney. [\#255](https://github.com/overtrue/wechat/pull/255) ([tianyong90](https://github.com/tianyong90)) +- CS. [\#254](https://github.com/overtrue/wechat/pull/254) ([overtrue](https://github.com/overtrue)) +- Scrutinizer Auto-Fixes [\#253](https://github.com/overtrue/wechat/pull/253) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer)) +- Applied fixes from StyleCI [\#251](https://github.com/overtrue/wechat/pull/251) ([overtrue](https://github.com/overtrue)) +- Applied fixes from StyleCI [\#250](https://github.com/overtrue/wechat/pull/250) ([overtrue](https://github.com/overtrue)) +- Merge Develop [\#249](https://github.com/overtrue/wechat/pull/249) ([overtrue](https://github.com/overtrue)) +- Merge Develop [\#248](https://github.com/overtrue/wechat/pull/248) ([overtrue](https://github.com/overtrue)) +- Merge from Develop [\#243](https://github.com/overtrue/wechat/pull/243) ([overtrue](https://github.com/overtrue)) +- Applied fixes from StyleCI [\#242](https://github.com/overtrue/wechat/pull/242) ([overtrue](https://github.com/overtrue)) +- Applied fixes from StyleCI [\#241](https://github.com/overtrue/wechat/pull/241) ([overtrue](https://github.com/overtrue)) +- Add Luckymoney. [\#240](https://github.com/overtrue/wechat/pull/240) ([tianyong90](https://github.com/tianyong90)) +- Applied fixes from StyleCI [\#237](https://github.com/overtrue/wechat/pull/237) ([overtrue](https://github.com/overtrue)) +- Applied fixes from StyleCI [\#234](https://github.com/overtrue/wechat/pull/234) ([overtrue](https://github.com/overtrue)) +- Multiple News Items Support [\#230](https://github.com/overtrue/wechat/pull/230) ([fanglinks](https://github.com/fanglinks)) +- Applied fixes from StyleCI [\#221](https://github.com/overtrue/wechat/pull/221) ([overtrue](https://github.com/overtrue)) +- \[3.0\]\[Bugfix\]发送图文消息缺少type [\#217](https://github.com/overtrue/wechat/pull/217) ([sunbiao0526](https://github.com/sunbiao0526)) +- fix Js.php 获取自定义cache对象 [\#215](https://github.com/overtrue/wechat/pull/215) ([sunbiao0526](https://github.com/sunbiao0526)) +- Applied fixes from StyleCI [\#197](https://github.com/overtrue/wechat/pull/197) ([overtrue](https://github.com/overtrue)) +- Add alias [\#196](https://github.com/overtrue/wechat/pull/196) ([ruchengtang](https://github.com/ruchengtang)) +- Applied fixes from StyleCI [\#195](https://github.com/overtrue/wechat/pull/195) ([overtrue](https://github.com/overtrue)) +- Applied fixes from StyleCI [\#194](https://github.com/overtrue/wechat/pull/194) ([overtrue](https://github.com/overtrue)) +- Add Broadcast. [\#193](https://github.com/overtrue/wechat/pull/193) ([ruchengtang](https://github.com/ruchengtang)) +- 微信红包类优化 [\#190](https://github.com/overtrue/wechat/pull/190) ([tianyong90](https://github.com/tianyong90)) +- Update ServerServiceProvider.php [\#187](https://github.com/overtrue/wechat/pull/187) ([ghost](https://github.com/ghost)) +- Update README\_EN.md [\#178](https://github.com/overtrue/wechat/pull/178) ([spekulatius](https://github.com/spekulatius)) +- 添加群发消息文档 [\#177](https://github.com/overtrue/wechat/pull/177) ([ruchengtang](https://github.com/ruchengtang)) +- 群发消息 [\#176](https://github.com/overtrue/wechat/pull/176) ([ruchengtang](https://github.com/ruchengtang)) +- Master [\#167](https://github.com/overtrue/wechat/pull/167) ([xiaohome](https://github.com/xiaohome)) +- 微信小店 [\#166](https://github.com/overtrue/wechat/pull/166) ([xiaohome](https://github.com/xiaohome)) +- 红包类更新 [\#161](https://github.com/overtrue/wechat/pull/161) ([overtrue](https://github.com/overtrue)) +- 加入摇一摇红包类,红包类提升至Overtrue命名空间 [\#160](https://github.com/overtrue/wechat/pull/160) ([tianyong90](https://github.com/tianyong90)) +- 2.1 [\#159](https://github.com/overtrue/wechat/pull/159) ([overtrue](https://github.com/overtrue)) +- Update QRCode.php [\#156](https://github.com/overtrue/wechat/pull/156) ([ruchengtang](https://github.com/ruchengtang)) +- 修复使用!=,来判断0 != null 的时候的一个bug [\#154](https://github.com/overtrue/wechat/pull/154) ([Liv1020](https://github.com/Liv1020)) +- 调整多客服类删除客服方法 [\#151](https://github.com/overtrue/wechat/pull/151) ([tianyong90](https://github.com/tianyong90)) +- 修复个bug [\#146](https://github.com/overtrue/wechat/pull/146) ([xiaohome](https://github.com/xiaohome)) +- Update README.md [\#142](https://github.com/overtrue/wechat/pull/142) ([parkshinhye](https://github.com/parkshinhye)) +- Fix code style to PSR-2 [\#139](https://github.com/overtrue/wechat/pull/139) ([tianyong90](https://github.com/tianyong90)) +- 加入红包工具类,支持现金和裂变红包的发送及查询 [\#137](https://github.com/overtrue/wechat/pull/137) ([tianyong90](https://github.com/tianyong90)) +- 卡券类批量获取卡券ID方法支持仅获取指定状态卡券 [\#132](https://github.com/overtrue/wechat/pull/132) ([tianyong90](https://github.com/tianyong90)) +- 添加客服 卡券回复!!! [\#124](https://github.com/overtrue/wechat/pull/124) ([parkshinhye](https://github.com/parkshinhye)) +- 调整退款类中一处异常抛出逻辑并修正单词拼写错误 [\#122](https://github.com/overtrue/wechat/pull/122) ([tianyong90](https://github.com/tianyong90)) +- 加入创建卡券货架接口 [\#121](https://github.com/overtrue/wechat/pull/121) ([tianyong90](https://github.com/tianyong90)) +- 增加退款类 [\#105](https://github.com/overtrue/wechat/pull/105) ([jaring](https://github.com/jaring)) +- 增加获取用户已领取卡券方法 [\#103](https://github.com/overtrue/wechat/pull/103) ([tenstone](https://github.com/tenstone)) +- Scrutinizer Auto-Fixes [\#100](https://github.com/overtrue/wechat/pull/100) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer)) +- 修正二维码类中生成卡券二维码方法 [\#99](https://github.com/overtrue/wechat/pull/99) ([tianyong90](https://github.com/tianyong90)) +- 卡券接口加入添加测试白名单方法 [\#98](https://github.com/overtrue/wechat/pull/98) ([tianyong90](https://github.com/tianyong90)) +- 依样画葫芦写了一个查询订单,更改了UnifiedOrder中Http初始化 [\#95](https://github.com/overtrue/wechat/pull/95) ([jaring](https://github.com/jaring)) +- accessToken根据appId变化 [\#92](https://github.com/overtrue/wechat/pull/92) ([keepeye](https://github.com/keepeye)) +- Fix payment sign bug. [\#82](https://github.com/overtrue/wechat/pull/82) ([0i](https://github.com/0i)) +- \[wiki\] wechat payment [\#81](https://github.com/overtrue/wechat/pull/81) ([0i](https://github.com/0i)) + +## [2.1.0](https://github.com/overtrue/wechat/tree/2.1.0) (2015-08-18) +[Full Changelog](https://github.com/overtrue/wechat/compare/2.0.35...2.1.0) + +**Merged pull requests:** + +- Wechat Payment [\#80](https://github.com/overtrue/wechat/pull/80) ([0i](https://github.com/0i)) + +## [2.0.35](https://github.com/overtrue/wechat/tree/2.0.35) (2015-08-11) +[Full Changelog](https://github.com/overtrue/wechat/compare/2.0.1...2.0.35) + +**Implemented enhancements:** + +- Overtrue\Wechat\Http识别JSON的问题 [\#47](https://github.com/overtrue/wechat/issues/47) + +**Fixed bugs:** + +- 模板消息简单格式无效 [\#34](https://github.com/overtrue/wechat/issues/34) + +**Closed issues:** + +- $data是数组,title输出不了内容 [\#73](https://github.com/overtrue/wechat/issues/73) +- 回调是如何传递外部参数的? [\#72](https://github.com/overtrue/wechat/issues/72) +- 【建议】可以添加微信js的功能吗? [\#71](https://github.com/overtrue/wechat/issues/71) +- Message::make\('link'\) 无效 [\#70](https://github.com/overtrue/wechat/issues/70) +- 监听消息 返回Bad Request [\#65](https://github.com/overtrue/wechat/issues/65) +- 微信素材管理小改版,求跟上~ [\#64](https://github.com/overtrue/wechat/issues/64) +- 在新浪SAE平台上的部署问题 [\#63](https://github.com/overtrue/wechat/issues/63) +- $xmlInput = file\_get\_contents\('php://input'\);貌似在某些版本的PHP有问题还是怎的 [\#57](https://github.com/overtrue/wechat/issues/57) +- 卡券的 attachExtension 方法 [\#56](https://github.com/overtrue/wechat/issues/56) +- 网页授权$auth-\>authorize\(\) 后还需要保存access\_token吗? [\#53](https://github.com/overtrue/wechat/issues/53) +- php 5.6版本下出现错误(5.6以下版本正常) [\#51](https://github.com/overtrue/wechat/issues/51) +- 消息发送后服务器无法正确返回响应 [\#48](https://github.com/overtrue/wechat/issues/48) +- token验证失败 [\#45](https://github.com/overtrue/wechat/issues/45) +- 微信关注自动回复问题 [\#44](https://github.com/overtrue/wechat/issues/44) +- js sdk config 建议增加 beta 字段 [\#35](https://github.com/overtrue/wechat/issues/35) +- 关于Util\HTTP::encode\(\)中的urlencode\(\)/urldecode\(\)成组操作的疑问 [\#31](https://github.com/overtrue/wechat/issues/31) +- Media::updateNews\(\) 方法与微信API不一致 [\#29](https://github.com/overtrue/wechat/issues/29) +- 希望能有一个ThinkPHP的使用示例 [\#28](https://github.com/overtrue/wechat/issues/28) +- 事件消息 [\#22](https://github.com/overtrue/wechat/issues/22) +- 模板消息notice [\#21](https://github.com/overtrue/wechat/issues/21) +- 关于获取(接收)用户发送消息 [\#19](https://github.com/overtrue/wechat/issues/19) +- 微信公众号绑定的一点问题,请教。 [\#16](https://github.com/overtrue/wechat/issues/16) +- 获取素材列表错误 [\#15](https://github.com/overtrue/wechat/issues/15) + +**Merged pull requests:** + +- Scrutinizer Auto-Fixes [\#69](https://github.com/overtrue/wechat/pull/69) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer)) +- Scrutinizer Auto-Fixes [\#68](https://github.com/overtrue/wechat/pull/68) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer)) +- Scrutinizer Auto-Fixes [\#67](https://github.com/overtrue/wechat/pull/67) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer)) +- Fixed StyleCI config [\#66](https://github.com/overtrue/wechat/pull/66) ([GrahamCampbell](https://github.com/GrahamCampbell)) +- 洁癖爆发了。。。 [\#62](https://github.com/overtrue/wechat/pull/62) ([TheNorthMemory](https://github.com/TheNorthMemory)) +- fix: js getUrl use Url::current\(\) [\#61](https://github.com/overtrue/wechat/pull/61) ([wdjwxh](https://github.com/wdjwxh)) +- bug-fix: add x-forwarded-host for Url::current [\#60](https://github.com/overtrue/wechat/pull/60) ([wdjwxh](https://github.com/wdjwxh)) +- Fix request method for User::batchGet\(\), should be POST with JSON. [\#59](https://github.com/overtrue/wechat/pull/59) ([acgrid](https://github.com/acgrid)) +- optimize some code [\#58](https://github.com/overtrue/wechat/pull/58) ([tabalt](https://github.com/tabalt)) +- 增加使用media id发送图文消息的功能 [\#52](https://github.com/overtrue/wechat/pull/52) ([zengohm](https://github.com/zengohm)) +- fix Staff::delete, let it works [\#42](https://github.com/overtrue/wechat/pull/42) ([TheNorthMemory](https://github.com/TheNorthMemory)) +- 支持自定义菜单类型:下发消息media\_id、跳转图文消息view\_limited [\#40](https://github.com/overtrue/wechat/pull/40) ([acgrid](https://github.com/acgrid)) +- docline comments & fix AccessToken parameter typos [\#39](https://github.com/overtrue/wechat/pull/39) ([TheNorthMemory](https://github.com/TheNorthMemory)) +- Merge from master [\#38](https://github.com/overtrue/wechat/pull/38) ([overtrue](https://github.com/overtrue)) +- 客服接口Bugfix [\#37](https://github.com/overtrue/wechat/pull/37) ([overtrue](https://github.com/overtrue)) +- fix Staff and AccessToken typos [\#36](https://github.com/overtrue/wechat/pull/36) ([TheNorthMemory](https://github.com/TheNorthMemory)) +- Update QRCode.php [\#33](https://github.com/overtrue/wechat/pull/33) ([refear99](https://github.com/refear99)) +- English Readme [\#32](https://github.com/overtrue/wechat/pull/32) ([hareluya](https://github.com/hareluya)) +- 更新图文消息方法Media::updateNews\(\)与微信API不一致 [\#30](https://github.com/overtrue/wechat/pull/30) ([acgrid](https://github.com/acgrid)) +- 代码之美在于不断修正 :\) [\#27](https://github.com/overtrue/wechat/pull/27) ([TheNorthMemory](https://github.com/TheNorthMemory)) +- the json\_encode $depth parameter was added@5.5.0 [\#26](https://github.com/overtrue/wechat/pull/26) ([TheNorthMemory](https://github.com/TheNorthMemory)) +- fix \#4 for PHP5.3 [\#25](https://github.com/overtrue/wechat/pull/25) ([TheNorthMemory](https://github.com/TheNorthMemory)) +- fix \#4 for PHP5.3 [\#23](https://github.com/overtrue/wechat/pull/23) ([TheNorthMemory](https://github.com/TheNorthMemory)) +- Update QRCode.php [\#17](https://github.com/overtrue/wechat/pull/17) ([gundanx10](https://github.com/gundanx10)) + +## [2.0.1](https://github.com/overtrue/wechat/tree/2.0.1) (2015-05-08) +[Full Changelog](https://github.com/overtrue/wechat/compare/2.0.0...2.0.1) + +**Closed issues:** + +- 2.0版本使用问题 [\#14](https://github.com/overtrue/wechat/issues/14) + +## [2.0.0](https://github.com/overtrue/wechat/tree/2.0.0) (2015-05-07) +[Full Changelog](https://github.com/overtrue/wechat/compare/1.0.1...2.0.0) + +**Closed issues:** + +- 素材管理 -- 部分图片下载失败 [\#13](https://github.com/overtrue/wechat/issues/13) +- 素材管理 -- 图片下载失败 [\#12](https://github.com/overtrue/wechat/issues/12) +- 请问这样判断Mcrypt到底准不准? [\#11](https://github.com/overtrue/wechat/issues/11) +- 好奇怪啊,开发者中心的服务器配置已经提交并验证成功了,可是message不起作用 [\#10](https://github.com/overtrue/wechat/issues/10) +- 网页授权一刷新页面就出现40029 不合法的oauth\_code [\#8](https://github.com/overtrue/wechat/issues/8) +- mcrypt\_module\_open error [\#7](https://github.com/overtrue/wechat/issues/7) +- composer update 之后报错 [\#6](https://github.com/overtrue/wechat/issues/6) +- 今天开始,授权时候一直报40029,invalid code的错误 [\#5](https://github.com/overtrue/wechat/issues/5) +- Using $this when not in object context [\#4](https://github.com/overtrue/wechat/issues/4) +- 监听事件时不区分 $target(监听所有event和message) [\#3](https://github.com/overtrue/wechat/issues/3) +- Does this support Oauth already? [\#1](https://github.com/overtrue/wechat/issues/1) + +**Merged pull requests:** + +- Fix wiki url error [\#9](https://github.com/overtrue/wechat/pull/9) ([sinoon](https://github.com/sinoon)) +- Update Bag.php [\#2](https://github.com/overtrue/wechat/pull/2) ([zerozh](https://github.com/zerozh)) + +## [1.0.1](https://github.com/overtrue/wechat/tree/1.0.1) (2015-03-19) +[Full Changelog](https://github.com/overtrue/wechat/compare/1.0...1.0.1) + +## [1.0](https://github.com/overtrue/wechat/tree/1.0) (2015-03-13) + + +\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* \ No newline at end of file diff --git a/vendor/overtrue/wechat/CONTRIBUTING.md b/vendor/overtrue/wechat/CONTRIBUTING.md new file mode 100644 index 0000000..134204f --- /dev/null +++ b/vendor/overtrue/wechat/CONTRIBUTING.md @@ -0,0 +1,67 @@ +# Contribute + +## Introduction + +First, thank you for considering contributing to wechat! It's people like you that make the open source community such a great community! 😊 + +We welcome any type of contribution, not only code. You can help with +- **QA**: file bug reports, the more details you can give the better (e.g. screenshots with the console open) +- **Marketing**: writing blog posts, howto's, printing stickers, ... +- **Community**: presenting the project at meetups, organizing a dedicated meetup for the local community, ... +- **Code**: take a look at the [open issues](issues). Even if you can't write code, commenting on them, showing that you care about a given issue matters. It helps us triage them. +- **Money**: we welcome financial contributions in full transparency on our [open collective](https://opencollective.com/wechat). + +## Your First Contribution + +Working on your first Pull Request? You can learn how from this *free* series, [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github). + +## Submitting code + +Any code change should be submitted as a pull request. The description should explain what the code does and give steps to execute it. The pull request should also contain tests. + +## Code review process + +The bigger the pull request, the longer it will take to review and merge. Try to break down large pull requests in smaller chunks that are easier to review and merge. +It is also always helpful to have some context for your pull request. What was the purpose? Why does it matter to you? + +## Financial contributions + +We also welcome financial contributions in full transparency on our [open collective](https://opencollective.com/wechat). +Anyone can file an expense. If the expense makes sense for the development of the community, it will be "merged" in the ledger of our open collective by the core contributors and the person who filed the expense will be reimbursed. + +## Questions + +If you have any questions, create an [issue](issue) (protip: do a quick search first to see if someone else didn't ask the same question before!). +You can also reach us at hello@wechat.opencollective.com. + +## Credits + +### Contributors + +Thank you to all the people who have already contributed to wechat! + + + +### Backers + +Thank you to all our backers! [[Become a backer](https://opencollective.com/wechat#backer)] + + + + +### Sponsors + +Thank you to all our sponsors! (please ask your company to also support this open source project by [becoming a sponsor](https://opencollective.com/wechat#sponsor)) + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/overtrue/wechat/LICENSE b/vendor/overtrue/wechat/LICENSE new file mode 100644 index 0000000..f80ba02 --- /dev/null +++ b/vendor/overtrue/wechat/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) overtrue + +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/overtrue/wechat/README.md b/vendor/overtrue/wechat/README.md new file mode 100644 index 0000000..1539c52 --- /dev/null +++ b/vendor/overtrue/wechat/README.md @@ -0,0 +1,92 @@ +EasyWeChat Logo + +

    EasyWeChat

    + +📦 It is probably the best SDK in the world for developing Wechat App. + +[![Test Status](https://github.com/overtrue/wechat/workflows/Test/badge.svg)](https://github.com/overtrue/wechat/actions) +[![Lint Status](https://github.com/overtrue/wechat/workflows/Lint/badge.svg)](https://github.com/overtrue/wechat/actions) +[![Latest Stable Version](https://poser.pugx.org/overtrue/wechat/v/stable.svg)](https://packagist.org/packages/overtrue/wechat) +[![Latest Unstable Version](https://poser.pugx.org/overtrue/wechat/v/unstable.svg)](https://packagist.org/packages/overtrue/wechat) +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/overtrue/wechat/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/overtrue/wechat/?branch=master) +[![Code Coverage](https://scrutinizer-ci.com/g/overtrue/wechat/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/overtrue/wechat/?branch=master) +[![Total Downloads](https://poser.pugx.org/overtrue/wechat/downloads)](https://packagist.org/packages/overtrue/wechat) +[![License](https://poser.pugx.org/overtrue/wechat/license)](https://packagist.org/packages/overtrue/wechat) + + +## Requirement + +1. PHP >= 7.1 +2. **[Composer](https://getcomposer.org/)** +3. openssl 拓展 +4. fileinfo 拓展(素材管理模块需要用到) + +## Installation + +```shell +$ composer require "overtrue/wechat:^4.2" -vvv +``` + +## Usage + +基本使用(以服务端为例): + +```php + 'wx3cf0f39249eb0exxx', + 'secret' => 'f1c242f4f28f735d4687abb469072xxx', + 'token' => 'easywechat', + 'log' => [ + 'level' => 'debug', + 'file' => '/tmp/easywechat.log', + ], + // ... +]; + +$app = Factory::officialAccount($options); + +$server = $app->server; +$user = $app->user; + +$server->push(function($message) use ($user) { + $fromUser = $user->get($message['FromUserName']); + + return "{$fromUser->nickname} 您好!欢迎关注 overtrue!"; +}); + +$server->serve()->send(); +``` + +更多请参考 [https://www.easywechat.com/](https://www.easywechat.com/)。 + +## Documentation + +[官网](https://www.easywechat.com) · [教程](https://www.easywechat.com/tutorials) · [讨论](https://yike.io/) · [微信公众平台](https://mp.weixin.qq.com/wiki) · [WeChat Official](http://admin.wechat.com/wiki) + +## Integration + +[Laravel 5 拓展包: overtrue/laravel-wechat](https://github.com/overtrue/laravel-wechat) + +## Contributors + +This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)]. + + + +## PHP 扩展包开发 + +> 想知道如何从零开始构建 PHP 扩展包? +> +> 请关注我的实战课程,我会在此课程中分享一些扩展开发经验 —— [《PHP 扩展包实战教程 - 从入门到发布》](https://learnku.com/courses/creating-package) + + +## License + +MIT + + +[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fovertrue%2Fwechat.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fovertrue%2Fwechat?ref=badge_large) diff --git a/vendor/overtrue/wechat/composer.json b/vendor/overtrue/wechat/composer.json new file mode 100644 index 0000000..e2547c6 --- /dev/null +++ b/vendor/overtrue/wechat/composer.json @@ -0,0 +1,59 @@ +{ + "name": "overtrue/wechat", + "description": "微信SDK", + "keywords": [ + "wechat", + "weixin", + "weixin-sdk", + "sdk" + ], + "license": "MIT", + "authors": [ + { + "name": "overtrue", + "email": "anzhengchao@gmail.com" + } + ], + "require": { + "php": ">=7.1", + "ext-fileinfo": "*", + "ext-openssl": "*", + "ext-simplexml": "*", + "easywechat-composer/easywechat-composer": "^1.1", + "guzzlehttp/guzzle": "^6.2", + "monolog/monolog": "^1.22 || ^2.0", + "overtrue/socialite": "~2.0", + "pimple/pimple": "^3.0", + "psr/simple-cache": "^1.0", + "symfony/cache": "^3.3 || ^4.3", + "symfony/event-dispatcher": "^4.3", + "symfony/http-foundation": "^2.7 || ^3.0 || ^4.0", + "symfony/psr-http-message-bridge": "^0.3 || ^1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.15", + "mikey179/vfsstream": "^1.6", + "mockery/mockery": "^1.2.3", + "phpstan/phpstan": "^0.11.12", + "phpunit/phpunit": "^7.5" + }, + "autoload": { + "psr-4": { + "EasyWeChat\\": "src/" + }, + "files": [ + "src/Kernel/Support/Helpers.php", + "src/Kernel/Helpers.php" + ] + }, + "autoload-dev": { + "psr-4": { + "EasyWeChat\\Tests\\": "tests/" + } + }, + "scripts": { + "phpcs": "vendor/bin/php-cs-fixer fix", + "phpstan": "vendor/bin/phpstan analyse", + "test": "vendor/bin/phpunit" + } +} diff --git a/vendor/overtrue/wechat/src/BasicService/Application.php b/vendor/overtrue/wechat/src/BasicService/Application.php new file mode 100644 index 0000000..e4b1e8a --- /dev/null +++ b/vendor/overtrue/wechat/src/BasicService/Application.php @@ -0,0 +1,39 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\BasicService; + +use EasyWeChat\Kernel\ServiceContainer; + +/** + * Class Application. + * + * @author overtrue + * + * @property \EasyWeChat\BasicService\Jssdk\Client $jssdk + * @property \EasyWeChat\BasicService\Media\Client $media + * @property \EasyWeChat\BasicService\QrCode\Client $qrcode + * @property \EasyWeChat\BasicService\Url\Client $url + * @property \EasyWeChat\BasicService\ContentSecurity\Client $content_security + */ +class Application extends ServiceContainer +{ + /** + * @var array + */ + protected $providers = [ + Jssdk\ServiceProvider::class, + QrCode\ServiceProvider::class, + Media\ServiceProvider::class, + Url\ServiceProvider::class, + ContentSecurity\ServiceProvider::class, + ]; +} diff --git a/vendor/overtrue/wechat/src/BasicService/ContentSecurity/Client.php b/vendor/overtrue/wechat/src/BasicService/ContentSecurity/Client.php new file mode 100644 index 0000000..7bc11bd --- /dev/null +++ b/vendor/overtrue/wechat/src/BasicService/ContentSecurity/Client.php @@ -0,0 +1,123 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\BasicService\ContentSecurity; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; + +/** + * Class Client. + * + * @author tianyong90 <412039588@qq.com> + */ +class Client extends BaseClient +{ + /** + * @var string + */ + protected $baseUri = 'https://api.weixin.qq.com/wxa/'; + + /** + * Text content security check. + * + * @param string $text + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function checkText(string $text) + { + $params = [ + 'content' => $text, + ]; + + return $this->httpPostJson('msg_sec_check', $params); + } + + /** + * Image security check. + * + * @param string $path + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function checkImage(string $path) + { + return $this->httpUpload('img_sec_check', ['media' => $path]); + } + + /** + * Media security check. + * + * @param string $mediaUrl + * @param int $mediaType + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + public function checkMediaAsync(string $mediaUrl, int $mediaType) + { + /* + * 1:音频;2:图片 + */ + $mediaTypes = [1, 2]; + + if (!in_array($mediaType, $mediaTypes, true)) { + throw new InvalidArgumentException('media type must be 1 or 2'); + } + + $params = [ + 'media_url' => $mediaUrl, + 'media_type' => $mediaType, + ]; + + return $this->httpPostJson('media_check_async', $params); + } + + /** + * Image security check async. + * + * @param string $mediaUrl + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function checkImageAsync(string $mediaUrl) + { + return $this->checkMediaAsync($mediaUrl, 2); + } + + /** + * Audio security check async. + * + * @param string $mediaUrl + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function checkAudioAsync(string $mediaUrl) + { + return $this->checkMediaAsync($mediaUrl, 1); + } +} diff --git a/vendor/overtrue/wechat/src/BasicService/ContentSecurity/ServiceProvider.php b/vendor/overtrue/wechat/src/BasicService/ContentSecurity/ServiceProvider.php new file mode 100644 index 0000000..3645de9 --- /dev/null +++ b/vendor/overtrue/wechat/src/BasicService/ContentSecurity/ServiceProvider.php @@ -0,0 +1,31 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\BasicService\ContentSecurity; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['content_security'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/BasicService/Jssdk/Client.php b/vendor/overtrue/wechat/src/BasicService/Jssdk/Client.php new file mode 100644 index 0000000..33367fc --- /dev/null +++ b/vendor/overtrue/wechat/src/BasicService/Jssdk/Client.php @@ -0,0 +1,207 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\BasicService\Jssdk; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\Exceptions\RuntimeException; +use EasyWeChat\Kernel\Support; +use EasyWeChat\Kernel\Traits\InteractsWithCache; + +/** + * Class Client. + * + * @author overtrue + */ +class Client extends BaseClient +{ + use InteractsWithCache; + + /** + * @var string + */ + protected $ticketEndpoint = 'https://api.weixin.qq.com/cgi-bin/ticket/getticket'; + + /** + * Current URI. + * + * @var string + */ + protected $url; + + /** + * Get config json for jsapi. + * + * @param array $jsApiList + * @param bool $debug + * @param bool $beta + * @param bool $json + * + * @return array|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \Psr\SimpleCache\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + public function buildConfig(array $jsApiList, bool $debug = false, bool $beta = false, bool $json = true) + { + $config = array_merge(compact('debug', 'beta', 'jsApiList'), $this->configSignature()); + + return $json ? json_encode($config) : $config; + } + + /** + * Return jsapi config as a PHP array. + * + * @param array $apis + * @param bool $debug + * @param bool $beta + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \Psr\SimpleCache\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + public function getConfigArray(array $apis, bool $debug = false, bool $beta = false) + { + return $this->buildConfig($apis, $debug, $beta, false); + } + + /** + * Get js ticket. + * + * @param bool $refresh + * @param string $type + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + * @throws \GuzzleHttp\Exception\GuzzleException + * @throws \Psr\SimpleCache\InvalidArgumentException + */ + public function getTicket(bool $refresh = false, string $type = 'jsapi'): array + { + $cacheKey = sprintf('easywechat.basic_service.jssdk.ticket.%s.%s', $type, $this->getAppId()); + + if (!$refresh && $this->getCache()->has($cacheKey)) { + return $this->getCache()->get($cacheKey); + } + + /** @var array $result */ + $result = $this->castResponseToType( + $this->requestRaw($this->ticketEndpoint, 'GET', ['query' => ['type' => $type]]), + 'array' + ); + + $this->getCache()->set($cacheKey, $result, $result['expires_in'] - 500); + + if (!$this->getCache()->has($cacheKey)) { + throw new RuntimeException('Failed to cache jssdk ticket.'); + } + + return $result; + } + + /** + * Build signature. + * + * @param string|null $url + * @param string|null $nonce + * @param int|null $timestamp + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + * @throws \Psr\SimpleCache\InvalidArgumentException + */ + protected function configSignature(string $url = null, string $nonce = null, $timestamp = null): array + { + $url = $url ?: $this->getUrl(); + $nonce = $nonce ?: Support\Str::quickRandom(10); + $timestamp = $timestamp ?: time(); + + return [ + 'appId' => $this->getAppId(), + 'nonceStr' => $nonce, + 'timestamp' => $timestamp, + 'url' => $url, + 'signature' => $this->getTicketSignature($this->getTicket()['ticket'], $nonce, $timestamp, $url), + ]; + } + + /** + * Sign the params. + * + * @param string $ticket + * @param string $nonce + * @param int $timestamp + * @param string $url + * + * @return string + */ + public function getTicketSignature($ticket, $nonce, $timestamp, $url): string + { + return sha1(sprintf('jsapi_ticket=%s&noncestr=%s×tamp=%s&url=%s', $ticket, $nonce, $timestamp, $url)); + } + + /** + * @return string + */ + public function dictionaryOrderSignature() + { + $params = func_get_args(); + + sort($params, SORT_STRING); + + return sha1(implode('', $params)); + } + + /** + * Set current url. + * + * @param string $url + * + * @return $this + */ + public function setUrl(string $url) + { + $this->url = $url; + + return $this; + } + + /** + * Get current url. + * + * @return string + */ + public function getUrl(): string + { + if ($this->url) { + return $this->url; + } + + return Support\current_url(); + } + + /** + * @return string + */ + protected function getAppId() + { + return $this->app['config']->get('app_id'); + } +} diff --git a/vendor/overtrue/wechat/src/BasicService/Jssdk/ServiceProvider.php b/vendor/overtrue/wechat/src/BasicService/Jssdk/ServiceProvider.php new file mode 100644 index 0000000..5581a1e --- /dev/null +++ b/vendor/overtrue/wechat/src/BasicService/Jssdk/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\BasicService\Jssdk; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['jssdk'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/BasicService/Media/Client.php b/vendor/overtrue/wechat/src/BasicService/Media/Client.php new file mode 100644 index 0000000..13f1afa --- /dev/null +++ b/vendor/overtrue/wechat/src/BasicService/Media/Client.php @@ -0,0 +1,212 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\BasicService\Media; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; +use EasyWeChat\Kernel\Http\StreamResponse; + +/** + * Class Client. + * + * @author overtrue + */ +class Client extends BaseClient +{ + /** + * @var string + */ + protected $baseUri = 'https://api.weixin.qq.com/cgi-bin/'; + + /** + * Allow media type. + * + * @var array + */ + protected $allowTypes = ['image', 'voice', 'video', 'thumb']; + + /** + * Upload image. + * + * @param string $path + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function uploadImage($path) + { + return $this->upload('image', $path); + } + + /** + * Upload video. + * + * @param string $path + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function uploadVideo($path) + { + return $this->upload('video', $path); + } + + /** + * @param string $path + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function uploadVoice($path) + { + return $this->upload('voice', $path); + } + + /** + * @param string $path + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function uploadThumb($path) + { + return $this->upload('thumb', $path); + } + + /** + * Upload temporary material. + * + * @param string $type + * @param string $path + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function upload(string $type, string $path) + { + if (!file_exists($path) || !is_readable($path)) { + throw new InvalidArgumentException(sprintf("File does not exist, or the file is unreadable: '%s'", $path)); + } + + if (!in_array($type, $this->allowTypes, true)) { + throw new InvalidArgumentException(sprintf("Unsupported media type: '%s'", $type)); + } + + return $this->httpUpload('media/upload', ['media' => $path], ['type' => $type]); + } + + /** + * @param string $path + * @param string $title + * @param string $description + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function uploadVideoForBroadcasting(string $path, string $title, string $description) + { + $response = $this->uploadVideo($path); + /** @var array $arrayResponse */ + $arrayResponse = $this->detectAndCastResponseToType($response, 'array'); + + if (!empty($arrayResponse['media_id'])) { + return $this->createVideoForBroadcasting($arrayResponse['media_id'], $title, $description); + } + + return $response; + } + + /** + * @param string $mediaId + * @param string $title + * @param string $description + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function createVideoForBroadcasting(string $mediaId, string $title, string $description) + { + return $this->httpPostJson('media/uploadvideo', [ + 'media_id' => $mediaId, + 'title' => $title, + 'description' => $description, + ]); + } + + /** + * Fetch item from WeChat server. + * + * @param string $mediaId + * + * @return \EasyWeChat\Kernel\Http\StreamResponse|\Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function get(string $mediaId) + { + $response = $this->requestRaw('media/get', 'GET', [ + 'query' => [ + 'media_id' => $mediaId, + ], + ]); + + if (false !== stripos($response->getHeaderLine('Content-disposition'), 'attachment')) { + return StreamResponse::buildFromPsrResponse($response); + } + + return $this->castResponseToType($response, $this->app['config']->get('response_type')); + } + + /** + * @param string $mediaId + * + * @return array|\EasyWeChat\Kernel\Http\Response|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getJssdkMedia(string $mediaId) + { + $response = $this->requestRaw('media/get/jssdk', 'GET', [ + 'query' => [ + 'media_id' => $mediaId, + ], + ]); + + if (false !== stripos($response->getHeaderLine('Content-disposition'), 'attachment')) { + return StreamResponse::buildFromPsrResponse($response); + } + + return $this->castResponseToType($response, $this->app['config']->get('response_type')); + } +} diff --git a/vendor/overtrue/wechat/src/BasicService/Media/ServiceProvider.php b/vendor/overtrue/wechat/src/BasicService/Media/ServiceProvider.php new file mode 100644 index 0000000..45de142 --- /dev/null +++ b/vendor/overtrue/wechat/src/BasicService/Media/ServiceProvider.php @@ -0,0 +1,44 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * ServiceProvider.php. + * + * This file is part of the wechat. + * + * (c) overtrue + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\BasicService\Media; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['media'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/BasicService/QrCode/Client.php b/vendor/overtrue/wechat/src/BasicService/QrCode/Client.php new file mode 100644 index 0000000..ac606a3 --- /dev/null +++ b/vendor/overtrue/wechat/src/BasicService/QrCode/Client.php @@ -0,0 +1,120 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\BasicService\QrCode; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author overtrue + */ +class Client extends BaseClient +{ + /** + * @var string + */ + protected $baseUri = 'https://api.weixin.qq.com/cgi-bin/'; + + const DAY = 86400; + const SCENE_MAX_VALUE = 100000; + const SCENE_QR_CARD = 'QR_CARD'; + const SCENE_QR_TEMPORARY = 'QR_SCENE'; + const SCENE_QR_TEMPORARY_STR = 'QR_STR_SCENE'; + const SCENE_QR_FOREVER = 'QR_LIMIT_SCENE'; + const SCENE_QR_FOREVER_STR = 'QR_LIMIT_STR_SCENE'; + + /** + * Create forever QR code. + * + * @param string|int $sceneValue + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + */ + public function forever($sceneValue) + { + if (is_int($sceneValue) && $sceneValue > 0 && $sceneValue < self::SCENE_MAX_VALUE) { + $type = self::SCENE_QR_FOREVER; + $sceneKey = 'scene_id'; + } else { + $type = self::SCENE_QR_FOREVER_STR; + $sceneKey = 'scene_str'; + } + $scene = [$sceneKey => $sceneValue]; + + return $this->create($type, $scene, false); + } + + /** + * Create temporary QR code. + * + * @param string|int $sceneValue + * @param int|null $expireSeconds + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + */ + public function temporary($sceneValue, $expireSeconds = null) + { + if (is_int($sceneValue) && $sceneValue > 0) { + $type = self::SCENE_QR_TEMPORARY; + $sceneKey = 'scene_id'; + } else { + $type = self::SCENE_QR_TEMPORARY_STR; + $sceneKey = 'scene_str'; + } + $scene = [$sceneKey => $sceneValue]; + + return $this->create($type, $scene, true, $expireSeconds); + } + + /** + * Return url for ticket. + * Detail: https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1443433542 . + * + * @param string $ticket + * + * @return string + */ + public function url($ticket) + { + return sprintf('https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=%s', urlencode($ticket)); + } + + /** + * Create a QrCode. + * + * @param string $actionName + * @param array $actionInfo + * @param bool $temporary + * @param int $expireSeconds + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function create($actionName, $actionInfo, $temporary = true, $expireSeconds = null) + { + null !== $expireSeconds || $expireSeconds = 7 * self::DAY; + + $params = [ + 'action_name' => $actionName, + 'action_info' => ['scene' => $actionInfo], + ]; + + if ($temporary) { + $params['expire_seconds'] = min($expireSeconds, 30 * self::DAY); + } + + return $this->httpPostJson('qrcode/create', $params); + } +} diff --git a/vendor/overtrue/wechat/src/BasicService/QrCode/ServiceProvider.php b/vendor/overtrue/wechat/src/BasicService/QrCode/ServiceProvider.php new file mode 100644 index 0000000..1638c66 --- /dev/null +++ b/vendor/overtrue/wechat/src/BasicService/QrCode/ServiceProvider.php @@ -0,0 +1,31 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\BasicService\QrCode; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['qrcode'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/BasicService/Url/Client.php b/vendor/overtrue/wechat/src/BasicService/Url/Client.php new file mode 100644 index 0000000..19a5fb1 --- /dev/null +++ b/vendor/overtrue/wechat/src/BasicService/Url/Client.php @@ -0,0 +1,47 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\BasicService\Url; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author overtrue + */ +class Client extends BaseClient +{ + /** + * @var string + */ + protected $baseUri = 'https://api.weixin.qq.com/'; + + /** + * Shorten the url. + * + * @param string $url + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function shorten(string $url) + { + $params = [ + 'action' => 'long2short', + 'long_url' => $url, + ]; + + return $this->httpPostJson('cgi-bin/shorturl', $params); + } +} diff --git a/vendor/overtrue/wechat/src/BasicService/Url/ServiceProvider.php b/vendor/overtrue/wechat/src/BasicService/Url/ServiceProvider.php new file mode 100644 index 0000000..6740bb8 --- /dev/null +++ b/vendor/overtrue/wechat/src/BasicService/Url/ServiceProvider.php @@ -0,0 +1,31 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\BasicService\Url; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['url'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Factory.php b/vendor/overtrue/wechat/src/Factory.php new file mode 100644 index 0000000..295b4ff --- /dev/null +++ b/vendor/overtrue/wechat/src/Factory.php @@ -0,0 +1,54 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat; + +/** + * Class Factory. + * + * @method static \EasyWeChat\Payment\Application payment(array $config) + * @method static \EasyWeChat\MiniProgram\Application miniProgram(array $config) + * @method static \EasyWeChat\OpenPlatform\Application openPlatform(array $config) + * @method static \EasyWeChat\OfficialAccount\Application officialAccount(array $config) + * @method static \EasyWeChat\BasicService\Application basicService(array $config) + * @method static \EasyWeChat\Work\Application work(array $config) + * @method static \EasyWeChat\OpenWork\Application openWork(array $config) + * @method static \EasyWeChat\MicroMerchant\Application microMerchant(array $config) + */ +class Factory +{ + /** + * @param string $name + * @param array $config + * + * @return \EasyWeChat\Kernel\ServiceContainer + */ + public static function make($name, array $config) + { + $namespace = Kernel\Support\Str::studly($name); + $application = "\\EasyWeChat\\{$namespace}\\Application"; + + return new $application($config); + } + + /** + * Dynamically pass methods to the application. + * + * @param string $name + * @param array $arguments + * + * @return mixed + */ + public static function __callStatic($name, $arguments) + { + return self::make($name, ...$arguments); + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/AccessToken.php b/vendor/overtrue/wechat/src/Kernel/AccessToken.php new file mode 100644 index 0000000..77f23c0 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/AccessToken.php @@ -0,0 +1,282 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel; + +use EasyWeChat\Kernel\Contracts\AccessTokenInterface; +use EasyWeChat\Kernel\Exceptions\HttpException; +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; +use EasyWeChat\Kernel\Exceptions\RuntimeException; +use EasyWeChat\Kernel\Traits\HasHttpRequests; +use EasyWeChat\Kernel\Traits\InteractsWithCache; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; + +/** + * Class AccessToken. + * + * @author overtrue + */ +abstract class AccessToken implements AccessTokenInterface +{ + use HasHttpRequests; + use InteractsWithCache; + + /** + * @var \EasyWeChat\Kernel\ServiceContainer + */ + protected $app; + + /** + * @var string + */ + protected $requestMethod = 'GET'; + + /** + * @var string + */ + protected $endpointToGetToken; + + /** + * @var string + */ + protected $queryName; + + /** + * @var array + */ + protected $token; + + /** + * @var int + */ + protected $safeSeconds = 500; + + /** + * @var string + */ + protected $tokenKey = 'access_token'; + + /** + * @var string + */ + protected $cachePrefix = 'easywechat.kernel.access_token.'; + + /** + * AccessToken constructor. + * + * @param \EasyWeChat\Kernel\ServiceContainer $app + */ + public function __construct(ServiceContainer $app) + { + $this->app = $app; + } + + /** + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\HttpException + * @throws \Psr\SimpleCache\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + public function getRefreshedToken(): array + { + return $this->getToken(true); + } + + /** + * @param bool $refresh + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\HttpException + * @throws \Psr\SimpleCache\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + public function getToken(bool $refresh = false): array + { + $cacheKey = $this->getCacheKey(); + $cache = $this->getCache(); + + if (!$refresh && $cache->has($cacheKey)) { + return $cache->get($cacheKey); + } + + /** @var array $token */ + $token = $this->requestToken($this->getCredentials(), true); + + $this->setToken($token[$this->tokenKey], $token['expires_in'] ?? 7200); + + $this->app->events->dispatch(new Events\AccessTokenRefreshed($this)); + + return $token; + } + + /** + * @param string $token + * @param int $lifetime + * + * @return \EasyWeChat\Kernel\Contracts\AccessTokenInterface + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + * @throws \Psr\SimpleCache\InvalidArgumentException + */ + public function setToken(string $token, int $lifetime = 7200): AccessTokenInterface + { + $this->getCache()->set($this->getCacheKey(), [ + $this->tokenKey => $token, + 'expires_in' => $lifetime, + ], $lifetime - $this->safeSeconds); + + if (!$this->getCache()->has($this->getCacheKey())) { + throw new RuntimeException('Failed to cache access token.'); + } + + return $this; + } + + /** + * @return \EasyWeChat\Kernel\Contracts\AccessTokenInterface + * + * @throws \EasyWeChat\Kernel\Exceptions\HttpException + * @throws \Psr\SimpleCache\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + public function refresh(): AccessTokenInterface + { + $this->getToken(true); + + return $this; + } + + /** + * @param array $credentials + * @param bool $toArray + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\HttpException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + public function requestToken(array $credentials, $toArray = false) + { + $response = $this->sendRequest($credentials); + $result = json_decode($response->getBody()->getContents(), true); + $formatted = $this->castResponseToType($response, $this->app['config']->get('response_type')); + + if (empty($result[$this->tokenKey])) { + throw new HttpException('Request access_token fail: '.json_encode($result, JSON_UNESCAPED_UNICODE), $response, $formatted); + } + + return $toArray ? $result : $formatted; + } + + /** + * @param \Psr\Http\Message\RequestInterface $request + * @param array $requestOptions + * + * @return \Psr\Http\Message\RequestInterface + * + * @throws \EasyWeChat\Kernel\Exceptions\HttpException + * @throws \Psr\SimpleCache\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + public function applyToRequest(RequestInterface $request, array $requestOptions = []): RequestInterface + { + parse_str($request->getUri()->getQuery(), $query); + + $query = http_build_query(array_merge($this->getQuery(), $query)); + + return $request->withUri($request->getUri()->withQuery($query)); + } + + /** + * Send http request. + * + * @param array $credentials + * + * @return ResponseInterface + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function sendRequest(array $credentials): ResponseInterface + { + $options = [ + ('GET' === $this->requestMethod) ? 'query' : 'json' => $credentials, + ]; + + return $this->setHttpClient($this->app['http_client'])->request($this->getEndpoint(), $this->requestMethod, $options); + } + + /** + * @return string + */ + protected function getCacheKey() + { + return $this->cachePrefix.md5(json_encode($this->getCredentials())); + } + + /** + * The request query will be used to add to the request. + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\HttpException + * @throws \Psr\SimpleCache\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + protected function getQuery(): array + { + return [$this->queryName ?? $this->tokenKey => $this->getToken()[$this->tokenKey]]; + } + + /** + * @return string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + public function getEndpoint(): string + { + if (empty($this->endpointToGetToken)) { + throw new InvalidArgumentException('No endpoint for access token request.'); + } + + return $this->endpointToGetToken; + } + + /** + * @return string + */ + public function getTokenKey() + { + return $this->tokenKey; + } + + /** + * Credential for get token. + * + * @return array + */ + abstract protected function getCredentials(): array; +} diff --git a/vendor/overtrue/wechat/src/Kernel/BaseClient.php b/vendor/overtrue/wechat/src/Kernel/BaseClient.php new file mode 100644 index 0000000..75e79e8 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/BaseClient.php @@ -0,0 +1,271 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel; + +use EasyWeChat\Kernel\Contracts\AccessTokenInterface; +use EasyWeChat\Kernel\Http\Response; +use EasyWeChat\Kernel\Traits\HasHttpRequests; +use GuzzleHttp\MessageFormatter; +use GuzzleHttp\Middleware; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Log\LogLevel; + +/** + * Class BaseClient. + * + * @author overtrue + */ +class BaseClient +{ + use HasHttpRequests { request as performRequest; } + + /** + * @var \EasyWeChat\Kernel\ServiceContainer + */ + protected $app; + + /** + * @var \EasyWeChat\Kernel\Contracts\AccessTokenInterface + */ + protected $accessToken; + + /** + * @var string + */ + protected $baseUri; + + /** + * BaseClient constructor. + * + * @param \EasyWeChat\Kernel\ServiceContainer $app + * @param \EasyWeChat\Kernel\Contracts\AccessTokenInterface|null $accessToken + */ + public function __construct(ServiceContainer $app, AccessTokenInterface $accessToken = null) + { + $this->app = $app; + $this->accessToken = $accessToken ?? $this->app['access_token']; + } + + /** + * GET request. + * + * @param string $url + * @param array $query + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function httpGet(string $url, array $query = []) + { + return $this->request($url, 'GET', ['query' => $query]); + } + + /** + * POST request. + * + * @param string $url + * @param array $data + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function httpPost(string $url, array $data = []) + { + return $this->request($url, 'POST', ['form_params' => $data]); + } + + /** + * JSON request. + * + * @param string $url + * @param array $data + * @param array $query + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function httpPostJson(string $url, array $data = [], array $query = []) + { + return $this->request($url, 'POST', ['query' => $query, 'json' => $data]); + } + + /** + * Upload file. + * + * @param string $url + * @param array $files + * @param array $form + * @param array $query + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function httpUpload(string $url, array $files = [], array $form = [], array $query = []) + { + $multipart = []; + + foreach ($files as $name => $path) { + $multipart[] = [ + 'name' => $name, + 'contents' => fopen($path, 'r'), + ]; + } + + foreach ($form as $name => $contents) { + $multipart[] = compact('name', 'contents'); + } + + return $this->request($url, 'POST', ['query' => $query, 'multipart' => $multipart, 'connect_timeout' => 30, 'timeout' => 30, 'read_timeout' => 30]); + } + + /** + * @return AccessTokenInterface + */ + public function getAccessToken(): AccessTokenInterface + { + return $this->accessToken; + } + + /** + * @param \EasyWeChat\Kernel\Contracts\AccessTokenInterface $accessToken + * + * @return $this + */ + public function setAccessToken(AccessTokenInterface $accessToken) + { + $this->accessToken = $accessToken; + + return $this; + } + + /** + * @param string $url + * @param string $method + * @param array $options + * @param bool $returnRaw + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function request(string $url, string $method = 'GET', array $options = [], $returnRaw = false) + { + if (empty($this->middlewares)) { + $this->registerHttpMiddlewares(); + } + + $response = $this->performRequest($url, $method, $options); + + $this->app->events->dispatch(new Events\HttpResponseCreated($response)); + + return $returnRaw ? $response : $this->castResponseToType($response, $this->app->config->get('response_type')); + } + + /** + * @param string $url + * @param string $method + * @param array $options + * + * @return \EasyWeChat\Kernel\Http\Response + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function requestRaw(string $url, string $method = 'GET', array $options = []) + { + return Response::buildFromPsrResponse($this->request($url, $method, $options, true)); + } + + /** + * Register Guzzle middlewares. + */ + protected function registerHttpMiddlewares() + { + // retry + $this->pushMiddleware($this->retryMiddleware(), 'retry'); + // access token + $this->pushMiddleware($this->accessTokenMiddleware(), 'access_token'); + // log + $this->pushMiddleware($this->logMiddleware(), 'log'); + } + + /** + * Attache access token to request query. + * + * @return \Closure + */ + protected function accessTokenMiddleware() + { + return function (callable $handler) { + return function (RequestInterface $request, array $options) use ($handler) { + if ($this->accessToken) { + $request = $this->accessToken->applyToRequest($request, $options); + } + + return $handler($request, $options); + }; + }; + } + + /** + * Log the request. + * + * @return \Closure + */ + protected function logMiddleware() + { + $formatter = new MessageFormatter($this->app['config']['http.log_template'] ?? MessageFormatter::DEBUG); + + return Middleware::log($this->app['logger'], $formatter, LogLevel::DEBUG); + } + + /** + * Return retry middleware. + * + * @return \Closure + */ + protected function retryMiddleware() + { + return Middleware::retry(function ( + $retries, + RequestInterface $request, + ResponseInterface $response = null + ) { + // Limit the number of retries to 2 + if ($retries < $this->app->config->get('http.max_retries', 1) && $response && $body = $response->getBody()) { + // Retry on server errors + $response = json_decode($body, true); + + if (!empty($response['errcode']) && in_array(abs($response['errcode']), [40001, 40014, 42001], true)) { + $this->accessToken->refresh(); + $this->app['logger']->debug('Retrying with refreshed access token.'); + + return true; + } + } + + return false; + }, function () { + return abs($this->app->config->get('http.retry_delay', 500)); + }); + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Clauses/Clause.php b/vendor/overtrue/wechat/src/Kernel/Clauses/Clause.php new file mode 100644 index 0000000..49bbb22 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Clauses/Clause.php @@ -0,0 +1,64 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Clauses; + +/** + * Class Clause. + * + * @author mingyoung + */ +class Clause +{ + /** + * @var array + */ + protected $clauses = [ + 'where' => [], + ]; + + /** + * @param mixed ...$args + * + * @return $this + */ + public function where(...$args) + { + array_push($this->clauses['where'], $args); + + return $this; + } + + /** + * @param mixed $payload + * + * @return bool + */ + public function intercepted($payload) + { + return (bool) $this->interceptWhereClause($payload); + } + + /** + * @param mixed $payload + * + * @return bool + */ + protected function interceptWhereClause($payload) + { + foreach ($this->clauses['where'] as $item) { + list($key, $value) = $item; + if (isset($payload[$key]) && $payload[$key] !== $value) { + return true; + } + } + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Config.php b/vendor/overtrue/wechat/src/Kernel/Config.php new file mode 100644 index 0000000..081f6fd --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Config.php @@ -0,0 +1,23 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel; + +use EasyWeChat\Kernel\Support\Collection; + +/** + * Class Config. + * + * @author overtrue + */ +class Config extends Collection +{ +} diff --git a/vendor/overtrue/wechat/src/Kernel/Contracts/AccessTokenInterface.php b/vendor/overtrue/wechat/src/Kernel/Contracts/AccessTokenInterface.php new file mode 100644 index 0000000..56f4a4d --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Contracts/AccessTokenInterface.php @@ -0,0 +1,40 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Contracts; + +use Psr\Http\Message\RequestInterface; + +/** + * Interface AuthorizerAccessToken. + * + * @author overtrue + */ +interface AccessTokenInterface +{ + /** + * @return array + */ + public function getToken(): array; + + /** + * @return \EasyWeChat\Kernel\Contracts\AccessTokenInterface + */ + public function refresh(): self; + + /** + * @param \Psr\Http\Message\RequestInterface $request + * @param array $requestOptions + * + * @return \Psr\Http\Message\RequestInterface + */ + public function applyToRequest(RequestInterface $request, array $requestOptions = []): RequestInterface; +} diff --git a/vendor/overtrue/wechat/src/Kernel/Contracts/Arrayable.php b/vendor/overtrue/wechat/src/Kernel/Contracts/Arrayable.php new file mode 100644 index 0000000..d947f8f --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Contracts/Arrayable.php @@ -0,0 +1,29 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Contracts; + +use ArrayAccess; + +/** + * Interface Arrayable. + * + * @author overtrue + */ +interface Arrayable extends ArrayAccess +{ + /** + * Get the instance as an array. + * + * @return array + */ + public function toArray(); +} diff --git a/vendor/overtrue/wechat/src/Kernel/Contracts/EventHandlerInterface.php b/vendor/overtrue/wechat/src/Kernel/Contracts/EventHandlerInterface.php new file mode 100644 index 0000000..c9d116c --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Contracts/EventHandlerInterface.php @@ -0,0 +1,25 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Contracts; + +/** + * Interface EventHandlerInterface. + * + * @author mingyoung + */ +interface EventHandlerInterface +{ + /** + * @param mixed $payload + */ + public function handle($payload = null); +} diff --git a/vendor/overtrue/wechat/src/Kernel/Contracts/MediaInterface.php b/vendor/overtrue/wechat/src/Kernel/Contracts/MediaInterface.php new file mode 100644 index 0000000..63768cf --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Contracts/MediaInterface.php @@ -0,0 +1,25 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Contracts; + +/** + * Interface MediaInterface. + * + * @author overtrue + */ +interface MediaInterface extends MessageInterface +{ + /** + * @return string + */ + public function getMediaId(): string; +} diff --git a/vendor/overtrue/wechat/src/Kernel/Contracts/MessageInterface.php b/vendor/overtrue/wechat/src/Kernel/Contracts/MessageInterface.php new file mode 100644 index 0000000..29ddb57 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Contracts/MessageInterface.php @@ -0,0 +1,35 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Contracts; + +/** + * Interface MessageInterface. + * + * @author overtrue + */ +interface MessageInterface +{ + /** + * @return string + */ + public function getType(): string; + + /** + * @return array + */ + public function transformForJsonRequest(): array; + + /** + * @return string + */ + public function transformToXml(): string; +} diff --git a/vendor/overtrue/wechat/src/Kernel/Decorators/FinallyResult.php b/vendor/overtrue/wechat/src/Kernel/Decorators/FinallyResult.php new file mode 100644 index 0000000..e698bbf --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Decorators/FinallyResult.php @@ -0,0 +1,35 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Decorators; + +/** + * Class FinallyResult. + * + * @author overtrue + */ +class FinallyResult +{ + /** + * @var mixed + */ + public $content; + + /** + * FinallyResult constructor. + * + * @param mixed $content + */ + public function __construct($content) + { + $this->content = $content; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Decorators/TerminateResult.php b/vendor/overtrue/wechat/src/Kernel/Decorators/TerminateResult.php new file mode 100644 index 0000000..cf1042d --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Decorators/TerminateResult.php @@ -0,0 +1,35 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Decorators; + +/** + * Class TerminateResult. + * + * @author overtrue + */ +class TerminateResult +{ + /** + * @var mixed + */ + public $content; + + /** + * FinallyResult constructor. + * + * @param mixed $content + */ + public function __construct($content) + { + $this->content = $content; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Encryptor.php b/vendor/overtrue/wechat/src/Kernel/Encryptor.php new file mode 100644 index 0000000..150ad21 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Encryptor.php @@ -0,0 +1,219 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel; + +use EasyWeChat\Kernel\Exceptions\RuntimeException; +use EasyWeChat\Kernel\Support\AES; +use function EasyWeChat\Kernel\Support\str_random; +use EasyWeChat\Kernel\Support\XML; +use Throwable; + +/** + * Class Encryptor. + * + * @author overtrue + */ +class Encryptor +{ + const ERROR_INVALID_SIGNATURE = -40001; // Signature verification failed + const ERROR_PARSE_XML = -40002; // Parse XML failed + const ERROR_CALC_SIGNATURE = -40003; // Calculating the signature failed + const ERROR_INVALID_AES_KEY = -40004; // Invalid AESKey + const ERROR_INVALID_APP_ID = -40005; // Check AppID failed + const ERROR_ENCRYPT_AES = -40006; // AES EncryptionInterface failed + const ERROR_DECRYPT_AES = -40007; // AES decryption failed + const ERROR_INVALID_XML = -40008; // Invalid XML + const ERROR_BASE64_ENCODE = -40009; // Base64 encoding failed + const ERROR_BASE64_DECODE = -40010; // Base64 decoding failed + const ERROR_XML_BUILD = -40011; // XML build failed + const ILLEGAL_BUFFER = -41003; // Illegal buffer + + /** + * App id. + * + * @var string + */ + protected $appId; + + /** + * App token. + * + * @var string + */ + protected $token; + + /** + * @var string + */ + protected $aesKey; + + /** + * Block size. + * + * @var int + */ + protected $blockSize = 32; + + /** + * Constructor. + * + * @param string $appId + * @param string|null $token + * @param string|null $aesKey + */ + public function __construct(string $appId, string $token = null, string $aesKey = null) + { + $this->appId = $appId; + $this->token = $token; + $this->aesKey = base64_decode($aesKey.'=', true); + } + + /** + * Get the app token. + * + * @return string + */ + public function getToken(): string + { + return $this->token; + } + + /** + * Encrypt the message and return XML. + * + * @param string $xml + * @param string $nonce + * @param int $timestamp + * + * @return string + * + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + public function encrypt($xml, $nonce = null, $timestamp = null): string + { + try { + $xml = $this->pkcs7Pad(str_random(16).pack('N', strlen($xml)).$xml.$this->appId, $this->blockSize); + + $encrypted = base64_encode(AES::encrypt( + $xml, + $this->aesKey, + substr($this->aesKey, 0, 16), + OPENSSL_NO_PADDING + )); + // @codeCoverageIgnoreStart + } catch (Throwable $e) { + throw new RuntimeException($e->getMessage(), self::ERROR_ENCRYPT_AES); + } + // @codeCoverageIgnoreEnd + + !is_null($nonce) || $nonce = substr($this->appId, 0, 10); + !is_null($timestamp) || $timestamp = time(); + + $response = [ + 'Encrypt' => $encrypted, + 'MsgSignature' => $this->signature($this->token, $timestamp, $nonce, $encrypted), + 'TimeStamp' => $timestamp, + 'Nonce' => $nonce, + ]; + + //生成响应xml + return XML::build($response); + } + + /** + * Decrypt message. + * + * @param string $content + * @param string $msgSignature + * @param string $nonce + * @param string $timestamp + * + * @return string + * + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + public function decrypt($content, $msgSignature, $nonce, $timestamp): string + { + $signature = $this->signature($this->token, $timestamp, $nonce, $content); + + if ($signature !== $msgSignature) { + throw new RuntimeException('Invalid Signature.', self::ERROR_INVALID_SIGNATURE); + } + + $decrypted = AES::decrypt( + base64_decode($content, true), + $this->aesKey, + substr($this->aesKey, 0, 16), + OPENSSL_NO_PADDING + ); + $result = $this->pkcs7Unpad($decrypted); + $content = substr($result, 16, strlen($result)); + $contentLen = unpack('N', substr($content, 0, 4))[1]; + + if (trim(substr($content, $contentLen + 4)) !== $this->appId) { + throw new RuntimeException('Invalid appId.', self::ERROR_INVALID_APP_ID); + } + + return substr($content, 4, $contentLen); + } + + /** + * Get SHA1. + * + * @return string + */ + public function signature(): string + { + $array = func_get_args(); + sort($array, SORT_STRING); + + return sha1(implode($array)); + } + + /** + * PKCS#7 pad. + * + * @param string $text + * @param int $blockSize + * + * @return string + * + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + public function pkcs7Pad(string $text, int $blockSize): string + { + if ($blockSize > 256) { + throw new RuntimeException('$blockSize may not be more than 256'); + } + $padding = $blockSize - (strlen($text) % $blockSize); + $pattern = chr($padding); + + return $text.str_repeat($pattern, $padding); + } + + /** + * PKCS#7 unpad. + * + * @param string $text + * + * @return string + */ + public function pkcs7Unpad(string $text): string + { + $pad = ord(substr($text, -1)); + if ($pad < 1 || $pad > $this->blockSize) { + $pad = 0; + } + + return substr($text, 0, (strlen($text) - $pad)); + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Events/AccessTokenRefreshed.php b/vendor/overtrue/wechat/src/Kernel/Events/AccessTokenRefreshed.php new file mode 100644 index 0000000..a829901 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Events/AccessTokenRefreshed.php @@ -0,0 +1,35 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Events; + +use EasyWeChat\Kernel\AccessToken; + +/** + * Class AccessTokenRefreshed. + * + * @author mingyoung + */ +class AccessTokenRefreshed +{ + /** + * @var \EasyWeChat\Kernel\AccessToken + */ + public $accessToken; + + /** + * @param \EasyWeChat\Kernel\AccessToken $accessToken + */ + public function __construct(AccessToken $accessToken) + { + $this->accessToken = $accessToken; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Events/ApplicationInitialized.php b/vendor/overtrue/wechat/src/Kernel/Events/ApplicationInitialized.php new file mode 100644 index 0000000..89d67fe --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Events/ApplicationInitialized.php @@ -0,0 +1,35 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Events; + +use EasyWeChat\Kernel\ServiceContainer; + +/** + * Class ApplicationInitialized. + * + * @author mingyoung + */ +class ApplicationInitialized +{ + /** + * @var \EasyWeChat\Kernel\ServiceContainer + */ + public $app; + + /** + * @param \EasyWeChat\Kernel\ServiceContainer $app + */ + public function __construct(ServiceContainer $app) + { + $this->app = $app; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Events/HttpResponseCreated.php b/vendor/overtrue/wechat/src/Kernel/Events/HttpResponseCreated.php new file mode 100644 index 0000000..2257260 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Events/HttpResponseCreated.php @@ -0,0 +1,35 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Events; + +use Psr\Http\Message\ResponseInterface; + +/** + * Class HttpResponseCreated. + * + * @author mingyoung + */ +class HttpResponseCreated +{ + /** + * @var \Psr\Http\Message\ResponseInterface + */ + public $response; + + /** + * @param \Psr\Http\Message\ResponseInterface $response + */ + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Events/ServerGuardResponseCreated.php b/vendor/overtrue/wechat/src/Kernel/Events/ServerGuardResponseCreated.php new file mode 100644 index 0000000..3ab9925 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Events/ServerGuardResponseCreated.php @@ -0,0 +1,35 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Events; + +use Symfony\Component\HttpFoundation\Response; + +/** + * Class ServerGuardResponseCreated. + * + * @author mingyoung + */ +class ServerGuardResponseCreated +{ + /** + * @var \Symfony\Component\HttpFoundation\Response + */ + public $response; + + /** + * @param \Symfony\Component\HttpFoundation\Response $response + */ + public function __construct(Response $response) + { + $this->response = $response; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Exceptions/BadRequestException.php b/vendor/overtrue/wechat/src/Kernel/Exceptions/BadRequestException.php new file mode 100644 index 0000000..a0b4f91 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Exceptions/BadRequestException.php @@ -0,0 +1,21 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Exceptions; + +/** + * Class BadRequestException. + * + * @author overtrue + */ +class BadRequestException extends Exception +{ +} diff --git a/vendor/overtrue/wechat/src/Kernel/Exceptions/DecryptException.php b/vendor/overtrue/wechat/src/Kernel/Exceptions/DecryptException.php new file mode 100644 index 0000000..f29acca --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Exceptions/DecryptException.php @@ -0,0 +1,16 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Exceptions; + +class DecryptException extends Exception +{ +} diff --git a/vendor/overtrue/wechat/src/Kernel/Exceptions/Exception.php b/vendor/overtrue/wechat/src/Kernel/Exceptions/Exception.php new file mode 100644 index 0000000..9eba298 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Exceptions/Exception.php @@ -0,0 +1,23 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Exceptions; + +use Exception as BaseException; + +/** + * Class Exception. + * + * @author overtrue + */ +class Exception extends BaseException +{ +} diff --git a/vendor/overtrue/wechat/src/Kernel/Exceptions/HttpException.php b/vendor/overtrue/wechat/src/Kernel/Exceptions/HttpException.php new file mode 100644 index 0000000..8ab130d --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Exceptions/HttpException.php @@ -0,0 +1,52 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Exceptions; + +use Psr\Http\Message\ResponseInterface; + +/** + * Class HttpException. + * + * @author overtrue + */ +class HttpException extends Exception +{ + /** + * @var \Psr\Http\Message\ResponseInterface|null + */ + public $response; + + /** + * @var \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string|null + */ + public $formattedResponse; + + /** + * HttpException constructor. + * + * @param string $message + * @param \Psr\Http\Message\ResponseInterface|null $response + * @param null $formattedResponse + * @param int|null $code + */ + public function __construct($message, ResponseInterface $response = null, $formattedResponse = null, $code = null) + { + parent::__construct($message, $code); + + $this->response = $response; + $this->formattedResponse = $formattedResponse; + + if ($response) { + $response->getBody()->rewind(); + } + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Exceptions/InvalidArgumentException.php b/vendor/overtrue/wechat/src/Kernel/Exceptions/InvalidArgumentException.php new file mode 100644 index 0000000..386a144 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Exceptions/InvalidArgumentException.php @@ -0,0 +1,21 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Exceptions; + +/** + * Class InvalidArgumentException. + * + * @author overtrue + */ +class InvalidArgumentException extends Exception +{ +} diff --git a/vendor/overtrue/wechat/src/Kernel/Exceptions/InvalidConfigException.php b/vendor/overtrue/wechat/src/Kernel/Exceptions/InvalidConfigException.php new file mode 100644 index 0000000..1e4ae2a --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Exceptions/InvalidConfigException.php @@ -0,0 +1,21 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Exceptions; + +/** + * Class InvalidConfigException. + * + * @author overtrue + */ +class InvalidConfigException extends Exception +{ +} diff --git a/vendor/overtrue/wechat/src/Kernel/Exceptions/RuntimeException.php b/vendor/overtrue/wechat/src/Kernel/Exceptions/RuntimeException.php new file mode 100644 index 0000000..25f2f5b --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Exceptions/RuntimeException.php @@ -0,0 +1,21 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Exceptions; + +/** + * Class RuntimeException. + * + * @author overtrue + */ +class RuntimeException extends Exception +{ +} diff --git a/vendor/overtrue/wechat/src/Kernel/Exceptions/UnboundServiceException.php b/vendor/overtrue/wechat/src/Kernel/Exceptions/UnboundServiceException.php new file mode 100644 index 0000000..78ea85c --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Exceptions/UnboundServiceException.php @@ -0,0 +1,21 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Exceptions; + +/** + * Class InvalidConfigException. + * + * @author overtrue + */ +class UnboundServiceException extends Exception +{ +} diff --git a/vendor/overtrue/wechat/src/Kernel/Helpers.php b/vendor/overtrue/wechat/src/Kernel/Helpers.php new file mode 100644 index 0000000..3ea48a4 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Helpers.php @@ -0,0 +1,57 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel; + +use EasyWeChat\Kernel\Contracts\Arrayable; +use EasyWeChat\Kernel\Exceptions\RuntimeException; +use EasyWeChat\Kernel\Support\Arr; +use EasyWeChat\Kernel\Support\Collection; + +function data_get($data, $key, $default = null) +{ + switch (true) { + case is_array($data): + return Arr::get($data, $key, $default); + case $data instanceof Collection: + return $data->get($key, $default); + case $data instanceof Arrayable: + return Arr::get($data->toArray(), $key, $default); + case $data instanceof \ArrayIterator: + return $data->getArrayCopy()[$key] ?? $default; + case $data instanceof \ArrayAccess: + return $data[$key] ?? $default; + case $data instanceof \IteratorAggregate && $data->getIterator() instanceof \ArrayIterator: + return $data->getIterator()->getArrayCopy()[$key] ?? $default; + case is_object($data): + return $data->{$key} ?? $default; + default: + throw new RuntimeException(sprintf('Can\'t access data with key "%s"', $key)); + } +} + +function data_to_array($data) +{ + switch (true) { + case is_array($data): + return $data; + case $data instanceof Collection: + return $data->all(); + case $data instanceof Arrayable: + return $data->toArray(); + case $data instanceof \IteratorAggregate && $data->getIterator() instanceof \ArrayIterator: + return $data->getIterator()->getArrayCopy(); + case $data instanceof \ArrayIterator: + return $data->getArrayCopy(); + default: + throw new RuntimeException(sprintf('Can\'t transform data to array')); + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Http/Response.php b/vendor/overtrue/wechat/src/Kernel/Http/Response.php new file mode 100644 index 0000000..adcd416 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Http/Response.php @@ -0,0 +1,121 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Http; + +use EasyWeChat\Kernel\Support\Collection; +use EasyWeChat\Kernel\Support\XML; +use GuzzleHttp\Psr7\Response as GuzzleResponse; +use Psr\Http\Message\ResponseInterface; + +/** + * Class Response. + * + * @author overtrue + */ +class Response extends GuzzleResponse +{ + /** + * @return string + */ + public function getBodyContents() + { + $this->getBody()->rewind(); + $contents = $this->getBody()->getContents(); + $this->getBody()->rewind(); + + return $contents; + } + + /** + * @param \Psr\Http\Message\ResponseInterface $response + * + * @return \EasyWeChat\Kernel\Http\Response + */ + public static function buildFromPsrResponse(ResponseInterface $response) + { + return new static( + $response->getStatusCode(), + $response->getHeaders(), + $response->getBody(), + $response->getProtocolVersion(), + $response->getReasonPhrase() + ); + } + + /** + * Build to json. + * + * @return string + */ + public function toJson() + { + return json_encode($this->toArray()); + } + + /** + * Build to array. + * + * @return array + */ + public function toArray() + { + $content = $this->removeControlCharacters($this->getBodyContents()); + + if (false !== stripos($this->getHeaderLine('Content-Type'), 'xml') || 0 === stripos($content, 'toArray()); + } + + /** + * @return object + */ + public function toObject() + { + return json_decode($this->toJson()); + } + + /** + * @return bool|string + */ + public function __toString() + { + return $this->getBodyContents(); + } + + /** + * @param string $content + * + * @return string + */ + protected function removeControlCharacters(string $content) + { + return \preg_replace('/[\x00-\x1F\x80-\x9F]/u', '', $content); + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Http/StreamResponse.php b/vendor/overtrue/wechat/src/Kernel/Http/StreamResponse.php new file mode 100644 index 0000000..104e8c3 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Http/StreamResponse.php @@ -0,0 +1,86 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Http; + +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; +use EasyWeChat\Kernel\Exceptions\RuntimeException; +use EasyWeChat\Kernel\Support\File; + +/** + * Class StreamResponse. + * + * @author overtrue + */ +class StreamResponse extends Response +{ + /** + * @param string $directory + * @param string $filename + * @param bool $appendSuffix + * + * @return bool|int + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + public function save(string $directory, string $filename = '', bool $appendSuffix = true) + { + $this->getBody()->rewind(); + + $directory = rtrim($directory, '/'); + + if (!is_dir($directory)) { + mkdir($directory, 0755, true); // @codeCoverageIgnore + } + + if (!is_writable($directory)) { + throw new InvalidArgumentException(sprintf("'%s' is not writable.", $directory)); + } + + $contents = $this->getBody()->getContents(); + + if (empty($contents) || '{' === $contents[0]) { + throw new RuntimeException('Invalid media response content.'); + } + + if (empty($filename)) { + if (preg_match('/filename="(?.*?)"/', $this->getHeaderLine('Content-Disposition'), $match)) { + $filename = $match['filename']; + } else { + $filename = md5($contents); + } + } + + if ($appendSuffix && empty(pathinfo($filename, PATHINFO_EXTENSION))) { + $filename .= File::getStreamExt($contents); + } + + file_put_contents($directory.'/'.$filename, $contents); + + return $filename; + } + + /** + * @param string $directory + * @param string $filename + * @param bool $appendSuffix + * + * @return bool|int + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + public function saveAs(string $directory, string $filename, bool $appendSuffix = true) + { + return $this->save($directory, $filename, $appendSuffix); + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Log/LogManager.php b/vendor/overtrue/wechat/src/Kernel/Log/LogManager.php new file mode 100644 index 0000000..9159464 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Log/LogManager.php @@ -0,0 +1,608 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Log; + +use EasyWeChat\Kernel\ServiceContainer; +use InvalidArgumentException; +use Monolog\Formatter\LineFormatter; +use Monolog\Handler\ErrorLogHandler; +use Monolog\Handler\FormattableHandlerInterface; +use Monolog\Handler\HandlerInterface; +use Monolog\Handler\RotatingFileHandler; +use Monolog\Handler\SlackWebhookHandler; +use Monolog\Handler\StreamHandler; +use Monolog\Handler\SyslogHandler; +use Monolog\Handler\WhatFailureGroupHandler; +use Monolog\Logger as Monolog; +use Psr\Log\LoggerInterface; + +/** + * Class LogManager. + * + * @author overtrue + */ +class LogManager implements LoggerInterface +{ + /** + * @var \EasyWeChat\Kernel\ServiceContainer + */ + protected $app; + + /** + * The array of resolved channels. + * + * @var array + */ + protected $channels = []; + + /** + * The registered custom driver creators. + * + * @var array + */ + protected $customCreators = []; + + /** + * The Log levels. + * + * @var array + */ + protected $levels = [ + 'debug' => Monolog::DEBUG, + 'info' => Monolog::INFO, + 'notice' => Monolog::NOTICE, + 'warning' => Monolog::WARNING, + 'error' => Monolog::ERROR, + 'critical' => Monolog::CRITICAL, + 'alert' => Monolog::ALERT, + 'emergency' => Monolog::EMERGENCY, + ]; + + /** + * LogManager constructor. + * + * @param \EasyWeChat\Kernel\ServiceContainer $app + */ + public function __construct(ServiceContainer $app) + { + $this->app = $app; + } + + /** + * Create a new, on-demand aggregate logger instance. + * + * @param array $channels + * @param string|null $channel + * + * @return \Psr\Log\LoggerInterface + * + * @throws \Exception + */ + public function stack(array $channels, $channel = null) + { + return $this->createStackDriver(compact('channels', 'channel')); + } + + /** + * Get a log channel instance. + * + * @param string|null $channel + * + * @return mixed + * + * @throws \Exception + */ + public function channel($channel = null) + { + return $this->driver($channel); + } + + /** + * Get a log driver instance. + * + * @param string|null $driver + * + * @return mixed + * + * @throws \Exception + */ + public function driver($driver = null) + { + return $this->get($driver ?? $this->getDefaultDriver()); + } + + /** + * Attempt to get the log from the local cache. + * + * @param string $name + * + * @return \Psr\Log\LoggerInterface + * + * @throws \Exception + */ + protected function get($name) + { + try { + return $this->channels[$name] ?? ($this->channels[$name] = $this->resolve($name)); + } catch (\Throwable $e) { + $logger = $this->createEmergencyLogger(); + + $logger->emergency('Unable to create configured logger. Using emergency logger.', [ + 'exception' => $e, + ]); + + return $logger; + } + } + + /** + * Resolve the given log instance by name. + * + * @param string $name + * + * @return \Psr\Log\LoggerInterface + * + * @throws InvalidArgumentException + */ + protected function resolve($name) + { + $config = $this->app['config']->get(\sprintf('log.channels.%s', $name)); + + if (is_null($config)) { + throw new InvalidArgumentException(\sprintf('Log [%s] is not defined.', $name)); + } + + if (isset($this->customCreators[$config['driver']])) { + return $this->callCustomCreator($config); + } + + $driverMethod = 'create'.ucfirst($config['driver']).'Driver'; + + if (method_exists($this, $driverMethod)) { + return $this->{$driverMethod}($config); + } + + throw new InvalidArgumentException(\sprintf('Driver [%s] is not supported.', $config['driver'])); + } + + /** + * Create an emergency log handler to avoid white screens of death. + * + * @return \Monolog\Logger + * + * @throws \Exception + */ + protected function createEmergencyLogger() + { + return new Monolog('EasyWeChat', $this->prepareHandlers([new StreamHandler( + \sys_get_temp_dir().'/easywechat/easywechat.log', + $this->level(['level' => 'debug']) + )])); + } + + /** + * Call a custom driver creator. + * + * @param array $config + * + * @return mixed + */ + protected function callCustomCreator(array $config) + { + return $this->customCreators[$config['driver']]($this->app, $config); + } + + /** + * Create an aggregate log driver instance. + * + * @param array $config + * + * @return \Monolog\Logger + * + * @throws \Exception + */ + protected function createStackDriver(array $config) + { + $handlers = []; + + foreach ($config['channels'] ?? [] as $channel) { + $handlers = \array_merge($handlers, $this->channel($channel)->getHandlers()); + } + + if ($config['ignore_exceptions'] ?? false) { + $handlers = [new WhatFailureGroupHandler($handlers)]; + } + + return new Monolog($this->parseChannel($config), $handlers); + } + + /** + * Create an instance of the single file log driver. + * + * @param array $config + * + * @return \Psr\Log\LoggerInterface + * + * @throws \Exception + */ + protected function createSingleDriver(array $config) + { + return new Monolog($this->parseChannel($config), [ + $this->prepareHandler(new StreamHandler( + $config['path'], + $this->level($config), + $config['bubble'] ?? true, + $config['permission'] ?? null, + $config['locking'] ?? false + ), $config), + ]); + } + + /** + * Create an instance of the daily file log driver. + * + * @param array $config + * + * @return \Psr\Log\LoggerInterface + */ + protected function createDailyDriver(array $config) + { + return new Monolog($this->parseChannel($config), [ + $this->prepareHandler(new RotatingFileHandler( + $config['path'], + $config['days'] ?? 7, + $this->level($config), + $config['bubble'] ?? true, + $config['permission'] ?? null, + $config['locking'] ?? false + ), $config), + ]); + } + + /** + * Create an instance of the Slack log driver. + * + * @param array $config + * + * @return \Psr\Log\LoggerInterface + */ + protected function createSlackDriver(array $config) + { + return new Monolog($this->parseChannel($config), [ + $this->prepareHandler(new SlackWebhookHandler( + $config['url'], + $config['channel'] ?? null, + $config['username'] ?? 'EasyWeChat', + $config['attachment'] ?? true, + $config['emoji'] ?? ':boom:', + $config['short'] ?? false, + $config['context'] ?? true, + $this->level($config), + $config['bubble'] ?? true, + $config['exclude_fields'] ?? [] + ), $config), + ]); + } + + /** + * Create an instance of the syslog log driver. + * + * @param array $config + * + * @return \Psr\Log\LoggerInterface + */ + protected function createSyslogDriver(array $config) + { + return new Monolog($this->parseChannel($config), [ + $this->prepareHandler(new SyslogHandler( + 'EasyWeChat', + $config['facility'] ?? LOG_USER, + $this->level($config) + ), $config), + ]); + } + + /** + * Create an instance of the "error log" log driver. + * + * @param array $config + * + * @return \Psr\Log\LoggerInterface + */ + protected function createErrorlogDriver(array $config) + { + return new Monolog($this->parseChannel($config), [ + $this->prepareHandler( + new ErrorLogHandler( + $config['type'] ?? ErrorLogHandler::OPERATING_SYSTEM, + $this->level($config) + ) + ), + ]); + } + + /** + * Prepare the handlers for usage by Monolog. + * + * @param array $handlers + * + * @return array + */ + protected function prepareHandlers(array $handlers) + { + foreach ($handlers as $key => $handler) { + $handlers[$key] = $this->prepareHandler($handler); + } + + return $handlers; + } + + /** + * Prepare the handler for usage by Monolog. + * + * @param \Monolog\Handler\HandlerInterface $handler + * + * @return \Monolog\Handler\HandlerInterface + */ + protected function prepareHandler(HandlerInterface $handler, array $config = []) + { + if (!isset($config['formatter'])) { + if ($handler instanceof FormattableHandlerInterface) { + $handler->setFormatter($this->formatter()); + } + } + + return $handler; + } + + /** + * Get a Monolog formatter instance. + * + * @return \Monolog\Formatter\FormatterInterface + */ + protected function formatter() + { + $formatter = new LineFormatter(null, null, true, true); + $formatter->includeStacktraces(); + + return $formatter; + } + + /** + * Extract the log channel from the given configuration. + * + * @param array $config + * + * @return string + */ + protected function parseChannel(array $config) + { + return $config['name'] ?? 'EasyWeChat'; + } + + /** + * Parse the string level into a Monolog constant. + * + * @param array $config + * + * @return int + * + * @throws InvalidArgumentException + */ + protected function level(array $config) + { + $level = $config['level'] ?? 'debug'; + + if (isset($this->levels[$level])) { + return $this->levels[$level]; + } + + throw new InvalidArgumentException('Invalid log level.'); + } + + /** + * Get the default log driver name. + * + * @return string + */ + public function getDefaultDriver() + { + return $this->app['config']['log.default']; + } + + /** + * Set the default log driver name. + * + * @param string $name + */ + public function setDefaultDriver($name) + { + $this->app['config']['log.default'] = $name; + } + + /** + * Register a custom driver creator Closure. + * + * @param string $driver + * @param \Closure $callback + * + * @return $this + */ + public function extend($driver, \Closure $callback) + { + $this->customCreators[$driver] = $callback->bindTo($this, $this); + + return $this; + } + + /** + * System is unusable. + * + * @param string $message + * @param array $context + * + * @return mixed + * + * @throws \Exception + */ + public function emergency($message, array $context = []) + { + return $this->driver()->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 mixed + * + * @throws \Exception + */ + public function alert($message, array $context = []) + { + return $this->driver()->alert($message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string $message + * @param array $context + * + * @return mixed + * + * @throws \Exception + */ + public function critical($message, array $context = []) + { + return $this->driver()->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 mixed + * + * @throws \Exception + */ + public function error($message, array $context = []) + { + return $this->driver()->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 mixed + * + * @throws \Exception + */ + public function warning($message, array $context = []) + { + return $this->driver()->warning($message, $context); + } + + /** + * Normal but significant events. + * + * @param string $message + * @param array $context + * + * @return mixed + * + * @throws \Exception + */ + public function notice($message, array $context = []) + { + return $this->driver()->notice($message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string $message + * @param array $context + * + * @return mixed + * + * @throws \Exception + */ + public function info($message, array $context = []) + { + return $this->driver()->info($message, $context); + } + + /** + * Detailed debug information. + * + * @param string $message + * @param array $context + * + * @return mixed + * + * @throws \Exception + */ + public function debug($message, array $context = []) + { + return $this->driver()->debug($message, $context); + } + + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + * + * @return mixed + * + * @throws \Exception + */ + public function log($level, $message, array $context = []) + { + return $this->driver()->log($level, $message, $context); + } + + /** + * Dynamically call the default driver instance. + * + * @param string $method + * @param array $parameters + * + * @return mixed + * + * @throws \Exception + */ + public function __call($method, $parameters) + { + return $this->driver()->$method(...$parameters); + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Article.php b/vendor/overtrue/wechat/src/Kernel/Messages/Article.php new file mode 100644 index 0000000..4792a1f --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Messages/Article.php @@ -0,0 +1,58 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Messages; + +/** + * Class Article. + */ +class Article extends Message +{ + /** + * @var string + */ + protected $type = 'mpnews'; + + /** + * Properties. + * + * @var array + */ + protected $properties = [ + 'thumb_media_id', + 'author', + 'title', + 'content', + 'digest', + 'source_url', + 'show_cover', + ]; + + /** + * Aliases of attribute. + * + * @var array + */ + protected $jsonAliases = [ + 'content_source_url' => 'source_url', + 'show_cover_pic' => 'show_cover', + ]; + + /** + * @var array + */ + protected $required = [ + 'thumb_media_id', + 'title', + 'content', + 'show_cover', + ]; +} diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Card.php b/vendor/overtrue/wechat/src/Kernel/Messages/Card.php new file mode 100644 index 0000000..0e21231 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Messages/Card.php @@ -0,0 +1,52 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Card.php. + * + * @author overtrue + * @copyright 2015 overtrue + * + * @see https://github.com/overtrue + * @see http://overtrue.me + */ + +namespace EasyWeChat\Kernel\Messages; + +/** + * Class Card. + */ +class Card extends Message +{ + /** + * Message type. + * + * @var string + */ + protected $type = 'wxcard'; + + /** + * Properties. + * + * @var array + */ + protected $properties = ['card_id']; + + /** + * Media constructor. + * + * @param string $cardId + */ + public function __construct(string $cardId) + { + parent::__construct(['card_id' => $cardId]); + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/DeviceEvent.php b/vendor/overtrue/wechat/src/Kernel/Messages/DeviceEvent.php new file mode 100644 index 0000000..ae57910 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Messages/DeviceEvent.php @@ -0,0 +1,40 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Messages; + +/** + * Class DeviceEvent. + * + * @property string $media_id + */ +class DeviceEvent extends Message +{ + /** + * Messages type. + * + * @var string + */ + protected $type = 'device_event'; + + /** + * Properties. + * + * @var array + */ + protected $properties = [ + 'device_type', + 'device_id', + 'content', + 'session_id', + 'open_id', + ]; +} diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/DeviceText.php b/vendor/overtrue/wechat/src/Kernel/Messages/DeviceText.php new file mode 100644 index 0000000..87e627a --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Messages/DeviceText.php @@ -0,0 +1,50 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Messages; + +/** + * Class DeviceText. + * + * @property string $content + */ +class DeviceText extends Message +{ + /** + * Messages type. + * + * @var string + */ + protected $type = 'device_text'; + + /** + * Properties. + * + * @var array + */ + protected $properties = [ + 'device_type', + 'device_id', + 'content', + 'session_id', + 'open_id', + ]; + + public function toXmlArray() + { + return [ + 'DeviceType' => $this->get('device_type'), + 'DeviceID' => $this->get('device_id'), + 'SessionID' => $this->get('session_id'), + 'Content' => base64_encode($this->get('content')), + ]; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/File.php b/vendor/overtrue/wechat/src/Kernel/Messages/File.php new file mode 100644 index 0000000..a14c42f --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Messages/File.php @@ -0,0 +1,25 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Messages; + +/** + * Class Image. + * + * @property string $media_id + */ +class File extends Media +{ + /** + * @var string + */ + protected $type = 'file'; +} diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Image.php b/vendor/overtrue/wechat/src/Kernel/Messages/Image.php new file mode 100644 index 0000000..982033b --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Messages/Image.php @@ -0,0 +1,27 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Messages; + +/** + * Class Image. + * + * @property string $media_id + */ +class Image extends Media +{ + /** + * Messages type. + * + * @var string + */ + protected $type = 'image'; +} diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Link.php b/vendor/overtrue/wechat/src/Kernel/Messages/Link.php new file mode 100644 index 0000000..9c126b5 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Messages/Link.php @@ -0,0 +1,36 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Messages; + +/** + * Class Link. + */ +class Link extends Message +{ + /** + * Messages type. + * + * @var string + */ + protected $type = 'link'; + + /** + * Properties. + * + * @var array + */ + protected $properties = [ + 'title', + 'description', + 'url', + ]; +} diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Location.php b/vendor/overtrue/wechat/src/Kernel/Messages/Location.php new file mode 100644 index 0000000..f10351b --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Messages/Location.php @@ -0,0 +1,38 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Messages; + +/** + * Class Location. + */ +class Location extends Message +{ + /** + * Messages type. + * + * @var string + */ + protected $type = 'location'; + + /** + * Properties. + * + * @var array + */ + protected $properties = [ + 'latitude', + 'longitude', + 'scale', + 'label', + 'precision', + ]; +} diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Media.php b/vendor/overtrue/wechat/src/Kernel/Messages/Media.php new file mode 100644 index 0000000..d8706fe --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Messages/Media.php @@ -0,0 +1,70 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Messages; + +use EasyWeChat\Kernel\Contracts\MediaInterface; +use EasyWeChat\Kernel\Support\Str; + +/** + * Class Media. + */ +class Media extends Message implements MediaInterface +{ + /** + * Properties. + * + * @var array + */ + protected $properties = ['media_id']; + + /** + * @var array + */ + protected $required = [ + 'media_id', + ]; + + /** + * MaterialClient constructor. + * + * @param string $mediaId + * @param string $type + * @param array $attributes + */ + public function __construct(string $mediaId, $type = null, array $attributes = []) + { + parent::__construct(array_merge(['media_id' => $mediaId], $attributes)); + + !empty($type) && $this->setType($type); + } + + /** + * @return string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + public function getMediaId(): string + { + $this->checkRequiredAttributes(); + + return $this->get('media_id'); + } + + public function toXmlArray() + { + return [ + Str::studly($this->getType()) => [ + 'MediaId' => $this->get('media_id'), + ], + ]; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Message.php b/vendor/overtrue/wechat/src/Kernel/Messages/Message.php new file mode 100644 index 0000000..0c22aac --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Messages/Message.php @@ -0,0 +1,208 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Messages; + +use EasyWeChat\Kernel\Contracts\MessageInterface; +use EasyWeChat\Kernel\Support\XML; +use EasyWeChat\Kernel\Traits\HasAttributes; +use Mockery\Exception\BadMethodCallException; + +/** + * Class Messages. + */ +abstract class Message implements MessageInterface +{ + use HasAttributes; + + const TEXT = 2; + const IMAGE = 4; + const VOICE = 8; + const VIDEO = 16; + const SHORT_VIDEO = 32; + const LOCATION = 64; + const LINK = 128; + const DEVICE_EVENT = 256; + const DEVICE_TEXT = 512; + const FILE = 1024; + const TEXT_CARD = 2048; + const TRANSFER = 4096; + const EVENT = 1048576; + const MINIPROGRAM_PAGE = 2097152; + const ALL = self::TEXT | self::IMAGE | self::VOICE | self::VIDEO | self::SHORT_VIDEO | self::LOCATION | self::LINK + | self::DEVICE_EVENT | self::DEVICE_TEXT | self::FILE | self::TEXT_CARD | self::TRANSFER | self::EVENT | self::MINIPROGRAM_PAGE; + + /** + * @var string + */ + protected $type; + + /** + * @var int + */ + protected $id; + + /** + * @var string + */ + protected $to; + + /** + * @var string + */ + protected $from; + + /** + * @var array + */ + protected $properties = []; + + /** + * @var array + */ + protected $jsonAliases = []; + + /** + * Message constructor. + * + * @param array $attributes + */ + public function __construct(array $attributes = []) + { + $this->setAttributes($attributes); + } + + /** + * Return type name message. + * + * @return string + */ + public function getType(): string + { + return $this->type; + } + + /** + * @param string $type + */ + public function setType(string $type) + { + $this->type = $type; + } + + /** + * Magic getter. + * + * @param string $property + * + * @return mixed + */ + public function __get($property) + { + if (property_exists($this, $property)) { + return $this->$property; + } + + return $this->getAttribute($property); + } + + /** + * Magic setter. + * + * @param string $property + * @param mixed $value + * + * @return Message + */ + public function __set($property, $value) + { + if (property_exists($this, $property)) { + $this->$property = $value; + } else { + $this->setAttribute($property, $value); + } + + return $this; + } + + /** + * @param array $appends + * + * @return array + */ + public function transformForJsonRequestWithoutType(array $appends = []) + { + return $this->transformForJsonRequest($appends, false); + } + + /** + * @param array $appends + * @param bool $withType + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + public function transformForJsonRequest(array $appends = [], $withType = true): array + { + if (!$withType) { + return $this->propertiesToArray([], $this->jsonAliases); + } + $messageType = $this->getType(); + $data = array_merge(['msgtype' => $messageType], $appends); + + $data[$messageType] = array_merge($data[$messageType] ?? [], $this->propertiesToArray([], $this->jsonAliases)); + + return $data; + } + + /** + * @param array $appends + * @param bool $returnAsArray + * + * @return string + */ + public function transformToXml(array $appends = [], bool $returnAsArray = false): string + { + $data = array_merge(['MsgType' => $this->getType()], $this->toXmlArray(), $appends); + + return $returnAsArray ? $data : XML::build($data); + } + + /** + * @param array $data + * @param array $aliases + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + protected function propertiesToArray(array $data, array $aliases = []): array + { + $this->checkRequiredAttributes(); + + foreach ($this->attributes as $property => $value) { + if (is_null($value) && !$this->isRequired($property)) { + continue; + } + $alias = array_search($property, $aliases, true); + + $data[$alias ?: $property] = $this->get($property); + } + + return $data; + } + + public function toXmlArray() + { + throw new BadMethodCallException(sprintf('Class "%s" cannot support transform to XML message.', __CLASS__)); + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/MiniProgramPage.php b/vendor/overtrue/wechat/src/Kernel/Messages/MiniProgramPage.php new file mode 100644 index 0000000..e4973b1 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Messages/MiniProgramPage.php @@ -0,0 +1,31 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Messages; + +/** + * Class MiniProgramPage. + */ +class MiniProgramPage extends Message +{ + protected $type = 'miniprogrampage'; + + protected $properties = [ + 'title', + 'appid', + 'pagepath', + 'thumb_media_id', + ]; + + protected $required = [ + 'thumb_media_id', 'appid', 'pagepath', + ]; +} diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Music.php b/vendor/overtrue/wechat/src/Kernel/Messages/Music.php new file mode 100644 index 0000000..85feb76 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Messages/Music.php @@ -0,0 +1,73 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Messages; + +/** + * Class Music. + * + * @property string $url + * @property string $hq_url + * @property string $title + * @property string $description + * @property string $thumb_media_id + * @property string $format + */ +class Music extends Message +{ + /** + * Messages type. + * + * @var string + */ + protected $type = 'music'; + + /** + * Properties. + * + * @var array + */ + protected $properties = [ + 'title', + 'description', + 'url', + 'hq_url', + 'thumb_media_id', + 'format', + ]; + + /** + * Aliases of attribute. + * + * @var array + */ + protected $jsonAliases = [ + 'musicurl' => 'url', + 'hqmusicurl' => 'hq_url', + ]; + + public function toXmlArray() + { + $music = [ + 'Music' => [ + 'Title' => $this->get('title'), + 'Description' => $this->get('description'), + 'MusicUrl' => $this->get('url'), + 'HQMusicUrl' => $this->get('hq_url'), + ], + ]; + if ($thumbMediaId = $this->get('thumb_media_id')) { + $music['Music']['ThumbMediaId'] = $thumbMediaId; + } + + return $music; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/News.php b/vendor/overtrue/wechat/src/Kernel/Messages/News.php new file mode 100644 index 0000000..2ad46e8 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Messages/News.php @@ -0,0 +1,73 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Messages; + +/** + * Class News. + * + * @author overtrue + */ +class News extends Message +{ + /** + * @var string + */ + protected $type = 'news'; + + /** + * @var array + */ + protected $properties = [ + 'items', + ]; + + /** + * News constructor. + * + * @param array $items + */ + public function __construct(array $items = []) + { + parent::__construct(compact('items')); + } + + /** + * @param array $data + * @param array $aliases + * + * @return array + */ + public function propertiesToArray(array $data, array $aliases = []): array + { + return ['articles' => array_map(function ($item) { + if ($item instanceof NewsItem) { + return $item->toJsonArray(); + } + }, $this->get('items'))]; + } + + public function toXmlArray() + { + $items = []; + + foreach ($this->get('items') as $item) { + if ($item instanceof NewsItem) { + $items[] = $item->toXmlArray(); + } + } + + return [ + 'ArticleCount' => count($items), + 'Articles' => $items, + ]; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/NewsItem.php b/vendor/overtrue/wechat/src/Kernel/Messages/NewsItem.php new file mode 100644 index 0000000..50bf336 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Messages/NewsItem.php @@ -0,0 +1,57 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Messages; + +/** + * Class NewsItem. + */ +class NewsItem extends Message +{ + /** + * Messages type. + * + * @var string + */ + protected $type = 'news'; + + /** + * Properties. + * + * @var array + */ + protected $properties = [ + 'title', + 'description', + 'url', + 'image', + ]; + + public function toJsonArray() + { + return [ + 'title' => $this->get('title'), + 'description' => $this->get('description'), + 'url' => $this->get('url'), + 'picurl' => $this->get('image'), + ]; + } + + public function toXmlArray() + { + return [ + 'Title' => $this->get('title'), + 'Description' => $this->get('description'), + 'Url' => $this->get('url'), + 'PicUrl' => $this->get('image'), + ]; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Raw.php b/vendor/overtrue/wechat/src/Kernel/Messages/Raw.php new file mode 100644 index 0000000..53f2a78 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Messages/Raw.php @@ -0,0 +1,56 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Messages; + +/** + * Class Raw. + */ +class Raw extends Message +{ + /** + * @var string + */ + protected $type = 'raw'; + + /** + * Properties. + * + * @var array + */ + protected $properties = ['content']; + + /** + * Constructor. + * + * @param string $content + */ + public function __construct(string $content) + { + parent::__construct(['content' => strval($content)]); + } + + /** + * @param array $appends + * @param bool $withType + * + * @return array + */ + public function transformForJsonRequest(array $appends = [], $withType = true): array + { + return json_decode($this->content, true) ?? []; + } + + public function __toString() + { + return $this->get('content') ?? ''; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/ShortVideo.php b/vendor/overtrue/wechat/src/Kernel/Messages/ShortVideo.php new file mode 100644 index 0000000..1dc1db9 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Messages/ShortVideo.php @@ -0,0 +1,30 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Messages; + +/** + * Class ShortVideo. + * + * @property string $title + * @property string $media_id + * @property string $description + * @property string $thumb_media_id + */ +class ShortVideo extends Video +{ + /** + * Messages type. + * + * @var string + */ + protected $type = 'shortvideo'; +} diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/TaskCard.php b/vendor/overtrue/wechat/src/Kernel/Messages/TaskCard.php new file mode 100644 index 0000000..d02c411 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Messages/TaskCard.php @@ -0,0 +1,44 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Messages; + +/** + * Class TaskCard. + * + * @property string $title + * @property string $description + * @property string $url + * @property string $task_id + * @property array $btn + */ +class TaskCard extends Message +{ + /** + * Messages type. + * + * @var string + */ + protected $type = 'taskcard'; + + /** + * Properties. + * + * @var array + */ + protected $properties = [ + 'title', + 'description', + 'url', + 'task_id', + 'btn', + ]; +} diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Text.php b/vendor/overtrue/wechat/src/Kernel/Messages/Text.php new file mode 100644 index 0000000..e355743 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Messages/Text.php @@ -0,0 +1,54 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Messages; + +/** + * Class Text. + * + * @property string $content + */ +class Text extends Message +{ + /** + * Message type. + * + * @var string + */ + protected $type = 'text'; + + /** + * Properties. + * + * @var array + */ + protected $properties = ['content']; + + /** + * Text constructor. + * + * @param string $content + */ + public function __construct(string $content) + { + parent::__construct(compact('content')); + } + + /** + * @return array + */ + public function toXmlArray() + { + return [ + 'Content' => $this->get('content'), + ]; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/TextCard.php b/vendor/overtrue/wechat/src/Kernel/Messages/TextCard.php new file mode 100644 index 0000000..edfb7c5 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Messages/TextCard.php @@ -0,0 +1,40 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Messages; + +/** + * Class Text. + * + * @property string $title + * @property string $description + * @property string $url + */ +class TextCard extends Message +{ + /** + * Messages type. + * + * @var string + */ + protected $type = 'textcard'; + + /** + * Properties. + * + * @var array + */ + protected $properties = [ + 'title', + 'description', + 'url', + ]; +} diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Transfer.php b/vendor/overtrue/wechat/src/Kernel/Messages/Transfer.php new file mode 100644 index 0000000..ff27d1a --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Messages/Transfer.php @@ -0,0 +1,56 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Messages; + +/** + * Class Transfer. + * + * @property string $to + * @property string $account + */ +class Transfer extends Message +{ + /** + * Messages type. + * + * @var string + */ + protected $type = 'transfer_customer_service'; + + /** + * Properties. + * + * @var array + */ + protected $properties = [ + 'account', + ]; + + /** + * Transfer constructor. + * + * @param string|null $account + */ + public function __construct(string $account = null) + { + parent::__construct(compact('account')); + } + + public function toXmlArray() + { + return empty($this->get('account')) ? [] : [ + 'TransInfo' => [ + 'KfAccount' => $this->get('account'), + ], + ]; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Video.php b/vendor/overtrue/wechat/src/Kernel/Messages/Video.php new file mode 100644 index 0000000..90f72a0 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Messages/Video.php @@ -0,0 +1,65 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Messages; + +/** + * Class Video. + * + * @property string $video + * @property string $title + * @property string $media_id + * @property string $description + * @property string $thumb_media_id + */ +class Video extends Media +{ + /** + * Messages type. + * + * @var string + */ + protected $type = 'video'; + + /** + * Properties. + * + * @var array + */ + protected $properties = [ + 'title', + 'description', + 'media_id', + 'thumb_media_id', + ]; + + /** + * Video constructor. + * + * @param string $mediaId + * @param array $attributes + */ + public function __construct(string $mediaId, array $attributes = []) + { + parent::__construct($mediaId, 'video', $attributes); + } + + public function toXmlArray() + { + return [ + 'Video' => [ + 'MediaId' => $this->get('media_id'), + 'Title' => $this->get('title'), + 'Description' => $this->get('description'), + ], + ]; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Voice.php b/vendor/overtrue/wechat/src/Kernel/Messages/Voice.php new file mode 100644 index 0000000..ff723ea --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Messages/Voice.php @@ -0,0 +1,37 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Messages; + +/** + * Class Voice. + * + * @property string $media_id + */ +class Voice extends Media +{ + /** + * Messages type. + * + * @var string + */ + protected $type = 'voice'; + + /** + * Properties. + * + * @var array + */ + protected $properties = [ + 'media_id', + 'recognition', + ]; +} diff --git a/vendor/overtrue/wechat/src/Kernel/Providers/ConfigServiceProvider.php b/vendor/overtrue/wechat/src/Kernel/Providers/ConfigServiceProvider.php new file mode 100644 index 0000000..b33f4a1 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Providers/ConfigServiceProvider.php @@ -0,0 +1,39 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Providers; + +use EasyWeChat\Kernel\Config; +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ConfigServiceProvider. + * + * @author overtrue + */ +class ConfigServiceProvider 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 A container instance + */ + public function register(Container $pimple) + { + $pimple['config'] = function ($app) { + return new Config($app->getConfig()); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Providers/EventDispatcherServiceProvider.php b/vendor/overtrue/wechat/src/Kernel/Providers/EventDispatcherServiceProvider.php new file mode 100644 index 0000000..9095cef --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Providers/EventDispatcherServiceProvider.php @@ -0,0 +1,47 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Providers; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Symfony\Component\EventDispatcher\EventDispatcher; + +/** + * Class EventDispatcherServiceProvider. + * + * @author mingyoung + */ +class EventDispatcherServiceProvider 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 A container instance + */ + public function register(Container $pimple) + { + $pimple['events'] = function ($app) { + $dispatcher = new EventDispatcher(); + + foreach ($app->config->get('events.listen', []) as $event => $listeners) { + foreach ($listeners as $listener) { + $dispatcher->addListener($event, $listener); + } + } + + return $dispatcher; + }; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Providers/ExtensionServiceProvider.php b/vendor/overtrue/wechat/src/Kernel/Providers/ExtensionServiceProvider.php new file mode 100644 index 0000000..23af832 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Providers/ExtensionServiceProvider.php @@ -0,0 +1,39 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Providers; + +use EasyWeChatComposer\Extension; +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ExtensionServiceProvider. + * + * @author overtrue + */ +class ExtensionServiceProvider 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 A container instance + */ + public function register(Container $pimple) + { + $pimple['extension'] = function ($app) { + return new Extension($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Providers/HttpClientServiceProvider.php b/vendor/overtrue/wechat/src/Kernel/Providers/HttpClientServiceProvider.php new file mode 100644 index 0000000..aca7fa9 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Providers/HttpClientServiceProvider.php @@ -0,0 +1,39 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Providers; + +use GuzzleHttp\Client; +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class HttpClientServiceProvider. + * + * @author overtrue + */ +class HttpClientServiceProvider 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 A container instance + */ + public function register(Container $pimple) + { + $pimple['http_client'] = function ($app) { + return new Client($app['config']->get('http', [])); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Providers/LogServiceProvider.php b/vendor/overtrue/wechat/src/Kernel/Providers/LogServiceProvider.php new file mode 100644 index 0000000..83498ac --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Providers/LogServiceProvider.php @@ -0,0 +1,79 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Providers; + +use EasyWeChat\Kernel\Log\LogManager; +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class LoggingServiceProvider. + * + * @author overtrue + */ +class LogServiceProvider 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 A container instance + */ + public function register(Container $pimple) + { + $pimple['logger'] = $pimple['log'] = function ($app) { + $config = $this->formatLogConfig($app); + + if (!empty($config)) { + $app->rebind('config', $app['config']->merge($config)); + } + + return new LogManager($app); + }; + } + + public function formatLogConfig($app) + { + if (!empty($app['config']->get('log.channels'))) { + return $app['config']->get('log'); + } + + if (empty($app['config']->get('log'))) { + return [ + 'log' => [ + 'default' => 'errorlog', + 'channels' => [ + 'errorlog' => [ + 'driver' => 'errorlog', + 'level' => 'debug', + ], + ], + ], + ]; + } + + return [ + 'log' => [ + 'default' => 'single', + 'channels' => [ + 'single' => [ + 'driver' => 'single', + 'path' => $app['config']->get('log.file') ?: \sys_get_temp_dir().'/logs/easywechat.log', + 'level' => $app['config']->get('log.level', 'debug'), + ], + ], + ], + ]; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Providers/RequestServiceProvider.php b/vendor/overtrue/wechat/src/Kernel/Providers/RequestServiceProvider.php new file mode 100644 index 0000000..a0b111d --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Providers/RequestServiceProvider.php @@ -0,0 +1,39 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Providers; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Symfony\Component\HttpFoundation\Request; + +/** + * Class RequestServiceProvider. + * + * @author overtrue + */ +class RequestServiceProvider 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 A container instance + */ + public function register(Container $pimple) + { + $pimple['request'] = function () { + return Request::createFromGlobals(); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/ServerGuard.php b/vendor/overtrue/wechat/src/Kernel/ServerGuard.php new file mode 100644 index 0000000..c135e12 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/ServerGuard.php @@ -0,0 +1,375 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel; + +use EasyWeChat\Kernel\Contracts\MessageInterface; +use EasyWeChat\Kernel\Exceptions\BadRequestException; +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; +use EasyWeChat\Kernel\Messages\Message; +use EasyWeChat\Kernel\Messages\News; +use EasyWeChat\Kernel\Messages\NewsItem; +use EasyWeChat\Kernel\Messages\Raw as RawMessage; +use EasyWeChat\Kernel\Messages\Text; +use EasyWeChat\Kernel\Support\XML; +use EasyWeChat\Kernel\Traits\Observable; +use EasyWeChat\Kernel\Traits\ResponseCastable; +use Symfony\Component\HttpFoundation\Response; + +/** + * Class ServerGuard. + * + * 1. url 里的 signature 只是将 token+nonce+timestamp 得到的签名,只是用于验证当前请求的,在公众号环境下一直有 + * 2. 企业号消息发送时是没有的,因为固定为完全模式,所以 url 里不会存在 signature, 只有 msg_signature 用于解密消息的 + * + * @author overtrue + */ +class ServerGuard +{ + use Observable; + use ResponseCastable; + + /** + * @var bool + */ + protected $alwaysValidate = false; + + /** + * Empty string. + */ + const SUCCESS_EMPTY_RESPONSE = 'success'; + + /** + * @var array + */ + const MESSAGE_TYPE_MAPPING = [ + 'text' => Message::TEXT, + 'image' => Message::IMAGE, + 'voice' => Message::VOICE, + 'video' => Message::VIDEO, + 'shortvideo' => Message::SHORT_VIDEO, + 'location' => Message::LOCATION, + 'link' => Message::LINK, + 'device_event' => Message::DEVICE_EVENT, + 'device_text' => Message::DEVICE_TEXT, + 'event' => Message::EVENT, + 'file' => Message::FILE, + 'miniprogrampage' => Message::MINIPROGRAM_PAGE, + ]; + + /** + * @var \EasyWeChat\Kernel\ServiceContainer + */ + protected $app; + + /** + * Constructor. + * + * @codeCoverageIgnore + * + * @param \EasyWeChat\Kernel\ServiceContainer $app + */ + public function __construct(ServiceContainer $app) + { + $this->app = $app; + + foreach ($this->app->extension->observers() as $observer) { + call_user_func_array([$this, 'push'], $observer); + } + } + + /** + * Handle and return response. + * + * @return Response + * + * @throws BadRequestException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function serve(): Response + { + $this->app['logger']->debug('Request received:', [ + 'method' => $this->app['request']->getMethod(), + 'uri' => $this->app['request']->getUri(), + 'content-type' => $this->app['request']->getContentType(), + 'content' => $this->app['request']->getContent(), + ]); + + $response = $this->validate()->resolve(); + + $this->app['logger']->debug('Server response created:', ['content' => $response->getContent()]); + + return $response; + } + + /** + * @return $this + * + * @throws \EasyWeChat\Kernel\Exceptions\BadRequestException + */ + public function validate() + { + if (!$this->alwaysValidate && !$this->isSafeMode()) { + return $this; + } + + if ($this->app['request']->get('signature') !== $this->signature([ + $this->getToken(), + $this->app['request']->get('timestamp'), + $this->app['request']->get('nonce'), + ])) { + throw new BadRequestException('Invalid request signature.', 400); + } + + return $this; + } + + /** + * Force validate request. + * + * @return $this + */ + public function forceValidate() + { + $this->alwaysValidate = true; + + return $this; + } + + /** + * Get request message. + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|string + * + * @throws BadRequestException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function getMessage() + { + $message = $this->parseMessage($this->app['request']->getContent(false)); + + if (!is_array($message) || empty($message)) { + throw new BadRequestException('No message received.'); + } + + if ($this->isSafeMode() && !empty($message['Encrypt'])) { + $message = $this->decryptMessage($message); + + // Handle JSON format. + $dataSet = json_decode($message, true); + + if ($dataSet && (JSON_ERROR_NONE === json_last_error())) { + return $dataSet; + } + + $message = XML::parse($message); + } + + return $this->detectAndCastResponseToType($message, $this->app->config->get('response_type')); + } + + /** + * Resolve server request and return the response. + * + * @return \Symfony\Component\HttpFoundation\Response + * + * @throws \EasyWeChat\Kernel\Exceptions\BadRequestException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + protected function resolve(): Response + { + $result = $this->handleRequest(); + + if ($this->shouldReturnRawResponse()) { + $response = new Response($result['response']); + } else { + $response = new Response( + $this->buildResponse($result['to'], $result['from'], $result['response']), + 200, + ['Content-Type' => 'application/xml'] + ); + } + + $this->app->events->dispatch(new Events\ServerGuardResponseCreated($response)); + + return $response; + } + + /** + * @return string|null + */ + protected function getToken() + { + return $this->app['config']['token']; + } + + /** + * @param string $to + * @param string $from + * @param \EasyWeChat\Kernel\Contracts\MessageInterface|string|int $message + * + * @return string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + public function buildResponse(string $to, string $from, $message) + { + if (empty($message) || self::SUCCESS_EMPTY_RESPONSE === $message) { + return self::SUCCESS_EMPTY_RESPONSE; + } + + if ($message instanceof RawMessage) { + return $message->get('content', self::SUCCESS_EMPTY_RESPONSE); + } + + if (is_string($message) || is_numeric($message)) { + $message = new Text((string) $message); + } + + if (is_array($message) && reset($message) instanceof NewsItem) { + $message = new News($message); + } + + if (!($message instanceof Message)) { + throw new InvalidArgumentException(sprintf('Invalid Messages type "%s".', gettype($message))); + } + + return $this->buildReply($to, $from, $message); + } + + /** + * Handle request. + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\BadRequestException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + protected function handleRequest(): array + { + $castedMessage = $this->getMessage(); + + $messageArray = $this->detectAndCastResponseToType($castedMessage, 'array'); + + $response = $this->dispatch(self::MESSAGE_TYPE_MAPPING[$messageArray['MsgType'] ?? $messageArray['msg_type'] ?? 'text'], $castedMessage); + + return [ + 'to' => $messageArray['FromUserName'] ?? '', + 'from' => $messageArray['ToUserName'] ?? '', + 'response' => $response, + ]; + } + + /** + * Build reply XML. + * + * @param string $to + * @param string $from + * @param \EasyWeChat\Kernel\Contracts\MessageInterface $message + * + * @return string + */ + protected function buildReply(string $to, string $from, MessageInterface $message): string + { + $prepends = [ + 'ToUserName' => $to, + 'FromUserName' => $from, + 'CreateTime' => time(), + 'MsgType' => $message->getType(), + ]; + + $response = $message->transformToXml($prepends); + + if ($this->isSafeMode()) { + $this->app['logger']->debug('Messages safe mode is enabled.'); + $response = $this->app['encryptor']->encrypt($response); + } + + return $response; + } + + /** + * @param array $params + * + * @return string + */ + protected function signature(array $params) + { + sort($params, SORT_STRING); + + return sha1(implode($params)); + } + + /** + * Parse message array from raw php input. + * + * @param string $content + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\BadRequestException + */ + protected function parseMessage($content) + { + try { + if (0 === stripos($content, '<')) { + $content = XML::parse($content); + } else { + // Handle JSON format. + $dataSet = json_decode($content, true); + if ($dataSet && (JSON_ERROR_NONE === json_last_error())) { + $content = $dataSet; + } + } + + return (array) $content; + } catch (\Exception $e) { + throw new BadRequestException(sprintf('Invalid message content:(%s) %s', $e->getCode(), $e->getMessage()), $e->getCode()); + } + } + + /** + * Check the request message safe mode. + * + * @return bool + */ + protected function isSafeMode(): bool + { + return $this->app['request']->get('signature') && 'aes' === $this->app['request']->get('encrypt_type'); + } + + /** + * @return bool + */ + protected function shouldReturnRawResponse(): bool + { + return false; + } + + /** + * @param array $message + * + * @return mixed + */ + protected function decryptMessage(array $message) + { + return $message = $this->app['encryptor']->decrypt( + $message['Encrypt'], + $this->app['request']->get('msg_signature'), + $this->app['request']->get('nonce'), + $this->app['request']->get('timestamp') + ); + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/ServiceContainer.php b/vendor/overtrue/wechat/src/Kernel/ServiceContainer.php new file mode 100644 index 0000000..af1f35b --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/ServiceContainer.php @@ -0,0 +1,167 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel; + +use EasyWeChat\Kernel\Providers\ConfigServiceProvider; +use EasyWeChat\Kernel\Providers\EventDispatcherServiceProvider; +use EasyWeChat\Kernel\Providers\ExtensionServiceProvider; +use EasyWeChat\Kernel\Providers\HttpClientServiceProvider; +use EasyWeChat\Kernel\Providers\LogServiceProvider; +use EasyWeChat\Kernel\Providers\RequestServiceProvider; +use EasyWeChatComposer\Traits\WithAggregator; +use Pimple\Container; + +/** + * Class ServiceContainer. + * + * @author overtrue + * + * @property \EasyWeChat\Kernel\Config $config + * @property \Symfony\Component\HttpFoundation\Request $request + * @property \GuzzleHttp\Client $http_client + * @property \Monolog\Logger $logger + * @property \Symfony\Component\EventDispatcher\EventDispatcher $events + */ +class ServiceContainer extends Container +{ + use WithAggregator; + + /** + * @var string + */ + protected $id; + + /** + * @var array + */ + protected $providers = []; + + /** + * @var array + */ + protected $defaultConfig = []; + + /** + * @var array + */ + protected $userConfig = []; + + /** + * Constructor. + * + * @param array $config + * @param array $prepends + * @param string|null $id + */ + public function __construct(array $config = [], array $prepends = [], string $id = null) + { + $this->registerProviders($this->getProviders()); + + parent::__construct($prepends); + + $this->userConfig = $config; + + $this->id = $id; + + $this->aggregate(); + + $this->events->dispatch(new Events\ApplicationInitialized($this)); + } + + /** + * @return string + */ + public function getId() + { + return $this->id ?? $this->id = md5(json_encode($this->userConfig)); + } + + /** + * @return array + */ + public function getConfig() + { + $base = [ + // http://docs.guzzlephp.org/en/stable/request-options.html + 'http' => [ + 'timeout' => 30.0, + 'base_uri' => 'https://api.weixin.qq.com/', + ], + ]; + + return array_replace_recursive($base, $this->defaultConfig, $this->userConfig); + } + + /** + * Return all providers. + * + * @return array + */ + public function getProviders() + { + return array_merge([ + ConfigServiceProvider::class, + LogServiceProvider::class, + RequestServiceProvider::class, + HttpClientServiceProvider::class, + ExtensionServiceProvider::class, + EventDispatcherServiceProvider::class, + ], $this->providers); + } + + /** + * @param string $id + * @param mixed $value + */ + public function rebind($id, $value) + { + $this->offsetUnset($id); + $this->offsetSet($id, $value); + } + + /** + * Magic get access. + * + * @param string $id + * + * @return mixed + */ + public function __get($id) + { + if ($this->shouldDelegate($id)) { + return $this->delegateTo($id); + } + + return $this->offsetGet($id); + } + + /** + * Magic set access. + * + * @param string $id + * @param mixed $value + */ + public function __set($id, $value) + { + $this->offsetSet($id, $value); + } + + /** + * @param array $providers + */ + public function registerProviders(array $providers) + { + foreach ($providers as $provider) { + parent::register(new $provider()); + } + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Support/AES.php b/vendor/overtrue/wechat/src/Kernel/Support/AES.php new file mode 100644 index 0000000..73b1b24 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Support/AES.php @@ -0,0 +1,85 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Support; + +/** + * Class AES. + * + * @author overtrue + */ +class AES +{ + /** + * @param string $text + * @param string $key + * @param string $iv + * @param int $option + * + * @return string + */ + public static function encrypt(string $text, string $key, string $iv, int $option = OPENSSL_RAW_DATA): string + { + self::validateKey($key); + self::validateIv($iv); + + return openssl_encrypt($text, self::getMode($key), $key, $option, $iv); + } + + /** + * @param string $cipherText + * @param string $key + * @param string $iv + * @param int $option + * @param string|null $method + * + * @return string + */ + public static function decrypt(string $cipherText, string $key, string $iv, int $option = OPENSSL_RAW_DATA, $method = null): string + { + self::validateKey($key); + self::validateIv($iv); + + return openssl_decrypt($cipherText, $method ?: self::getMode($key), $key, $option, $iv); + } + + /** + * @param string $key + * + * @return string + */ + public static function getMode($key) + { + return 'aes-'.(8 * strlen($key)).'-cbc'; + } + + /** + * @param string $key + */ + public static function validateKey(string $key) + { + if (!in_array(strlen($key), [16, 24, 32], true)) { + throw new \InvalidArgumentException(sprintf('Key length must be 16, 24, or 32 bytes; got key len (%s).', strlen($key))); + } + } + + /** + * @param string $iv + * + * @throws \InvalidArgumentException + */ + public static function validateIv(string $iv) + { + if (!empty($iv) && 16 !== strlen($iv)) { + throw new \InvalidArgumentException('IV length must be 16 bytes.'); + } + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Support/Arr.php b/vendor/overtrue/wechat/src/Kernel/Support/Arr.php new file mode 100644 index 0000000..ac34038 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Support/Arr.php @@ -0,0 +1,466 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Support; + +/** + * Array helper from Illuminate\Support\Arr. + */ +class Arr +{ + /** + * 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 $array, $key, $value) + { + if (is_null(static::get($array, $key))) { + static::set($array, $key, $value); + } + + return $array; + } + + /** + * 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 $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 $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 items. + * + * @param array $array + * @param array|string $keys + * + * @return array + */ + public static function except(array $array, $keys) + { + static::forget($array, $keys); + + return $array; + } + + /** + * Determine if the given key exists in the provided array. + * + * @param array $array + * @param string|int $key + * + * @return bool + */ + public static function exists(array $array, $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 $array, callable $callback = null, $default = null) + { + if (is_null($callback)) { + if (empty($array)) { + return $default; + } + + foreach ($array as $item) { + return $item; + } + } + + foreach ($array as $key => $value) { + if (call_user_func($callback, $value, $key)) { + return $value; + } + } + + return $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 $array, callable $callback = null, $default = null) + { + if (is_null($callback)) { + return empty($array) ? $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 $array, $depth = INF) + { + return array_reduce($array, function ($result, $item) use ($depth) { + $item = $item instanceof Collection ? $item->all() : $item; + + if (!is_array($item)) { + return array_merge($result, [$item]); + } elseif (1 === $depth) { + return array_merge($result, array_values($item)); + } + + return array_merge($result, static::flatten($item, $depth - 1)); + }, []); + } + + /** + * Remove one or many array items from a given array using "dot" notation. + * + * @param array $array + * @param array|string $keys + */ + public static function forget(array &$array, $keys) + { + $original = &$array; + + $keys = (array) $keys; + + if (0 === count($keys)) { + 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 array $array + * @param string $key + * @param mixed $default + * + * @return mixed + */ + public static function get(array $array, $key, $default = null) + { + if (is_null($key)) { + return $array; + } + + if (static::exists($array, $key)) { + return $array[$key]; + } + + foreach (explode('.', $key) as $segment) { + if (static::exists($array, $segment)) { + $array = $array[$segment]; + } else { + return $default; + } + } + + return $array; + } + + /** + * Check if an item or items exist in an array using "dot" notation. + * + * @param array $array + * @param string|array $keys + * + * @return bool + */ + public static function has(array $array, $keys) + { + if (is_null($keys)) { + return false; + } + + $keys = (array) $keys; + + if (empty($array)) { + return false; + } + + if ($keys === []) { + return false; + } + + foreach ($keys as $key) { + $subKeyArray = $array; + + if (static::exists($array, $key)) { + continue; + } + + foreach (explode('.', $key) as $segment) { + if (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 $array, $keys) + { + return array_intersect_key($array, array_flip((array) $keys)); + } + + /** + * 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 $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 &$array, $key, $default = null) + { + $value = static::get($array, $key, $default); + + static::forget($array, $key); + + return $value; + } + + /** + * Get a 1 value from an array. + * + * @param array $array + * @param int|null $amount + * + * @return mixed + * + * @throws \InvalidArgumentException + */ + public static function random(array $array, int $amount = null) + { + if (is_null($amount)) { + return $array[array_rand($array)]; + } + + $keys = array_rand($array, $amount); + + $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 &$array, string $key, $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; + } + + /** + * Filter the array using the given callback. + * + * @param array $array + * @param callable $callback + * + * @return array + */ + public static function where(array $array, callable $callback) + { + return array_filter($array, $callback, ARRAY_FILTER_USE_BOTH); + } + + /** + * If the given value is not an array, wrap it in one. + * + * @param mixed $value + * + * @return array + */ + public static function wrap($value) + { + return !is_array($value) ? [$value] : $value; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Support/ArrayAccessible.php b/vendor/overtrue/wechat/src/Kernel/Support/ArrayAccessible.php new file mode 100644 index 0000000..72e0b04 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Support/ArrayAccessible.php @@ -0,0 +1,66 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Support; + +use ArrayAccess; +use ArrayIterator; +use EasyWeChat\Kernel\Contracts\Arrayable; +use IteratorAggregate; + +/** + * Class ArrayAccessible. + * + * @author overtrue + */ +class ArrayAccessible implements ArrayAccess, IteratorAggregate, Arrayable +{ + private $array; + + public function __construct(array $array = []) + { + $this->array = $array; + } + + public function offsetExists($offset) + { + return array_key_exists($offset, $this->array); + } + + public function offsetGet($offset) + { + return $this->array[$offset]; + } + + public function offsetSet($offset, $value) + { + if (null === $offset) { + $this->array[] = $value; + } else { + $this->array[$offset] = $value; + } + } + + public function offsetUnset($offset) + { + unset($this->array[$offset]); + } + + public function getIterator() + { + return new ArrayIterator($this->array); + } + + public function toArray() + { + return $this->array; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Support/Collection.php b/vendor/overtrue/wechat/src/Kernel/Support/Collection.php new file mode 100644 index 0000000..adf24ea --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Support/Collection.php @@ -0,0 +1,421 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Support; + +use ArrayAccess; +use ArrayIterator; +use Countable; +use EasyWeChat\Kernel\Contracts\Arrayable; +use IteratorAggregate; +use JsonSerializable; +use Serializable; + +/** + * Class Collection. + */ +class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable, Serializable, Arrayable +{ + /** + * The collection data. + * + * @var array + */ + protected $items = []; + + /** + * set data. + * + * @param array $items + */ + public function __construct(array $items = []) + { + foreach ($items as $key => $value) { + $this->set($key, $value); + } + } + + /** + * Return all items. + * + * @return array + */ + public function all() + { + return $this->items; + } + + /** + * Return specific items. + * + * @param array $keys + * + * @return \EasyWeChat\Kernel\Support\Collection + */ + public function only(array $keys) + { + $return = []; + + foreach ($keys as $key) { + $value = $this->get($key); + + if (!is_null($value)) { + $return[$key] = $value; + } + } + + return new static($return); + } + + /** + * Get all items except for those with the specified keys. + * + * @param mixed $keys + * + * @return static + */ + public function except($keys) + { + $keys = is_array($keys) ? $keys : func_get_args(); + + return new static(Arr::except($this->items, $keys)); + } + + /** + * Merge data. + * + * @param Collection|array $items + * + * @return \EasyWeChat\Kernel\Support\Collection + */ + public function merge($items) + { + $clone = new static($this->all()); + + foreach ($items as $key => $value) { + $clone->set($key, $value); + } + + return $clone; + } + + /** + * To determine Whether the specified element exists. + * + * @param string $key + * + * @return bool + */ + public function has($key) + { + return !is_null(Arr::get($this->items, $key)); + } + + /** + * Retrieve the first item. + * + * @return mixed + */ + public function first() + { + return reset($this->items); + } + + /** + * Retrieve the last item. + * + * @return bool + */ + public function last() + { + $end = end($this->items); + + reset($this->items); + + return $end; + } + + /** + * add the item value. + * + * @param string $key + * @param mixed $value + */ + public function add($key, $value) + { + Arr::set($this->items, $key, $value); + } + + /** + * Set the item value. + * + * @param string $key + * @param mixed $value + */ + public function set($key, $value) + { + Arr::set($this->items, $key, $value); + } + + /** + * Retrieve item from Collection. + * + * @param string $key + * @param mixed $default + * + * @return mixed + */ + public function get($key, $default = null) + { + return Arr::get($this->items, $key, $default); + } + + /** + * Remove item form Collection. + * + * @param string $key + */ + public function forget($key) + { + Arr::forget($this->items, $key); + } + + /** + * Build to array. + * + * @return array + */ + public function toArray() + { + return $this->all(); + } + + /** + * Build to json. + * + * @param int $option + * + * @return string + */ + public function toJson($option = JSON_UNESCAPED_UNICODE) + { + return json_encode($this->all(), $option); + } + + /** + * To string. + * + * @return string + */ + public function __toString() + { + return $this->toJson(); + } + + /** + * (PHP 5 >= 5.4.0)
    + * Specify data which should be serialized to JSON. + * + * @see http://php.net/manual/en/jsonserializable.jsonserialize.php + * + * @return mixed data which can be serialized by json_encode, + * which is a value of any type other than a resource + */ + public function jsonSerialize() + { + return $this->items; + } + + /** + * (PHP 5 >= 5.1.0)
    + * String representation of object. + * + * @see http://php.net/manual/en/serializable.serialize.php + * + * @return string the string representation of the object or null + */ + public function serialize() + { + return serialize($this->items); + } + + /** + * (PHP 5 >= 5.0.0)
    + * Retrieve an external iterator. + * + * @see http://php.net/manual/en/iteratoraggregate.getiterator.php + * + * @return \ArrayIterator An instance of an object implementing Iterator or + * Traversable + */ + public function getIterator() + { + return new ArrayIterator($this->items); + } + + /** + * (PHP 5 >= 5.1.0)
    + * Count elements of an object. + * + * @see http://php.net/manual/en/countable.count.php + * + * @return int the custom count as an integer. + *

    + *

    + * The return value is cast to an integer + */ + public function count() + { + return count($this->items); + } + + /** + * (PHP 5 >= 5.1.0)
    + * Constructs the object. + * + * @see http://php.net/manual/en/serializable.unserialize.php + * + * @param string $serialized

    + * The string representation of the object. + *

    + * + * @return mixed|void + */ + public function unserialize($serialized) + { + return $this->items = unserialize($serialized); + } + + /** + * Get a data by key. + * + * @param string $key + * + * @return mixed + */ + public function __get($key) + { + return $this->get($key); + } + + /** + * Assigns a value to the specified data. + * + * @param string $key + * @param mixed $value + */ + public function __set($key, $value) + { + $this->set($key, $value); + } + + /** + * Whether or not an data exists by key. + * + * @param string $key + * + * @return bool + */ + public function __isset($key) + { + return $this->has($key); + } + + /** + * Unset an data by key. + * + * @param string $key + */ + public function __unset($key) + { + $this->forget($key); + } + + /** + * var_export. + * + * @return array + */ + public function __set_state() + { + return $this->all(); + } + + /** + * (PHP 5 >= 5.0.0)
    + * Whether a offset exists. + * + * @see http://php.net/manual/en/arrayaccess.offsetexists.php + * + * @param mixed $offset

    + * An offset to check for. + *

    + * + * @return bool true on success or false on failure. + * The return value will be casted to boolean if non-boolean was returned + */ + public function offsetExists($offset) + { + return $this->has($offset); + } + + /** + * (PHP 5 >= 5.0.0)
    + * Offset to unset. + * + * @see http://php.net/manual/en/arrayaccess.offsetunset.php + * + * @param mixed $offset

    + * The offset to unset. + *

    + */ + public function offsetUnset($offset) + { + if ($this->offsetExists($offset)) { + $this->forget($offset); + } + } + + /** + * (PHP 5 >= 5.0.0)
    + * Offset to retrieve. + * + * @see http://php.net/manual/en/arrayaccess.offsetget.php + * + * @param mixed $offset

    + * The offset to retrieve. + *

    + * + * @return mixed Can return all value types + */ + public function offsetGet($offset) + { + return $this->offsetExists($offset) ? $this->get($offset) : null; + } + + /** + * (PHP 5 >= 5.0.0)
    + * Offset to set. + * + * @see http://php.net/manual/en/arrayaccess.offsetset.php + * + * @param mixed $offset

    + * The offset to assign the value to. + *

    + * @param mixed $value

    + * The value to set. + *

    + */ + public function offsetSet($offset, $value) + { + $this->set($offset, $value); + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Support/File.php b/vendor/overtrue/wechat/src/Kernel/Support/File.php new file mode 100644 index 0000000..b51b41d --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Support/File.php @@ -0,0 +1,135 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Support; + +use finfo; + +/** + * Class File. + */ +class File +{ + /** + * MIME mapping. + * + * @var array + */ + protected static $extensionMap = [ + 'audio/wav' => '.wav', + 'audio/x-ms-wma' => '.wma', + 'video/x-ms-wmv' => '.wmv', + 'video/mp4' => '.mp4', + 'audio/mpeg' => '.mp3', + 'audio/amr' => '.amr', + 'application/vnd.rn-realmedia' => '.rm', + 'audio/mid' => '.mid', + 'image/bmp' => '.bmp', + 'image/gif' => '.gif', + 'image/png' => '.png', + 'image/tiff' => '.tiff', + 'image/jpeg' => '.jpg', + 'application/pdf' => '.pdf', + + // 列举更多的文件 mime, 企业号是支持的,公众平台这边之后万一也更新了呢 + 'application/msword' => '.doc', + + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => '.docx', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.template' => '.dotx', + 'application/vnd.ms-word.document.macroEnabled.12' => '.docm', + 'application/vnd.ms-word.template.macroEnabled.12' => '.dotm', + + 'application/vnd.ms-excel' => '.xls', + + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => '.xlsx', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.template' => '.xltx', + 'application/vnd.ms-excel.sheet.macroEnabled.12' => '.xlsm', + 'application/vnd.ms-excel.template.macroEnabled.12' => '.xltm', + 'application/vnd.ms-excel.addin.macroEnabled.12' => '.xlam', + 'application/vnd.ms-excel.sheet.binary.macroEnabled.12' => '.xlsb', + + 'application/vnd.ms-powerpoint' => '.ppt', + + 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => '.pptx', + 'application/vnd.openxmlformats-officedocument.presentationml.template' => '.potx', + 'application/vnd.openxmlformats-officedocument.presentationml.slideshow' => '.ppsx', + 'application/vnd.ms-powerpoint.addin.macroEnabled.12' => '.ppam', + ]; + + /** + * File header signatures. + * + * @var array + */ + protected static $signatures = [ + 'ffd8ff' => '.jpg', + '424d' => '.bmp', + '47494638' => '.gif', + '2f55736572732f6f7665' => '.png', + '89504e47' => '.png', + '494433' => '.mp3', + 'fffb' => '.mp3', + 'fff3' => '.mp3', + '3026b2758e66cf11' => '.wma', + '52494646' => '.wav', + '57415645' => '.wav', + '41564920' => '.avi', + '000001ba' => '.mpg', + '000001b3' => '.mpg', + '2321414d52' => '.amr', + '25504446' => '.pdf', + ]; + + /** + * Return steam extension. + * + * @param string $stream + * + * @return string|false + */ + public static function getStreamExt($stream) + { + $ext = self::getExtBySignature($stream); + + try { + if (empty($ext) && is_readable($stream)) { + $stream = file_get_contents($stream); + } + } catch (\Exception $e) { + } + + $fileInfo = new finfo(FILEINFO_MIME); + + $mime = strstr($fileInfo->buffer($stream), ';', true); + + return isset(self::$extensionMap[$mime]) ? self::$extensionMap[$mime] : $ext; + } + + /** + * Get file extension by file header signature. + * + * @param string $stream + * + * @return string + */ + public static function getExtBySignature($stream) + { + $prefix = strval(bin2hex(mb_strcut($stream, 0, 10))); + + foreach (self::$signatures as $signature => $extension) { + if (0 === strpos($prefix, strval($signature))) { + return $extension; + } + } + + return ''; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Support/Helpers.php b/vendor/overtrue/wechat/src/Kernel/Support/Helpers.php new file mode 100644 index 0000000..08d4092 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Support/Helpers.php @@ -0,0 +1,131 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Support; + +/* + * helpers. + * + * @author overtrue + */ + +/** + * Generate a signature. + * + * @param array $attributes + * @param string $key + * @param string $encryptMethod + * + * @return string + */ +function generate_sign(array $attributes, $key, $encryptMethod = 'md5') +{ + ksort($attributes); + + $attributes['key'] = $key; + + return strtoupper(call_user_func_array($encryptMethod, [urldecode(http_build_query($attributes))])); +} + +/** + * @param string $signType + * @param string $secretKey + * + * @return \Closure|string + */ +function get_encrypt_method(string $signType, string $secretKey = '') +{ + if ('HMAC-SHA256' === $signType) { + return function ($str) use ($secretKey) { + return hash_hmac('sha256', $str, $secretKey); + }; + } + + return 'md5'; +} + +/** + * Get client ip. + * + * @return string + */ +function get_client_ip() +{ + if (!empty($_SERVER['REMOTE_ADDR'])) { + $ip = $_SERVER['REMOTE_ADDR']; + } else { + // for php-cli(phpunit etc.) + $ip = defined('PHPUNIT_RUNNING') ? '127.0.0.1' : gethostbyname(gethostname()); + } + + return filter_var($ip, FILTER_VALIDATE_IP) ?: '127.0.0.1'; +} + +/** + * Get current server ip. + * + * @return string + */ +function get_server_ip() +{ + if (!empty($_SERVER['SERVER_ADDR'])) { + $ip = $_SERVER['SERVER_ADDR']; + } elseif (!empty($_SERVER['SERVER_NAME'])) { + $ip = gethostbyname($_SERVER['SERVER_NAME']); + } else { + // for php-cli(phpunit etc.) + $ip = defined('PHPUNIT_RUNNING') ? '127.0.0.1' : gethostbyname(gethostname()); + } + + return filter_var($ip, FILTER_VALIDATE_IP) ?: '127.0.0.1'; +} + +/** + * Return current url. + * + * @return string + */ +function current_url() +{ + $protocol = 'http://'; + + if ((!empty($_SERVER['HTTPS']) && 'off' !== $_SERVER['HTTPS']) || ($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? 'http') === 'https') { + $protocol = 'https://'; + } + + return $protocol.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']; +} + +/** + * Return random string. + * + * @param string $length + * + * @return string + */ +function str_random($length) +{ + return Str::random($length); +} + +/** + * @param string $content + * @param string $publicKey + * + * @return string + */ +function rsa_public_encrypt($content, $publicKey) +{ + $encrypted = ''; + openssl_public_encrypt($content, $encrypted, openssl_pkey_get_public($publicKey), OPENSSL_PKCS1_OAEP_PADDING); + + return base64_encode($encrypted); +} diff --git a/vendor/overtrue/wechat/src/Kernel/Support/Str.php b/vendor/overtrue/wechat/src/Kernel/Support/Str.php new file mode 100644 index 0000000..3a51968 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Support/Str.php @@ -0,0 +1,193 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Support; + +use EasyWeChat\Kernel\Exceptions\RuntimeException; + +/** + * Class Str. + */ +class Str +{ + /** + * The cache of snake-cased words. + * + * @var array + */ + protected static $snakeCache = []; + + /** + * The cache of camel-cased words. + * + * @var array + */ + protected static $camelCache = []; + + /** + * The cache of studly-cased words. + * + * @var array + */ + protected static $studlyCache = []; + + /** + * Convert a value to camel case. + * + * @param string $value + * + * @return string + */ + public static function camel($value) + { + if (isset(static::$camelCache[$value])) { + return static::$camelCache[$value]; + } + + return static::$camelCache[$value] = lcfirst(static::studly($value)); + } + + /** + * Generate a more truly "random" alpha-numeric string. + * + * @param int $length + * + * @return string + * + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + public static function random($length = 16) + { + $string = ''; + + while (($len = strlen($string)) < $length) { + $size = $length - $len; + + $bytes = static::randomBytes($size); + + $string .= substr(str_replace(['/', '+', '='], '', base64_encode($bytes)), 0, $size); + } + + return $string; + } + + /** + * Generate a more truly "random" bytes. + * + * @param int $length + * + * @return string + * + * @throws RuntimeException + * + * @codeCoverageIgnore + * + * @throws \Exception + */ + public static function randomBytes($length = 16) + { + if (function_exists('random_bytes')) { + $bytes = random_bytes($length); + } elseif (function_exists('openssl_random_pseudo_bytes')) { + $bytes = openssl_random_pseudo_bytes($length, $strong); + if (false === $bytes || false === $strong) { + throw new RuntimeException('Unable to generate random string.'); + } + } else { + throw new RuntimeException('OpenSSL extension is required for PHP 5 users.'); + } + + return $bytes; + } + + /** + * Generate a "random" alpha-numeric string. + * + * Should not be considered sufficient for cryptography, etc. + * + * @param int $length + * + * @return string + */ + public static function quickRandom($length = 16) + { + $pool = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + + return substr(str_shuffle(str_repeat($pool, $length)), 0, $length); + } + + /** + * Convert the given string to upper-case. + * + * @param string $value + * + * @return string + */ + public static function upper($value) + { + return mb_strtoupper($value); + } + + /** + * Convert the given string to title case. + * + * @param string $value + * + * @return string + */ + public static function title($value) + { + return mb_convert_case($value, MB_CASE_TITLE, 'UTF-8'); + } + + /** + * Convert a string to snake case. + * + * @param string $value + * @param string $delimiter + * + * @return string + */ + public static function snake($value, $delimiter = '_') + { + $key = $value.$delimiter; + + if (isset(static::$snakeCache[$key])) { + return static::$snakeCache[$key]; + } + + if (!ctype_lower($value)) { + $value = strtolower(preg_replace('/(.)(?=[A-Z])/', '$1'.$delimiter, $value)); + } + + return static::$snakeCache[$key] = trim($value, '_'); + } + + /** + * Convert a value to studly caps case. + * + * @param string $value + * + * @return string + */ + public static function studly($value) + { + $key = $value; + + if (isset(static::$studlyCache[$key])) { + return static::$studlyCache[$key]; + } + + $value = ucwords(str_replace(['-', '_'], ' ', $value)); + + return static::$studlyCache[$key] = str_replace(' ', '', $value); + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Support/XML.php b/vendor/overtrue/wechat/src/Kernel/Support/XML.php new file mode 100644 index 0000000..93026e6 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Support/XML.php @@ -0,0 +1,167 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Support; + +use SimpleXMLElement; + +/** + * Class XML. + */ +class XML +{ + /** + * XML to array. + * + * @param string $xml XML string + * + * @return array + */ + public static function parse($xml) + { + $backup = libxml_disable_entity_loader(true); + + $result = self::normalize(simplexml_load_string(self::sanitize($xml), 'SimpleXMLElement', LIBXML_COMPACT | LIBXML_NOCDATA | LIBXML_NOBLANKS)); + + libxml_disable_entity_loader($backup); + + return $result; + } + + /** + * XML encode. + * + * @param mixed $data + * @param string $root + * @param string $item + * @param string $attr + * @param string $id + * + * @return string + */ + public static function build( + $data, + $root = 'xml', + $item = 'item', + $attr = '', + $id = 'id' + ) { + if (is_array($attr)) { + $_attr = []; + + foreach ($attr as $key => $value) { + $_attr[] = "{$key}=\"{$value}\""; + } + + $attr = implode(' ', $_attr); + } + + $attr = trim($attr); + $attr = empty($attr) ? '' : " {$attr}"; + $xml = "<{$root}{$attr}>"; + $xml .= self::data2Xml($data, $item, $id); + $xml .= ""; + + return $xml; + } + + /** + * Build CDATA. + * + * @param string $string + * + * @return string + */ + public static function cdata($string) + { + return sprintf('', $string); + } + + /** + * Object to array. + * + * + * @param SimpleXMLElement $obj + * + * @return array + */ + protected static function normalize($obj) + { + $result = null; + + if (is_object($obj)) { + $obj = (array) $obj; + } + + if (is_array($obj)) { + foreach ($obj as $key => $value) { + $res = self::normalize($value); + if (('@attributes' === $key) && ($key)) { + $result = $res; // @codeCoverageIgnore + } else { + $result[$key] = $res; + } + } + } else { + $result = $obj; + } + + return $result; + } + + /** + * Array to XML. + * + * @param array $data + * @param string $item + * @param string $id + * + * @return string + */ + protected static function data2Xml($data, $item = 'item', $id = 'id') + { + $xml = $attr = ''; + + foreach ($data as $key => $val) { + if (is_numeric($key)) { + $id && $attr = " {$id}=\"{$key}\""; + $key = $item; + } + + $xml .= "<{$key}{$attr}>"; + + if ((is_array($val) || is_object($val))) { + $xml .= self::data2Xml((array) $val, $item, $id); + } else { + $xml .= is_numeric($val) ? $val : self::cdata($val); + } + + $xml .= ""; + } + + return $xml; + } + + /** + * Delete invalid characters in XML. + * + * @see https://www.w3.org/TR/2008/REC-xml-20081126/#charsets - XML charset range + * @see http://php.net/manual/en/regexp.reference.escape.php - escape in UTF-8 mode + * + * @param string $xml + * + * @return string + */ + public static function sanitize($xml) + { + return preg_replace('/[^\x{9}\x{A}\x{D}\x{20}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]+/u', '', $xml); + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Traits/HasAttributes.php b/vendor/overtrue/wechat/src/Kernel/Traits/HasAttributes.php new file mode 100644 index 0000000..238d9e2 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Traits/HasAttributes.php @@ -0,0 +1,251 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Traits; + +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; +use EasyWeChat\Kernel\Support\Arr; +use EasyWeChat\Kernel\Support\Str; + +/** + * Trait Attributes. + */ +trait HasAttributes +{ + /** + * @var array + */ + protected $attributes = []; + + /** + * @var bool + */ + protected $snakeable = true; + + /** + * Set Attributes. + * + * @param array $attributes + * + * @return $this + */ + public function setAttributes(array $attributes = []) + { + $this->attributes = $attributes; + + return $this; + } + + /** + * Set attribute. + * + * @param string $attribute + * @param string $value + * + * @return $this + */ + public function setAttribute($attribute, $value) + { + Arr::set($this->attributes, $attribute, $value); + + return $this; + } + + /** + * Get attribute. + * + * @param string $attribute + * @param mixed $default + * + * @return mixed + */ + public function getAttribute($attribute, $default = null) + { + return Arr::get($this->attributes, $attribute, $default); + } + + /** + * @param string $attribute + * + * @return bool + */ + public function isRequired($attribute) + { + return in_array($attribute, $this->getRequired(), true); + } + + /** + * @return array|mixed + */ + public function getRequired() + { + return property_exists($this, 'required') ? $this->required : []; + } + + /** + * Set attribute. + * + * @param string $attribute + * @param mixed $value + * + * @return $this + */ + public function with($attribute, $value) + { + $this->snakeable && $attribute = Str::snake($attribute); + + $this->setAttribute($attribute, $value); + + return $this; + } + + /** + * Override parent set() method. + * + * @param string $attribute + * @param mixed $value + * + * @return $this + */ + public function set($attribute, $value) + { + $this->setAttribute($attribute, $value); + + return $this; + } + + /** + * Override parent get() method. + * + * @param string $attribute + * @param mixed $default + * + * @return mixed + */ + public function get($attribute, $default = null) + { + return $this->getAttribute($attribute, $default); + } + + /** + * @param string $key + * + * @return bool + */ + public function has(string $key) + { + return Arr::has($this->attributes, $key); + } + + /** + * @param array $attributes + * + * @return $this + */ + public function merge(array $attributes) + { + $this->attributes = array_merge($this->attributes, $attributes); + + return $this; + } + + /** + * @param array|string $keys + * + * @return array + */ + public function only($keys) + { + return Arr::only($this->attributes, $keys); + } + + /** + * Return all items. + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + public function all() + { + $this->checkRequiredAttributes(); + + return $this->attributes; + } + + /** + * Magic call. + * + * @param string $method + * @param array $args + * + * @return $this + */ + public function __call($method, $args) + { + if (0 === stripos($method, 'with')) { + return $this->with(substr($method, 4), array_shift($args)); + } + + throw new \BadMethodCallException(sprintf('Method "%s" does not exists.', $method)); + } + + /** + * Magic get. + * + * @param string $property + * + * @return mixed + */ + public function __get($property) + { + return $this->get($property); + } + + /** + * Magic set. + * + * @param string $property + * @param mixed $value + * + * @return $this + */ + public function __set($property, $value) + { + return $this->with($property, $value); + } + + /** + * Whether or not an data exists by key. + * + * @param string $key + * + * @return bool + */ + public function __isset($key) + { + return isset($this->attributes[$key]); + } + + /** + * Check required attributes. + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + protected function checkRequiredAttributes() + { + foreach ($this->getRequired() as $attribute) { + if (is_null($this->get($attribute))) { + throw new InvalidArgumentException(sprintf('"%s" cannot be empty.', $attribute)); + } + } + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Traits/HasHttpRequests.php b/vendor/overtrue/wechat/src/Kernel/Traits/HasHttpRequests.php new file mode 100644 index 0000000..9bf0a02 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Traits/HasHttpRequests.php @@ -0,0 +1,231 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Traits; + +use GuzzleHttp\Client; +use GuzzleHttp\ClientInterface; +use GuzzleHttp\HandlerStack; +use Psr\Http\Message\ResponseInterface; + +/** + * Trait HasHttpRequests. + * + * @author overtrue + */ +trait HasHttpRequests +{ + use ResponseCastable; + + /** + * @var \GuzzleHttp\ClientInterface + */ + protected $httpClient; + + /** + * @var array + */ + protected $middlewares = []; + + /** + * @var \GuzzleHttp\HandlerStack + */ + protected $handlerStack; + + /** + * @var array + */ + protected static $defaults = [ + 'curl' => [ + CURLOPT_IPRESOLVE => CURL_IPRESOLVE_V4, + ], + ]; + + /** + * Set guzzle default settings. + * + * @param array $defaults + */ + public static function setDefaultOptions($defaults = []) + { + self::$defaults = $defaults; + } + + /** + * Return current guzzle default settings. + * + * @return array + */ + public static function getDefaultOptions(): array + { + return self::$defaults; + } + + /** + * Set GuzzleHttp\Client. + * + * @param \GuzzleHttp\ClientInterface $httpClient + * + * @return $this + */ + public function setHttpClient(ClientInterface $httpClient) + { + $this->httpClient = $httpClient; + + return $this; + } + + /** + * Return GuzzleHttp\ClientInterface instance. + * + * @return ClientInterface + */ + public function getHttpClient(): ClientInterface + { + if (!($this->httpClient instanceof ClientInterface)) { + if (property_exists($this, 'app') && $this->app['http_client']) { + $this->httpClient = $this->app['http_client']; + } else { + $this->httpClient = new Client(['handler' => HandlerStack::create($this->getGuzzleHandler())]); + } + } + + return $this->httpClient; + } + + /** + * Add a middleware. + * + * @param callable $middleware + * @param string $name + * + * @return $this + */ + public function pushMiddleware(callable $middleware, string $name = null) + { + if (!is_null($name)) { + $this->middlewares[$name] = $middleware; + } else { + array_push($this->middlewares, $middleware); + } + + return $this; + } + + /** + * Return all middlewares. + * + * @return array + */ + public function getMiddlewares(): array + { + return $this->middlewares; + } + + /** + * Make a request. + * + * @param string $url + * @param string $method + * @param array $options + * + * @return \Psr\Http\Message\ResponseInterface + * + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function request($url, $method = 'GET', $options = []): ResponseInterface + { + $method = strtoupper($method); + + $options = array_merge(self::$defaults, $options, ['handler' => $this->getHandlerStack()]); + + $options = $this->fixJsonIssue($options); + + if (property_exists($this, 'baseUri') && !is_null($this->baseUri)) { + $options['base_uri'] = $this->baseUri; + } + + $response = $this->getHttpClient()->request($method, $url, $options); + $response->getBody()->rewind(); + + return $response; + } + + /** + * @param \GuzzleHttp\HandlerStack $handlerStack + * + * @return $this + */ + public function setHandlerStack(HandlerStack $handlerStack) + { + $this->handlerStack = $handlerStack; + + return $this; + } + + /** + * Build a handler stack. + * + * @return \GuzzleHttp\HandlerStack + */ + public function getHandlerStack(): HandlerStack + { + if ($this->handlerStack) { + return $this->handlerStack; + } + + $this->handlerStack = HandlerStack::create($this->getGuzzleHandler()); + + foreach ($this->middlewares as $name => $middleware) { + $this->handlerStack->push($middleware, $name); + } + + return $this->handlerStack; + } + + /** + * @param array $options + * + * @return array + */ + protected function fixJsonIssue(array $options): array + { + if (isset($options['json']) && is_array($options['json'])) { + $options['headers'] = array_merge($options['headers'] ?? [], ['Content-Type' => 'application/json']); + + if (empty($options['json'])) { + $options['body'] = \GuzzleHttp\json_encode($options['json'], JSON_FORCE_OBJECT); + } else { + $options['body'] = \GuzzleHttp\json_encode($options['json'], JSON_UNESCAPED_UNICODE); + } + + unset($options['json']); + } + + return $options; + } + + /** + * Get guzzle handler. + * + * @return callable + */ + protected function getGuzzleHandler() + { + if (property_exists($this, 'app') && isset($this->app['guzzle_handler'])) { + return is_string($handler = $this->app->raw('guzzle_handler')) + ? new $handler() + : $handler; + } + + return \GuzzleHttp\choose_handler(); + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Traits/InteractsWithCache.php b/vendor/overtrue/wechat/src/Kernel/Traits/InteractsWithCache.php new file mode 100644 index 0000000..fc5182c --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Traits/InteractsWithCache.php @@ -0,0 +1,105 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Traits; + +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; +use EasyWeChat\Kernel\ServiceContainer; +use Psr\Cache\CacheItemPoolInterface; +use Psr\SimpleCache\CacheInterface as SimpleCacheInterface; +use Symfony\Component\Cache\Adapter\FilesystemAdapter; +use Symfony\Component\Cache\Psr16Cache; +use Symfony\Component\Cache\Simple\FilesystemCache; + +/** + * Trait InteractsWithCache. + * + * @author overtrue + */ +trait InteractsWithCache +{ + /** + * @var \Psr\SimpleCache\CacheInterface + */ + protected $cache; + + /** + * Get cache instance. + * + * @return \Psr\SimpleCache\CacheInterface + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + public function getCache() + { + if ($this->cache) { + return $this->cache; + } + + if (property_exists($this, 'app') && $this->app instanceof ServiceContainer && isset($this->app['cache'])) { + $this->setCache($this->app['cache']); + + // Fix PHPStan error + assert($this->cache instanceof \Psr\SimpleCache\CacheInterface); + + return $this->cache; + } + + return $this->cache = $this->createDefaultCache(); + } + + /** + * Set cache instance. + * + * @param \Psr\SimpleCache\CacheInterface|\Psr\Cache\CacheItemPoolInterface $cache + * + * @return $this + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + public function setCache($cache) + { + if (empty(\array_intersect([SimpleCacheInterface::class, CacheItemPoolInterface::class], \class_implements($cache)))) { + throw new InvalidArgumentException(\sprintf('The cache instance must implements %s or %s interface.', SimpleCacheInterface::class, CacheItemPoolInterface::class)); + } + + if ($cache instanceof CacheItemPoolInterface) { + if (!$this->isSymfony43()) { + throw new InvalidArgumentException(sprintf('The cache instance must implements %s', SimpleCacheInterface::class)); + } + $cache = new Psr16Cache($cache); + } + + $this->cache = $cache; + + return $this; + } + + /** + * @return \Psr\SimpleCache\CacheInterface + */ + protected function createDefaultCache() + { + if ($this->isSymfony43()) { + return new Psr16Cache(new FilesystemAdapter('easywechat', 1500)); + } + + return new FilesystemCache(); + } + + /** + * @return bool + */ + protected function isSymfony43(): bool + { + return \class_exists('Symfony\Component\Cache\Psr16Cache'); + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Traits/Observable.php b/vendor/overtrue/wechat/src/Kernel/Traits/Observable.php new file mode 100644 index 0000000..7db03f5 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Traits/Observable.php @@ -0,0 +1,273 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Traits; + +use EasyWeChat\Kernel\Clauses\Clause; +use EasyWeChat\Kernel\Contracts\EventHandlerInterface; +use EasyWeChat\Kernel\Decorators\FinallyResult; +use EasyWeChat\Kernel\Decorators\TerminateResult; +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; +use EasyWeChat\Kernel\ServiceContainer; + +/** + * Trait Observable. + * + * @author overtrue + */ +trait Observable +{ + /** + * @var array + */ + protected $handlers = []; + + /** + * @var array + */ + protected $clauses = []; + + /** + * @param \Closure|EventHandlerInterface|callable|string $handler + * @param \Closure|EventHandlerInterface|callable|string $condition + * + * @return \EasyWeChat\Kernel\Clauses\Clause + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \ReflectionException + */ + public function push($handler, $condition = '*') + { + list($handler, $condition) = $this->resolveHandlerAndCondition($handler, $condition); + + if (!isset($this->handlers[$condition])) { + $this->handlers[$condition] = []; + } + + array_push($this->handlers[$condition], $handler); + + return $this->newClause($handler); + } + + /** + * @param \Closure|EventHandlerInterface|string $handler + * @param \Closure|EventHandlerInterface|string $condition + * + * @return \EasyWeChat\Kernel\Clauses\Clause + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \ReflectionException + */ + public function unshift($handler, $condition = '*') + { + list($handler, $condition) = $this->resolveHandlerAndCondition($handler, $condition); + + if (!isset($this->handlers[$condition])) { + $this->handlers[$condition] = []; + } + + array_unshift($this->handlers[$condition], $handler); + + return $this->newClause($handler); + } + + /** + * @param string $condition + * @param \Closure|EventHandlerInterface|string $handler + * + * @return \EasyWeChat\Kernel\Clauses\Clause + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \ReflectionException + */ + public function observe($condition, $handler) + { + return $this->push($handler, $condition); + } + + /** + * @param string $condition + * @param \Closure|EventHandlerInterface|string $handler + * + * @return \EasyWeChat\Kernel\Clauses\Clause + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \ReflectionException + */ + public function on($condition, $handler) + { + return $this->push($handler, $condition); + } + + /** + * @param string|int $event + * @param mixed ...$payload + * + * @return mixed|null + */ + public function dispatch($event, $payload) + { + return $this->notify($event, $payload); + } + + /** + * @param string|int $event + * @param mixed ...$payload + * + * @return mixed|null + */ + public function notify($event, $payload) + { + $result = null; + + foreach ($this->handlers as $condition => $handlers) { + if ('*' === $condition || ($condition & $event) === $event) { + foreach ($handlers as $handler) { + if ($clause = $this->clauses[$this->getHandlerHash($handler)] ?? null) { + if ($clause->intercepted($payload)) { + continue; + } + } + + $response = $this->callHandler($handler, $payload); + + switch (true) { + case $response instanceof TerminateResult: + return $response->content; + case true === $response: + continue 2; + case false === $response: + break 2; + case !empty($response) && !($result instanceof FinallyResult): + $result = $response; + } + } + } + } + + return $result instanceof FinallyResult ? $result->content : $result; + } + + /** + * @return array + */ + public function getHandlers() + { + return $this->handlers; + } + + /** + * @param mixed $handler + * + * @return \EasyWeChat\Kernel\Clauses\Clause + */ + protected function newClause($handler): Clause + { + return $this->clauses[$this->getHandlerHash($handler)] = new Clause(); + } + + /** + * @param mixed $handler + * + * @return string + */ + protected function getHandlerHash($handler) + { + if (is_string($handler)) { + return $handler; + } + + if (is_array($handler)) { + return is_string($handler[0]) + ? $handler[0].'::'.$handler[1] + : get_class($handler[0]).$handler[1]; + } + + return spl_object_hash($handler); + } + + /** + * @param callable $handler + * @param mixed $payload + * + * @return mixed + */ + protected function callHandler(callable $handler, $payload) + { + try { + return call_user_func_array($handler, [$payload]); + } catch (\Exception $e) { + if (property_exists($this, 'app') && $this->app instanceof ServiceContainer) { + $this->app['logger']->error($e->getCode().': '.$e->getMessage(), [ + 'code' => $e->getCode(), + 'message' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + ]); + } + } + } + + /** + * @param mixed $handler + * + * @return \Closure + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \ReflectionException + */ + protected function makeClosure($handler) + { + if (is_callable($handler)) { + return $handler; + } + + if (is_string($handler) && '*' !== $handler) { + if (!class_exists($handler)) { + throw new InvalidArgumentException(sprintf('Class "%s" not exists.', $handler)); + } + + if (!in_array(EventHandlerInterface::class, (new \ReflectionClass($handler))->getInterfaceNames(), true)) { + throw new InvalidArgumentException(sprintf('Class "%s" not an instance of "%s".', $handler, EventHandlerInterface::class)); + } + + return function ($payload) use ($handler) { + return (new $handler($this->app ?? null))->handle($payload); + }; + } + + if ($handler instanceof EventHandlerInterface) { + return function () use ($handler) { + return $handler->handle(...func_get_args()); + }; + } + + throw new InvalidArgumentException('No valid handler is found in arguments.'); + } + + /** + * @param mixed $handler + * @param mixed $condition + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \ReflectionException + */ + protected function resolveHandlerAndCondition($handler, $condition): array + { + if (is_int($handler) || (is_string($handler) && !class_exists($handler))) { + list($handler, $condition) = [$condition, $handler]; + } + + return [$this->makeClosure($handler), $condition]; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Traits/ResponseCastable.php b/vendor/overtrue/wechat/src/Kernel/Traits/ResponseCastable.php new file mode 100644 index 0000000..5492bd4 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Traits/ResponseCastable.php @@ -0,0 +1,93 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Traits; + +use EasyWeChat\Kernel\Contracts\Arrayable; +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; +use EasyWeChat\Kernel\Exceptions\InvalidConfigException; +use EasyWeChat\Kernel\Http\Response; +use EasyWeChat\Kernel\Support\Collection; +use Psr\Http\Message\ResponseInterface; + +/** + * Trait ResponseCastable. + * + * @author overtrue + */ +trait ResponseCastable +{ + /** + * @param \Psr\Http\Message\ResponseInterface $response + * @param string|null $type + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + protected function castResponseToType(ResponseInterface $response, $type = null) + { + $response = Response::buildFromPsrResponse($response); + $response->getBody()->rewind(); + + switch ($type ?? 'array') { + case 'collection': + return $response->toCollection(); + case 'array': + return $response->toArray(); + case 'object': + return $response->toObject(); + case 'raw': + return $response; + default: + if (!is_subclass_of($type, Arrayable::class)) { + throw new InvalidConfigException(sprintf('Config key "response_type" classname must be an instanceof %s', Arrayable::class)); + } + + return new $type($response); + } + } + + /** + * @param mixed $response + * @param string|null $type + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + protected function detectAndCastResponseToType($response, $type = null) + { + switch (true) { + case $response instanceof ResponseInterface: + $response = Response::buildFromPsrResponse($response); + + break; + case $response instanceof Arrayable: + $response = new Response(200, [], json_encode($response->toArray())); + + break; + case ($response instanceof Collection) || is_array($response) || is_object($response): + $response = new Response(200, [], json_encode($response)); + + break; + case is_scalar($response): + $response = new Response(200, [], (string) $response); + + break; + default: + throw new InvalidArgumentException(sprintf('Unsupported response type "%s"', gettype($response))); + } + + return $this->castResponseToType($response, $type); + } +} diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Application.php b/vendor/overtrue/wechat/src/MicroMerchant/Application.php new file mode 100644 index 0000000..9bf8196 --- /dev/null +++ b/vendor/overtrue/wechat/src/MicroMerchant/Application.php @@ -0,0 +1,171 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MicroMerchant; + +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; +use EasyWeChat\Kernel\ServiceContainer; +use EasyWeChat\Kernel\Support; +use EasyWeChat\MicroMerchant\Kernel\Exceptions\InvalidSignException; + +/** + * Class Application. + * + * @author liuml + * + * @property \EasyWeChat\MicroMerchant\Certficates\Client $certficates + * @property \EasyWeChat\MicroMerchant\Material\Client $material + * @property \EasyWeChat\MicroMerchant\MerchantConfig\Client $merchantConfig + * @property \EasyWeChat\MicroMerchant\Withdraw\Client $withdraw + * @property \EasyWeChat\MicroMerchant\Media\Client $media + * + * @method mixed submitApplication(array $params) + * @method mixed getStatus(string $applymentId, string $businessCode = '') + * @method mixed upgrade(array $params) + * @method mixed getUpgradeStatus(string $subMchId = '') + */ +class Application extends ServiceContainer +{ + /** + * @var array + */ + protected $providers = [ + // Base services + Base\ServiceProvider::class, + Certficates\ServiceProvider::class, + MerchantConfig\ServiceProvider::class, + Material\ServiceProvider::class, + Withdraw\ServiceProvider::class, + Media\ServiceProvider::class, + ]; + + /** + * @var array + */ + protected $defaultConfig = [ + 'http' => [ + 'base_uri' => 'https://api.mch.weixin.qq.com/', + ], + 'log' => [ + 'default' => 'dev', // 默认使用的 channel,生产环境可以改为下面的 prod + 'channels' => [ + // 测试环境 + 'dev' => [ + 'driver' => 'single', + 'path' => '/tmp/easywechat.log', + 'level' => 'debug', + ], + // 生产环境 + 'prod' => [ + 'driver' => 'daily', + 'path' => '/tmp/easywechat.log', + 'level' => 'info', + ], + ], + ], + ]; + + /** + * @return string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + public function getKey() + { + $key = $this['config']->key; + + if (empty($key)) { + throw new InvalidArgumentException('config key connot be empty.'); + } + + if (32 !== strlen($key)) { + throw new InvalidArgumentException(sprintf("'%s' should be 32 chars length.", $key)); + } + + return $key; + } + + /** + * set sub-mch-id and appid. + * + * @param string $subMchId Identification Number of Small and Micro Businessmen Reported by Service Providers + * @param string $appId Public Account ID of Service Provider + * + * @return $this + */ + public function setSubMchId(string $subMchId, string $appId = '') + { + $this['config']->set('sub_mch_id', $subMchId); + $this['config']->set('appid', $appId); + + return $this; + } + + /** + * setCertificate. + * + * @param string $certificate + * @param string $serialNo + * + * @return $this + */ + public function setCertificate(string $certificate, string $serialNo) + { + $this['config']->set('certificate', $certificate); + $this['config']->set('serial_no', $serialNo); + + return $this; + } + + /** + * Returning true indicates that the verification is successful, + * returning false indicates that the signature field does not exist or is empty, + * and if the signature verification is wrong, the InvalidSignException will be thrown directly. + * + * @param array $data + * + * @return bool + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\InvalidSignException + */ + public function verifySignature(array $data) + { + if (!isset($data['sign']) || empty($data['sign'])) { + return false; + } + + $sign = $data['sign']; + unset($data['sign']); + + $signType = strlen($sign) > 32 ? 'HMAC-SHA256' : 'MD5'; + $secretKey = $this->getKey(); + + $encryptMethod = Support\get_encrypt_method($signType, $secretKey); + + if (Support\generate_sign($data, $secretKey, $encryptMethod) === $sign) { + return true; + } + + throw new InvalidSignException('return value signature verification error'); + } + + /** + * @param string $name + * @param array $arguments + * + * @return mixed + */ + public function __call($name, $arguments) + { + return call_user_func_array([$this['base'], $name], $arguments); + } +} diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Base/Client.php b/vendor/overtrue/wechat/src/MicroMerchant/Base/Client.php new file mode 100644 index 0000000..ba92756 --- /dev/null +++ b/vendor/overtrue/wechat/src/MicroMerchant/Base/Client.php @@ -0,0 +1,126 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MicroMerchant\Base; + +use EasyWeChat\MicroMerchant\Kernel\BaseClient; + +/** + * Class Client. + * + * @author liuml + * @DateTime 2019-05-30 14:19 + */ +class Client extends BaseClient +{ + /** + * apply to settle in to become a small micro merchant. + * + * @param array $params + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\EncryptException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function submitApplication(array $params) + { + $params = $this->processParams(array_merge($params, [ + 'version' => '3.0', + 'cert_sn' => '', + 'sign_type' => 'HMAC-SHA256', + 'nonce_str' => uniqid('micro'), + ])); + + return $this->safeRequest('applyment/micro/submit', $params); + } + + /** + * query application status. + * + * @param string $applymentId + * @param string $businessCode + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getStatus(string $applymentId, string $businessCode = '') + { + if (!empty($applymentId)) { + $params = [ + 'applyment_id' => $applymentId, + ]; + } else { + $params = [ + 'business_code' => $businessCode, + ]; + } + + $params = array_merge($params, [ + 'version' => '1.0', + 'sign_type' => 'HMAC-SHA256', + 'nonce_str' => uniqid('micro'), + ]); + + return $this->safeRequest('applyment/micro/getstate', $params); + } + + /** + * merchant upgrade api. + * + * @param array $params + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\EncryptException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function upgrade(array $params) + { + $params['sub_mch_id'] = $params['sub_mch_id'] ?? $this->app['config']->sub_mch_id; + $params = $this->processParams(array_merge($params, [ + 'version' => '1.0', + 'cert_sn' => '', + 'sign_type' => 'HMAC-SHA256', + 'nonce_str' => uniqid('micro'), + ])); + + return $this->safeRequest('applyment/micro/submitupgrade', $params); + } + + /** + * get upgrade status. + * + * @param string $subMchId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getUpgradeStatus(string $subMchId = '') + { + return $this->safeRequest('applyment/micro/getupgradestate', [ + 'version' => '1.0', + 'sign_type' => 'HMAC-SHA256', + 'sub_mch_id' => $subMchId ?: $this->app['config']->sub_mch_id, + 'nonce_str' => uniqid('micro'), + ]); + } +} diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Base/ServiceProvider.php b/vendor/overtrue/wechat/src/MicroMerchant/Base/ServiceProvider.php new file mode 100644 index 0000000..db2f056 --- /dev/null +++ b/vendor/overtrue/wechat/src/MicroMerchant/Base/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MicroMerchant\Base; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['base'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Certficates/Client.php b/vendor/overtrue/wechat/src/MicroMerchant/Certficates/Client.php new file mode 100644 index 0000000..9731703 --- /dev/null +++ b/vendor/overtrue/wechat/src/MicroMerchant/Certficates/Client.php @@ -0,0 +1,93 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MicroMerchant\Certficates; + +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; +use EasyWeChat\MicroMerchant\Kernel\BaseClient; +use EasyWeChat\MicroMerchant\Kernel\Exceptions\InvalidExtensionException; + +/** + * Class Client. + * + * @author liuml + * @DateTime 2019-05-30 14:19 + */ +class Client extends BaseClient +{ + /** + * get certficates. + * + * @param bool $returnRaw + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\InvalidExtensionException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function get(bool $returnRaw = false) + { + $params = [ + 'sign_type' => 'HMAC-SHA256', + 'nonce_str' => uniqid('micro'), + ]; + + if (true === $returnRaw) { + return $this->requestRaw('risk/getcertficates', $params); + } + /** @var array $response */ + $response = $this->requestArray('risk/getcertficates', $params); + + if ('SUCCESS' !== $response['return_code']) { + throw new InvalidArgumentException(sprintf('Failed to get certificate. return_code_msg: "%s" .', $response['return_code'].'('.$response['return_msg'].')')); + } + if ('SUCCESS' !== $response['result_code']) { + throw new InvalidArgumentException(sprintf('Failed to get certificate. result_err_code_des: "%s" .', $response['result_code'].'('.$response['err_code'].'['.$response['err_code_des'].'])')); + } + $certificates = \GuzzleHttp\json_decode($response['certificates'], true)['data'][0]; + $ciphertext = $this->decrypt($certificates['encrypt_certificate']); + unset($certificates['encrypt_certificate']); + $certificates['certificates'] = $ciphertext; + + return $certificates; + } + + /** + * decrypt ciphertext. + * + * @param array $encryptCertificate + * + * @return string + * + * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\InvalidExtensionException + */ + public function decrypt(array $encryptCertificate) + { + if (false === extension_loaded('sodium')) { + throw new InvalidExtensionException('sodium extension is not installed,Reference link https://php.net/manual/zh/book.sodium.php'); + } + + if (false === sodium_crypto_aead_aes256gcm_is_available()) { + throw new InvalidExtensionException('aes256gcm is not currently supported'); + } + + // sodium_crypto_aead_aes256gcm_decrypt function needs to open libsodium extension. + // https://www.php.net/manual/zh/function.sodium-crypto-aead-aes256gcm-decrypt.php + return sodium_crypto_aead_aes256gcm_decrypt( + base64_decode($encryptCertificate['ciphertext'], true), + $encryptCertificate['associated_data'], + $encryptCertificate['nonce'], + $this->app['config']->apiv3_key + ); + } +} diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Certficates/ServiceProvider.php b/vendor/overtrue/wechat/src/MicroMerchant/Certficates/ServiceProvider.php new file mode 100644 index 0000000..2e88b7e --- /dev/null +++ b/vendor/overtrue/wechat/src/MicroMerchant/Certficates/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MicroMerchant\Certficates; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['certficates'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Kernel/BaseClient.php b/vendor/overtrue/wechat/src/MicroMerchant/Kernel/BaseClient.php new file mode 100644 index 0000000..d91b467 --- /dev/null +++ b/vendor/overtrue/wechat/src/MicroMerchant/Kernel/BaseClient.php @@ -0,0 +1,256 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MicroMerchant\Kernel; + +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; +use EasyWeChat\Kernel\Support; +use EasyWeChat\MicroMerchant\Application; +use EasyWeChat\MicroMerchant\Kernel\Exceptions\EncryptException; +use EasyWeChat\Payment\Kernel\BaseClient as PaymentBaseClient; + +/** + * Class BaseClient. + * + * @author liuml + * @DateTime 2019-07-10 12:06 + */ +class BaseClient extends PaymentBaseClient +{ + /** + * @var string + */ + protected $certificates; + + /** + * BaseClient constructor. + * + * @param \EasyWeChat\MicroMerchant\Application $app + */ + public function __construct(Application $app) + { + $this->app = $app; + + $this->setHttpClient($this->app['http_client']); + } + + /** + * Extra request params. + * + * @return array + */ + protected function prepends() + { + return []; + } + + /** + * httpUpload. + * + * @param string $url + * @param array $files + * @param array $form + * @param array $query + * @param bool $returnResponse + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\InvalidSignException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function httpUpload(string $url, array $files = [], array $form = [], array $query = [], $returnResponse = false) + { + $multipart = []; + + foreach ($files as $name => $path) { + $multipart[] = [ + 'name' => $name, + 'contents' => fopen($path, 'r'), + ]; + } + + $base = [ + 'mch_id' => $this->app['config']['mch_id'], + ]; + + $form = array_merge($base, $form); + + $form['sign'] = $this->getSign($form); + + foreach ($form as $name => $contents) { + $multipart[] = compact('name', 'contents'); + } + + $options = [ + 'query' => $query, + 'multipart' => $multipart, + 'connect_timeout' => 30, + 'timeout' => 30, + 'read_timeout' => 30, + 'cert' => $this->app['config']->get('cert_path'), + 'ssl_key' => $this->app['config']->get('key_path'), + ]; + + $this->pushMiddleware($this->logMiddleware(), 'log'); + + $response = $this->performRequest($url, 'POST', $options); + + $result = $returnResponse ? $response : $this->castResponseToType($response, $this->app->config->get('response_type')); + // auto verify signature + if ($returnResponse || 'array' !== ($this->app->config->get('response_type') ?? 'array')) { + $this->app->verifySignature($this->castResponseToType($response, 'array')); + } else { + $this->app->verifySignature($result); + } + + return $result; + } + + /** + * request. + * + * @param string $endpoint + * @param array $params + * @param string $method + * @param array $options + * @param bool $returnResponse + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\InvalidSignException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function request(string $endpoint, array $params = [], $method = 'post', array $options = [], $returnResponse = false) + { + $base = [ + 'mch_id' => $this->app['config']['mch_id'], + ]; + + $params = array_merge($base, $this->prepends(), $params); + $params['sign'] = $this->getSign($params); + $options = array_merge([ + 'body' => Support\XML::build($params), + ], $options); + + $this->pushMiddleware($this->logMiddleware(), 'log'); + $response = $this->performRequest($endpoint, $method, $options); + $result = $returnResponse ? $response : $this->castResponseToType($response, $this->app->config->get('response_type')); + // auto verify signature + if ($returnResponse || 'array' !== ($this->app->config->get('response_type') ?? 'array')) { + $this->app->verifySignature($this->castResponseToType($response, 'array')); + } else { + $this->app->verifySignature($result); + } + + return $result; + } + + /** + * processing parameters contain fields that require sensitive information encryption. + * + * @param array $params + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\EncryptException + */ + protected function processParams(array $params) + { + $serial_no = $this->app['config']->get('serial_no'); + if (null === $serial_no) { + throw new InvalidArgumentException('config serial_no connot be empty.'); + } + + $params['cert_sn'] = $serial_no; + $sensitive_fields = $this->getSensitiveFieldsName(); + foreach ($params as $k => $v) { + if (in_array($k, $sensitive_fields, true)) { + $params[$k] = $this->encryptSensitiveInformation($v); + } + } + + return $params; + } + + /** + * To id card, mobile phone number and other fields sensitive information encryption. + * + * @param string $string + * + * @return string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\EncryptException + */ + protected function encryptSensitiveInformation(string $string) + { + $certificates = $this->app['config']->get('certificate'); + if (null === $certificates) { + throw new InvalidArgumentException('config certificate connot be empty.'); + } + + $encrypted = ''; + $publicKeyResource = openssl_get_publickey($certificates); + $f = openssl_public_encrypt($string, $encrypted, $publicKeyResource); + openssl_free_key($publicKeyResource); + if ($f) { + return base64_encode($encrypted); + } + + throw new EncryptException('Encryption of sensitive information failed'); + } + + /** + * get sensitive fields name. + * + * @return array + */ + protected function getSensitiveFieldsName() + { + return [ + 'id_card_name', + 'id_card_number', + 'account_name', + 'account_number', + 'contact', + 'contact_phone', + 'contact_email', + 'legal_person', + 'mobile_phone', + 'email', + ]; + } + + /** + * getSign. + * + * @param array $params + * + * @return string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + protected function getSign(array $params) + { + $params = array_filter($params); + + $key = $this->app->getKey(); + + $encryptMethod = Support\get_encrypt_method(Support\Arr::get($params, 'sign_type', 'MD5'), $key); + + return Support\generate_sign($params, $key, $encryptMethod); + } +} diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Kernel/Exceptions/EncryptException.php b/vendor/overtrue/wechat/src/MicroMerchant/Kernel/Exceptions/EncryptException.php new file mode 100644 index 0000000..dd874ae --- /dev/null +++ b/vendor/overtrue/wechat/src/MicroMerchant/Kernel/Exceptions/EncryptException.php @@ -0,0 +1,23 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MicroMerchant\Kernel\Exceptions; + +use EasyWeChat\Kernel\Exceptions\Exception; + +/** + * Class EncryptException. + * + * @author liuml + */ +class EncryptException extends Exception +{ +} diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Kernel/Exceptions/InvalidExtensionException.php b/vendor/overtrue/wechat/src/MicroMerchant/Kernel/Exceptions/InvalidExtensionException.php new file mode 100644 index 0000000..41e21cf --- /dev/null +++ b/vendor/overtrue/wechat/src/MicroMerchant/Kernel/Exceptions/InvalidExtensionException.php @@ -0,0 +1,23 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MicroMerchant\Kernel\Exceptions; + +use EasyWeChat\Kernel\Exceptions\Exception; + +/** + * Class InvalidExtensionException. + * + * @author liuml + */ +class InvalidExtensionException extends Exception +{ +} diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Kernel/Exceptions/InvalidSignException.php b/vendor/overtrue/wechat/src/MicroMerchant/Kernel/Exceptions/InvalidSignException.php new file mode 100644 index 0000000..d09d92b --- /dev/null +++ b/vendor/overtrue/wechat/src/MicroMerchant/Kernel/Exceptions/InvalidSignException.php @@ -0,0 +1,23 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MicroMerchant\Kernel\Exceptions; + +use EasyWeChat\Kernel\Exceptions\Exception; + +/** + * Class InvalidSignException. + * + * @author liuml + */ +class InvalidSignException extends Exception +{ +} diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Material/Client.php b/vendor/overtrue/wechat/src/MicroMerchant/Material/Client.php new file mode 100644 index 0000000..ee1038a --- /dev/null +++ b/vendor/overtrue/wechat/src/MicroMerchant/Material/Client.php @@ -0,0 +1,73 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MicroMerchant\Material; + +use EasyWeChat\MicroMerchant\Kernel\BaseClient; + +/** + * Class Client. + * + * @author liuml + * @DateTime 2019-05-30 14:19 + */ +class Client extends BaseClient +{ + /** + * update settlement card. + * + * @param array $params + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\EncryptException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function setSettlementCard(array $params) + { + $params['sub_mch_id'] = $params['sub_mch_id'] ?? $this->app['config']->sub_mch_id; + $params = $this->processParams(array_merge($params, [ + 'version' => '1.0', + 'cert_sn' => '', + 'sign_type' => 'HMAC-SHA256', + 'nonce_str' => uniqid('micro'), + ])); + + return $this->safeRequest('applyment/micro/modifyarchives', $params); + } + + /** + * update contact info. + * + * @param array $params + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\EncryptException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function updateContact(array $params) + { + $params['sub_mch_id'] = $params['sub_mch_id'] ?? $this->app['config']->sub_mch_id; + $params = $this->processParams(array_merge($params, [ + 'version' => '1.0', + 'cert_sn' => '', + 'sign_type' => 'HMAC-SHA256', + 'nonce_str' => uniqid('micro'), + ])); + + return $this->safeRequest('applyment/micro/modifycontactinfo', $params); + } +} diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Material/ServiceProvider.php b/vendor/overtrue/wechat/src/MicroMerchant/Material/ServiceProvider.php new file mode 100644 index 0000000..dec5af7 --- /dev/null +++ b/vendor/overtrue/wechat/src/MicroMerchant/Material/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MicroMerchant\Material; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['material'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Media/Client.php b/vendor/overtrue/wechat/src/MicroMerchant/Media/Client.php new file mode 100644 index 0000000..fde755b --- /dev/null +++ b/vendor/overtrue/wechat/src/MicroMerchant/Media/Client.php @@ -0,0 +1,49 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MicroMerchant\Media; + +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; +use EasyWeChat\MicroMerchant\Kernel\BaseClient; + +/** + * Class Client. + * + * @author liuml + * @DateTime 2019-06-10 14:50 + */ +class Client extends BaseClient +{ + /** + * Upload material. + * + * @param string $path + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\InvalidSignException + */ + public function upload(string $path) + { + if (!file_exists($path) || !is_readable($path)) { + throw new InvalidArgumentException(sprintf("File does not exist, or the file is unreadable: '%s'", $path)); + } + + $form = [ + 'media_hash' => strtolower(md5_file($path)), + 'sign_type' => 'HMAC-SHA256', + ]; + + return $this->httpUpload('secapi/mch/uploadmedia', ['media' => $path], $form); + } +} diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Media/ServiceProvider.php b/vendor/overtrue/wechat/src/MicroMerchant/Media/ServiceProvider.php new file mode 100644 index 0000000..9d46a84 --- /dev/null +++ b/vendor/overtrue/wechat/src/MicroMerchant/Media/ServiceProvider.php @@ -0,0 +1,44 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * ServiceProvider.php. + * + * This file is part of the wechat. + * + * (c) overtrue + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MicroMerchant\Media; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['media'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MicroMerchant/MerchantConfig/Client.php b/vendor/overtrue/wechat/src/MicroMerchant/MerchantConfig/Client.php new file mode 100644 index 0000000..c8573af --- /dev/null +++ b/vendor/overtrue/wechat/src/MicroMerchant/MerchantConfig/Client.php @@ -0,0 +1,134 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MicroMerchant\MerchantConfig; + +use EasyWeChat\MicroMerchant\Kernel\BaseClient; + +/** + * Class Client. + * + * @author liuml + * @DateTime 2019-05-30 14:19 + */ +class Client extends BaseClient +{ + /** + * Service providers configure recommendation functions for small and micro businesses. + * + * @param string $subAppId + * @param string $subscribeAppId + * @param string $receiptAppId + * @param string $subMchId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function setFollowConfig(string $subAppId, string $subscribeAppId, string $receiptAppId = '', string $subMchId = '') + { + $params = [ + 'sub_appid' => $subAppId, + 'sub_mch_id' => $subMchId ?: $this->app['config']->sub_mch_id, + ]; + + if (!empty($subscribeAppId)) { + $params['subscribe_appid'] = $subscribeAppId; + } else { + $params['receipt_appid'] = $receiptAppId; + } + + return $this->safeRequest('secapi/mkt/addrecommendconf', array_merge($params, [ + 'sign_type' => 'HMAC-SHA256', + 'nonce_str' => uniqid('micro'), + ])); + } + + /** + * Configure the new payment directory. + * + * @param string $jsapiPath + * @param string $appId + * @param string $subMchId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function addPath(string $jsapiPath, string $appId = '', string $subMchId = '') + { + return $this->addConfig([ + 'appid' => $appId ?: $this->app['config']->appid, + 'sub_mch_id' => $subMchId ?: $this->app['config']->sub_mch_id, + 'jsapi_path' => $jsapiPath, + ]); + } + + /** + * bind appid. + * + * @param string $subAppId + * @param string $appId + * @param string $subMchId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function bindAppId(string $subAppId, string $appId = '', string $subMchId = '') + { + return $this->addConfig([ + 'appid' => $appId ?: $this->app['config']->appid, + 'sub_mch_id' => $subMchId ?: $this->app['config']->sub_mch_id, + 'sub_appid' => $subAppId, + ]); + } + + /** + * add sub dev config. + * + * @param array $params + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + private function addConfig(array $params) + { + return $this->safeRequest('secapi/mch/addsubdevconfig', $params); + } + + /** + * query Sub Dev Config. + * + * @param string $subMchId + * @param string $appId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getConfig(string $subMchId = '', string $appId = '') + { + return $this->safeRequest('secapi/mch/querysubdevconfig', [ + 'sub_mch_id' => $subMchId ?: $this->app['config']->sub_mch_id, + 'appid' => $appId ?: $this->app['config']->appid, + ]); + } +} diff --git a/vendor/overtrue/wechat/src/MicroMerchant/MerchantConfig/ServiceProvider.php b/vendor/overtrue/wechat/src/MicroMerchant/MerchantConfig/ServiceProvider.php new file mode 100644 index 0000000..5b78710 --- /dev/null +++ b/vendor/overtrue/wechat/src/MicroMerchant/MerchantConfig/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MicroMerchant\MerchantConfig; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['merchantConfig'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Withdraw/Client.php b/vendor/overtrue/wechat/src/MicroMerchant/Withdraw/Client.php new file mode 100644 index 0000000..c96c363 --- /dev/null +++ b/vendor/overtrue/wechat/src/MicroMerchant/Withdraw/Client.php @@ -0,0 +1,67 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MicroMerchant\Withdraw; + +use EasyWeChat\MicroMerchant\Kernel\BaseClient; + +/** + * Class Client. + * + * @author liuml + * @DateTime 2019-05-30 14:19 + */ +class Client extends BaseClient +{ + /** + * Query withdrawal status. + * + * @param string $date + * @param string $subMchId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function queryWithdrawalStatus($date, $subMchId = '') + { + return $this->safeRequest('fund/queryautowithdrawbydate', [ + 'date' => $date, + 'sign_type' => 'HMAC-SHA256', + 'nonce_str' => uniqid('micro'), + 'sub_mch_id' => $subMchId ?: $this->app['config']->sub_mch_id, + ]); + } + + /** + * Re-initiation of withdrawal. + * + * @param string $date + * @param string $subMchId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function requestWithdraw($date, $subMchId = '') + { + return $this->safeRequest('fund/reautowithdrawbydate', [ + 'date' => $date, + 'sign_type' => 'HMAC-SHA256', + 'nonce_str' => uniqid('micro'), + 'sub_mch_id' => $subMchId ?: $this->app['config']->sub_mch_id, + ]); + } +} diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Withdraw/ServiceProvider.php b/vendor/overtrue/wechat/src/MicroMerchant/Withdraw/ServiceProvider.php new file mode 100644 index 0000000..b9c0141 --- /dev/null +++ b/vendor/overtrue/wechat/src/MicroMerchant/Withdraw/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MicroMerchant\Withdraw; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['withdraw'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/ActivityMessage/Client.php b/vendor/overtrue/wechat/src/MiniProgram/ActivityMessage/Client.php new file mode 100644 index 0000000..16f273d --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/ActivityMessage/Client.php @@ -0,0 +1,85 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\ActivityMessage; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; + +class Client extends BaseClient +{ + /** + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function createActivityId() + { + return $this->httpGet('cgi-bin/message/wxopen/activityid/create'); + } + + /** + * @param string $activityId + * @param int $state + * @param array $params + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function updateMessage(string $activityId, int $state = 0, array $params = []) + { + if (!in_array($state, [0, 1], true)) { + throw new InvalidArgumentException('"state" should be "0" or "1".'); + } + + $params = $this->formatParameters($params); + + $params = [ + 'activity_id' => $activityId, + 'target_state' => $state, + 'template_info' => ['parameter_list' => $params], + ]; + + return $this->httpPostJson('cgi-bin/message/wxopen/updatablemsg/send', $params); + } + + /** + * @param array $params + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + protected function formatParameters(array $params) + { + $formatted = []; + + foreach ($params as $name => $value) { + if (!in_array($name, ['member_count', 'room_limit', 'path', 'version_type'], true)) { + continue; + } + + if ('version_type' === $name && !in_array($value, ['develop', 'trial', 'release'], true)) { + throw new InvalidArgumentException('Invalid value of attribute "version_type".'); + } + + $formatted[] = [ + 'name' => $name, + 'value' => strval($value), + ]; + } + + return $formatted; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/ActivityMessage/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/ActivityMessage/ServiceProvider.php new file mode 100644 index 0000000..fa7d3cf --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/ActivityMessage/ServiceProvider.php @@ -0,0 +1,28 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\ActivityMessage; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['activity_message'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/AppCode/Client.php b/vendor/overtrue/wechat/src/MiniProgram/AppCode/Client.php new file mode 100644 index 0000000..82d2af2 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/AppCode/Client.php @@ -0,0 +1,92 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\AppCode; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\Http\StreamResponse; + +/** + * Class Client. + * + * @author mingyoung + */ +class Client extends BaseClient +{ + /** + * Get AppCode. + * + * @param string $path + * @param array $optional + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + */ + public function get(string $path, array $optional = []) + { + $params = array_merge([ + 'path' => $path, + ], $optional); + + return $this->getStream('wxa/getwxacode', $params); + } + + /** + * Get AppCode unlimit. + * + * @param string $scene + * @param array $optional + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + */ + public function getUnlimit(string $scene, array $optional = []) + { + $params = array_merge([ + 'scene' => $scene, + ], $optional); + + return $this->getStream('wxa/getwxacodeunlimit', $params); + } + + /** + * Create QrCode. + * + * @param string $path + * @param int|null $width + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + */ + public function getQrCode(string $path, int $width = null) + { + return $this->getStream('cgi-bin/wxaapp/createwxaqrcode', compact('path', 'width')); + } + + /** + * Get stream. + * + * @param string $endpoint + * @param array $params + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function getStream(string $endpoint, array $params) + { + $response = $this->requestRaw($endpoint, 'POST', ['json' => $params]); + + if (false !== stripos($response->getHeaderLine('Content-disposition'), 'attachment')) { + return StreamResponse::buildFromPsrResponse($response); + } + + return $this->castResponseToType($response, $this->app['config']->get('response_type')); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/AppCode/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/AppCode/ServiceProvider.php new file mode 100644 index 0000000..fefdc22 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/AppCode/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\AppCode; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author mingyoung + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['app_code'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Application.php b/vendor/overtrue/wechat/src/MiniProgram/Application.php new file mode 100644 index 0000000..4f81ff7 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Application.php @@ -0,0 +1,83 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram; + +use EasyWeChat\BasicService; +use EasyWeChat\Kernel\ServiceContainer; + +/** + * Class Application. + * + * @author mingyoung + * + * @property \EasyWeChat\MiniProgram\Auth\AccessToken $access_token + * @property \EasyWeChat\MiniProgram\DataCube\Client $data_cube + * @property \EasyWeChat\MiniProgram\AppCode\Client $app_code + * @property \EasyWeChat\MiniProgram\Auth\Client $auth + * @property \EasyWeChat\OfficialAccount\Server\Guard $server + * @property \EasyWeChat\MiniProgram\Encryptor $encryptor + * @property \EasyWeChat\MiniProgram\TemplateMessage\Client $template_message + * @property \EasyWeChat\OfficialAccount\CustomerService\Client $customer_service + * @property \EasyWeChat\MiniProgram\Plugin\Client $plugin + * @property \EasyWeChat\MiniProgram\Plugin\DevClient $plugin_dev + * @property \EasyWeChat\MiniProgram\UniformMessage\Client $uniform_message + * @property \EasyWeChat\MiniProgram\ActivityMessage\Client $activity_message + * @property \EasyWeChat\MiniProgram\Express\Client $logistics + * @property \EasyWeChat\MiniProgram\NearbyPoi\Client $nearby_poi + * @property \EasyWeChat\MiniProgram\OCR\Client $ocr + * @property \EasyWeChat\MiniProgram\Soter\Client $soter + * @property \EasyWeChat\BasicService\Media\Client $media + * @property \EasyWeChat\BasicService\ContentSecurity\Client $content_security + * @property \EasyWeChat\MiniProgram\Mall\ForwardsMall $mall + * @property \EasyWeChat\MiniProgram\SubscribeMessage\Client $subscribe_message + */ +class Application extends ServiceContainer +{ + /** + * @var array + */ + protected $providers = [ + Auth\ServiceProvider::class, + DataCube\ServiceProvider::class, + AppCode\ServiceProvider::class, + Server\ServiceProvider::class, + TemplateMessage\ServiceProvider::class, + CustomerService\ServiceProvider::class, + UniformMessage\ServiceProvider::class, + ActivityMessage\ServiceProvider::class, + OpenData\ServiceProvider::class, + Plugin\ServiceProvider::class, + Base\ServiceProvider::class, + Express\ServiceProvider::class, + NearbyPoi\ServiceProvider::class, + OCR\ServiceProvider::class, + Soter\ServiceProvider::class, + Mall\ServiceProvider::class, + SubscribeMessage\ServiceProvider::class, + // Base services + BasicService\Media\ServiceProvider::class, + BasicService\ContentSecurity\ServiceProvider::class, + ]; + + /** + * Handle dynamic calls. + * + * @param string $method + * @param array $args + * + * @return mixed + */ + public function __call($method, $args) + { + return $this->base->$method(...$args); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Auth/AccessToken.php b/vendor/overtrue/wechat/src/MiniProgram/Auth/AccessToken.php new file mode 100644 index 0000000..8147d25 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Auth/AccessToken.php @@ -0,0 +1,39 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\Auth; + +use EasyWeChat\Kernel\AccessToken as BaseAccessToken; + +/** + * Class AccessToken. + * + * @author mingyoung + */ +class AccessToken extends BaseAccessToken +{ + /** + * @var string + */ + protected $endpointToGetToken = 'https://api.weixin.qq.com/cgi-bin/token'; + + /** + * {@inheritdoc} + */ + protected function getCredentials(): array + { + return [ + 'grant_type' => 'client_credential', + 'appid' => $this->app['config']['app_id'], + 'secret' => $this->app['config']['secret'], + ]; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Auth/Client.php b/vendor/overtrue/wechat/src/MiniProgram/Auth/Client.php new file mode 100644 index 0000000..90d2c88 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Auth/Client.php @@ -0,0 +1,43 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\Auth; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Auth. + * + * @author mingyoung + */ +class Client extends BaseClient +{ + /** + * Get session info by code. + * + * @param string $code + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function session(string $code) + { + $params = [ + 'appid' => $this->app['config']['app_id'], + 'secret' => $this->app['config']['secret'], + 'js_code' => $code, + 'grant_type' => 'authorization_code', + ]; + + return $this->httpGet('sns/jscode2session', $params); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Auth/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Auth/ServiceProvider.php new file mode 100644 index 0000000..fcb687b --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Auth/ServiceProvider.php @@ -0,0 +1,32 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\Auth; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + !isset($app['access_token']) && $app['access_token'] = function ($app) { + return new AccessToken($app); + }; + + !isset($app['auth']) && $app['auth'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Base/Client.php b/vendor/overtrue/wechat/src/MiniProgram/Base/Client.php new file mode 100644 index 0000000..84d6b81 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Base/Client.php @@ -0,0 +1,38 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\Base; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author mingyoung + */ +class Client extends BaseClient +{ + /** + * Get paid unionid. + * + * @param string $openid + * @param array $options + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getPaidUnionid($openid, $options = []) + { + return $this->httpGet('wxa/getpaidunionid', compact('openid') + $options); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Base/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Base/ServiceProvider.php new file mode 100644 index 0000000..d9aa41b --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Base/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\Base; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author mingyoung + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['base'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/CustomerService/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/CustomerService/ServiceProvider.php new file mode 100644 index 0000000..cfd3039 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/CustomerService/ServiceProvider.php @@ -0,0 +1,34 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\CustomerService; + +use EasyWeChat\OfficialAccount\CustomerService\Client; +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['customer_service'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/DataCube/Client.php b/vendor/overtrue/wechat/src/MiniProgram/DataCube/Client.php new file mode 100644 index 0000000..8a10b7e --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/DataCube/Client.php @@ -0,0 +1,174 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\DataCube; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author mingyoung + */ +class Client extends BaseClient +{ + /** + * Get summary trend. + * + * @param string $from + * @param string $to + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + */ + public function summaryTrend(string $from, string $to) + { + return $this->query('datacube/getweanalysisappiddailysummarytrend', $from, $to); + } + + /** + * Get daily visit trend. + * + * @param string $from + * @param string $to + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + */ + public function dailyVisitTrend(string $from, string $to) + { + return $this->query('datacube/getweanalysisappiddailyvisittrend', $from, $to); + } + + /** + * Get weekly visit trend. + * + * @param string $from + * @param string $to + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + */ + public function weeklyVisitTrend(string $from, string $to) + { + return $this->query('datacube/getweanalysisappidweeklyvisittrend', $from, $to); + } + + /** + * Get monthly visit trend. + * + * @param string $from + * @param string $to + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + */ + public function monthlyVisitTrend(string $from, string $to) + { + return $this->query('datacube/getweanalysisappidmonthlyvisittrend', $from, $to); + } + + /** + * Get visit distribution. + * + * @param string $from + * @param string $to + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + */ + public function visitDistribution(string $from, string $to) + { + return $this->query('datacube/getweanalysisappidvisitdistribution', $from, $to); + } + + /** + * Get daily retain info. + * + * @param string $from + * @param string $to + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + */ + public function dailyRetainInfo(string $from, string $to) + { + return $this->query('datacube/getweanalysisappiddailyretaininfo', $from, $to); + } + + /** + * Get weekly retain info. + * + * @param string $from + * @param string $to + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + */ + public function weeklyRetainInfo(string $from, string $to) + { + return $this->query('datacube/getweanalysisappidweeklyretaininfo', $from, $to); + } + + /** + * Get monthly retain info. + * + * @param string $from + * @param string $to + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + */ + public function monthlyRetainInfo(string $from, string $to) + { + return $this->query('datacube/getweanalysisappidmonthlyretaininfo', $from, $to); + } + + /** + * Get visit page. + * + * @param string $from + * @param string $to + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + */ + public function visitPage(string $from, string $to) + { + return $this->query('datacube/getweanalysisappidvisitpage', $from, $to); + } + + /** + * Get user portrait. + * + * @param string $from + * @param string $to + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + */ + public function userPortrait(string $from, string $to) + { + return $this->query('datacube/getweanalysisappiduserportrait', $from, $to); + } + + /** + * Unify query. + * + * @param string $api + * @param string $from + * @param string $to + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function query(string $api, string $from, string $to) + { + $params = [ + 'begin_date' => $from, + 'end_date' => $to, + ]; + + return $this->httpPostJson($api, $params); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/DataCube/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/DataCube/ServiceProvider.php new file mode 100644 index 0000000..258d7c4 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/DataCube/ServiceProvider.php @@ -0,0 +1,28 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\DataCube; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['data_cube'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Encryptor.php b/vendor/overtrue/wechat/src/MiniProgram/Encryptor.php new file mode 100644 index 0000000..167a2a0 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Encryptor.php @@ -0,0 +1,50 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram; + +use EasyWeChat\Kernel\Encryptor as BaseEncryptor; +use EasyWeChat\Kernel\Exceptions\DecryptException; +use EasyWeChat\Kernel\Support\AES; + +/** + * Class Encryptor. + * + * @author mingyoung + */ +class Encryptor extends BaseEncryptor +{ + /** + * Decrypt data. + * + * @param string $sessionKey + * @param string $iv + * @param string $encrypted + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\DecryptException + */ + public function decryptData(string $sessionKey, string $iv, string $encrypted): array + { + $decrypted = AES::decrypt( + base64_decode($encrypted, false), base64_decode($sessionKey, false), base64_decode($iv, false) + ); + + $decrypted = json_decode($this->pkcs7Unpad($decrypted), true); + + if (!$decrypted) { + throw new DecryptException('The given payload is invalid.'); + } + + return $decrypted; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Express/Client.php b/vendor/overtrue/wechat/src/MiniProgram/Express/Client.php new file mode 100644 index 0000000..a0311c8 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Express/Client.php @@ -0,0 +1,133 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\Express; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author kehuanhuan <1152018701@qq.com> + */ +class Client extends BaseClient +{ + /** + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function listProviders() + { + return $this->httpGet('cgi-bin/express/business/delivery/getall'); + } + + /** + * @param array $params + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function createWaybill(array $params = []) + { + return $this->httpPostJson('cgi-bin/express/business/order/add', $params); + } + + /** + * @param array $params + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function deleteWaybill(array $params = []) + { + return $this->httpPostJson('cgi-bin/express/business/order/cancel', $params); + } + + /** + * @param array $params + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getWaybill(array $params = []) + { + return $this->httpPostJson('cgi-bin/express/business/order/get', $params); + } + + /** + * @param array $params + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getWaybillTrack(array $params = []) + { + return $this->httpPostJson('cgi-bin/express/business/path/get', $params); + } + + /** + * @param string $deliveryId + * @param string $bizId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getBalance(string $deliveryId, string $bizId) + { + return $this->httpPostJson('cgi-bin/express/business/quota/get', [ + 'delivery_id' => $deliveryId, + 'biz_id' => $bizId, + ]); + } + + /** + * @param string $openid + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function bindPrinter(string $openid) + { + return $this->httpPostJson('cgi-bin/express/business/printer/update', [ + 'update_type' => 'bind', + 'openid' => $openid, + ]); + } + + /** + * @param string $openid + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function unbindPrinter(string $openid) + { + return $this->httpPostJson('cgi-bin/express/business/printer/update', [ + 'update_type' => 'unbind', + 'openid' => $openid, + ]); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Express/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Express/ServiceProvider.php new file mode 100644 index 0000000..4794094 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Express/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\Express; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author kehuanhuan <1152018701@qq.com> + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['express'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Mall/CartClient.php b/vendor/overtrue/wechat/src/MiniProgram/Mall/CartClient.php new file mode 100644 index 0000000..7a07e77 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Mall/CartClient.php @@ -0,0 +1,88 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\Mall; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author mingyoung + */ +class CartClient extends BaseClient +{ + /** + * 导入收藏. + * + * @param array $params + * @param bool $isTest + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function add($params, $isTest = false) + { + return $this->httpPostJson('mall/addshoppinglist', $params, ['is_test' => (int) $isTest]); + } + + /** + * 查询用户收藏信息. + * + * @param array $params + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function query($params) + { + return $this->httpPostJson('mall/queryshoppinglist', $params, ['type' => 'batchquery']); + } + + /** + * 查询用户收藏信息. + * + * @param array $params + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function queryByPage($params) + { + return $this->httpPostJson('mall/queryshoppinglist', $params, ['type' => 'getbypage']); + } + + /** + * 删除收藏. + * + * @param string $openid + * @param array $products + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function delete($openid, array $products = []) + { + if (empty($products)) { + return $this->httpPostJson('mall/deletebizallshoppinglist', ['user_open_id' => $openid]); + } + + return $this->httpPostJson('mall/deleteshoppinglist', ['user_open_id' => $openid, 'sku_product_list' => $products]); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Mall/ForwardsMall.php b/vendor/overtrue/wechat/src/MiniProgram/Mall/ForwardsMall.php new file mode 100644 index 0000000..f727b17 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Mall/ForwardsMall.php @@ -0,0 +1,48 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\Mall; + +/** + * Class Application. + * + * @author mingyoung + * + * @property \EasyWeChat\MiniProgram\Mall\OrderClient $order + * @property \EasyWeChat\MiniProgram\Mall\CartClient $cart + * @property \EasyWeChat\MiniProgram\Mall\ProductClient $product + * @property \EasyWeChat\MiniProgram\Mall\MediaClient $media + */ +class ForwardsMall +{ + /** + * @var \EasyWeChat\Kernel\ServiceContainer + */ + protected $app; + + /** + * @param \EasyWeChat\Kernel\ServiceContainer $app + */ + public function __construct($app) + { + $this->app = $app; + } + + /** + * @param string $property + * + * @return mixed + */ + public function __get($property) + { + return $this->app["mall.{$property}"]; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Mall/MediaClient.php b/vendor/overtrue/wechat/src/MiniProgram/Mall/MediaClient.php new file mode 100644 index 0000000..a3827bc --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Mall/MediaClient.php @@ -0,0 +1,37 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\Mall; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author mingyoung + */ +class MediaClient extends BaseClient +{ + /** + * 更新或导入媒体信息. + * + * @param array $params + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function import($params) + { + return $this->httpPostJson('mall/importmedia', $params); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Mall/OrderClient.php b/vendor/overtrue/wechat/src/MiniProgram/Mall/OrderClient.php new file mode 100644 index 0000000..9879695 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Mall/OrderClient.php @@ -0,0 +1,75 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\Mall; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author mingyoung + */ +class OrderClient extends BaseClient +{ + /** + * 导入订单. + * + * @param array $params + * @param bool $isHistory + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function add($params, $isHistory = false) + { + return $this->httpPostJson('mall/importorder', $params, ['action' => 'add-order', 'is_history' => (int) $isHistory]); + } + + /** + * 导入订单. + * + * @param array $params + * @param bool $isHistory + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function update($params, $isHistory = false) + { + return $this->httpPostJson('mall/importorder', $params, ['action' => 'update-order', 'is_history' => (int) $isHistory]); + } + + /** + * 删除订单. + * + * @param string $openid + * @param string $orderId + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function delete($openid, $orderId) + { + $params = [ + 'user_open_id' => $openid, + 'order_id' => $orderId, + ]; + + return $this->httpPostJson('mall/deleteorder', $params); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Mall/ProductClient.php b/vendor/overtrue/wechat/src/MiniProgram/Mall/ProductClient.php new file mode 100644 index 0000000..f7fbfd0 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Mall/ProductClient.php @@ -0,0 +1,68 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\Mall; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author mingyoung + */ +class ProductClient extends BaseClient +{ + /** + * 更新或导入物品信息. + * + * @param array $params + * @param bool $isTest + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function import($params, $isTest = false) + { + return $this->httpPostJson('mall/importproduct', $params, ['is_test' => (int) $isTest]); + } + + /** + * 查询物品信息. + * + * @param array $params + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function query($params) + { + return $this->httpPostJson('mall/queryproduct', $params, ['type' => 'batchquery']); + } + + /** + * 小程序的物品是否可被搜索. + * + * @param bool $value + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function setSearchable($value) + { + return $this->httpPostJson('mall/brandmanage', ['can_be_search' => $value], ['action' => 'set_biz_can_be_search']); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Mall/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Mall/ServiceProvider.php new file mode 100644 index 0000000..964395b --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Mall/ServiceProvider.php @@ -0,0 +1,44 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\Mall; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['mall'] = function ($app) { + return new ForwardsMall($app); + }; + + $app['mall.order'] = function ($app) { + return new OrderClient($app); + }; + + $app['mall.cart'] = function ($app) { + return new CartClient($app); + }; + + $app['mall.product'] = function ($app) { + return new ProductClient($app); + }; + + $app['mall.media'] = function ($app) { + return new MediaClient($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/NearbyPoi/Client.php b/vendor/overtrue/wechat/src/MiniProgram/NearbyPoi/Client.php new file mode 100644 index 0000000..da9ee05 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/NearbyPoi/Client.php @@ -0,0 +1,123 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\NearbyPoi; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; + +/** + * Class Client. + * + * @author joyeekk + */ +class Client extends BaseClient +{ + /** + * Add nearby poi. + * + * @param array $params + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function add(array $params) + { + $params = array_merge([ + 'is_comm_nearby' => '1', + 'poi_id' => '', + ], $params); + + return $this->httpPostJson('wxa/addnearbypoi', $params); + } + + /** + * Update nearby poi. + * + * @param string $poiId + * @param array $params + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function update(string $poiId, array $params) + { + $params = array_merge([ + 'is_comm_nearby' => '1', + 'poi_id' => $poiId, + ], $params); + + return $this->httpPostJson('wxa/addnearbypoi', $params); + } + + /** + * Delete nearby poi. + * + * @param string $poiId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function delete(string $poiId) + { + return $this->httpPostJson('wxa/delnearbypoi', [ + 'poi_id' => $poiId, + ]); + } + + /** + * Get nearby poi list. + * + * @param int $page + * @param int $pageRows + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function list(int $page, int $pageRows) + { + return $this->httpGet('wxa/getnearbypoilist', [ + 'page' => $page, + 'page_rows' => $pageRows, + ]); + } + + /** + * Set nearby poi show status. + * + * @param string $poiId + * @param int $status + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function setVisibility(string $poiId, int $status) + { + if (!in_array($status, [0, 1], true)) { + throw new InvalidArgumentException('status should be 0 or 1.'); + } + + return $this->httpPostJson('wxa/setnearbypoishowstatus', [ + 'poi_id' => $poiId, + 'status' => $status, + ]); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/NearbyPoi/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/NearbyPoi/ServiceProvider.php new file mode 100644 index 0000000..cb15bb3 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/NearbyPoi/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\NearbyPoi; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author joyeekk + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['nearby_poi'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/OCR/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/OCR/ServiceProvider.php new file mode 100644 index 0000000..80e0001 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/OCR/ServiceProvider.php @@ -0,0 +1,34 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\OCR; + +use EasyWeChat\OfficialAccount\OCR\Client; +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author joyeekk + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['ocr'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/OpenData/Client.php b/vendor/overtrue/wechat/src/MiniProgram/OpenData/Client.php new file mode 100644 index 0000000..c9472c6 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/OpenData/Client.php @@ -0,0 +1,96 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\OpenData; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author tianyong90 <412039588@qq.com> + */ +class Client extends BaseClient +{ + /** + * @var string + */ + protected $baseUri = 'https://api.weixin.qq.com/wxa/'; + + /** + * removeUserStorage. + * + * @param string $openid + * @param string $sessionKey + * @param array $key + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function removeUserStorage(string $openid, string $sessionKey, array $key) + { + $data = ['key' => $key]; + $query = [ + 'openid' => $openid, + 'sig_method' => 'hmac_sha256', + 'signature' => hash_hmac('sha256', json_encode($data), $sessionKey), + ]; + + return $this->httpPostJson('remove_user_storage', $data, $query); + } + + /** + * setUserStorage. + * + * @param string $openid + * @param string $sessionKey + * @param array $kvList + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function setUserStorage(string $openid, string $sessionKey, array $kvList) + { + $kvList = $this->formatKVLists($kvList); + + $data = ['kv_list' => $kvList]; + $query = [ + 'openid' => $openid, + 'sig_method' => 'hmac_sha256', + 'signature' => hash_hmac('sha256', json_encode($data), $sessionKey), + ]; + + return $this->httpPostJson('set_user_storage', $data, $query); + } + + /** + * @param array $params + * + * @return array + */ + protected function formatKVLists(array $params) + { + $formatted = []; + + foreach ($params as $name => $value) { + $formatted[] = [ + 'key' => $name, + 'value' => strval($value), + ]; + } + + return $formatted; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/OpenData/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/OpenData/ServiceProvider.php new file mode 100644 index 0000000..3ea1287 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/OpenData/ServiceProvider.php @@ -0,0 +1,28 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\OpenData; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['open_data'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Plugin/Client.php b/vendor/overtrue/wechat/src/MiniProgram/Plugin/Client.php new file mode 100644 index 0000000..cd885cc --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Plugin/Client.php @@ -0,0 +1,67 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\Plugin; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author mingyoung + */ +class Client extends BaseClient +{ + /** + * @param string $appId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function apply($appId) + { + return $this->httpPostJson('wxa/plugin', [ + 'action' => 'apply', + 'plugin_appid' => $appId, + ]); + } + + /** + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function list() + { + return $this->httpPostJson('wxa/plugin', [ + 'action' => 'list', + ]); + } + + /** + * @param string $appId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function unbind($appId) + { + return $this->httpPostJson('wxa/plugin', [ + 'action' => 'unbind', + 'plugin_appid' => $appId, + ]); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Plugin/DevClient.php b/vendor/overtrue/wechat/src/MiniProgram/Plugin/DevClient.php new file mode 100644 index 0000000..3f25edc --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Plugin/DevClient.php @@ -0,0 +1,93 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\Plugin; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class DevClient. + * + * @author her-cat + */ +class DevClient extends BaseClient +{ + /** + * Get users. + * + * @param int $page + * @param int $size + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getUsers(int $page = 1, int $size = 10) + { + return $this->httpPostJson('wxa/devplugin', [ + 'action' => 'dev_apply_list', + 'page' => $page, + 'num' => $size, + ]); + } + + /** + * Agree to use plugin. + * + * @param string $appId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function agree(string $appId) + { + return $this->httpPostJson('wxa/devplugin', [ + 'action' => 'dev_agree', + 'appid' => $appId, + ]); + } + + /** + * Refuse to use plugin. + * + * @param string $reason + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function refuse(string $reason) + { + return $this->httpPostJson('wxa/devplugin', [ + 'action' => 'dev_refuse', + 'reason' => $reason, + ]); + } + + /** + * Delete rejected applications. + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function delete() + { + return $this->httpPostJson('wxa/devplugin', [ + 'action' => 'dev_delete', + ]); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Plugin/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Plugin/ServiceProvider.php new file mode 100644 index 0000000..097c77e --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Plugin/ServiceProvider.php @@ -0,0 +1,42 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\Plugin; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author mingyoung + */ +class ServiceProvider 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 \Pimple\Container $app + */ + public function register(Container $app) + { + $app['plugin'] = function ($app) { + return new Client($app); + }; + + $app['plugin_dev'] = function ($app) { + return new DevClient($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Server/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Server/ServiceProvider.php new file mode 100644 index 0000000..0586bb9 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Server/ServiceProvider.php @@ -0,0 +1,42 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\Server; + +use EasyWeChat\MiniProgram\Encryptor; +use EasyWeChat\OfficialAccount\Server\Guard; +use EasyWeChat\OfficialAccount\Server\Handlers\EchoStrHandler; +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + !isset($app['encryptor']) && $app['encryptor'] = function ($app) { + return new Encryptor( + $app['config']['app_id'], + $app['config']['token'], + $app['config']['aes_key'] + ); + }; + + !isset($app['server']) && $app['server'] = function ($app) { + $guard = new Guard($app); + $guard->push(new EchoStrHandler($app)); + + return $guard; + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Soter/Client.php b/vendor/overtrue/wechat/src/MiniProgram/Soter/Client.php new file mode 100644 index 0000000..cdf66d7 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Soter/Client.php @@ -0,0 +1,41 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\Soter; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author her-cat + */ +class Client extends BaseClient +{ + /** + * @param string $openid + * @param string $json + * @param string $signature + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function verifySignature(string $openid, string $json, string $signature) + { + return $this->httpPostJson('cgi-bin/soter/verify_signature', [ + 'openid' => $openid, + 'json_string' => $json, + 'json_signature' => $signature, + ]); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Soter/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Soter/ServiceProvider.php new file mode 100644 index 0000000..c8520db --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Soter/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\Soter; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author her-cat + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc} + */ + public function register(Container $app) + { + $app['soter'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/SubscribeMessage/Client.php b/vendor/overtrue/wechat/src/MiniProgram/SubscribeMessage/Client.php new file mode 100644 index 0000000..a9a7dfe --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/SubscribeMessage/Client.php @@ -0,0 +1,208 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\SubscribeMessage; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; +use ReflectionClass; + +/** + * Class Client. + * + * @author hugo + */ +class Client extends BaseClient +{ + /** + * {@inheritdoc}. + */ + protected $message = [ + 'touser' => '', + 'template_id' => '', + 'page' => '', + 'data' => [], + ]; + + /** + * {@inheritdoc}. + */ + protected $required = ['touser', 'template_id', 'data']; + + /** + * Send a template message. + * + * @param array $data + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function send(array $data = []) + { + $params = $this->formatMessage($data); + + $this->restoreMessage(); + + return $this->httpPostJson('cgi-bin/message/subscribe/send', $params); + } + + /** + * @param array $data + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + protected function formatMessage(array $data = []) + { + $params = array_merge($this->message, $data); + + foreach ($params as $key => $value) { + if (in_array($key, $this->required, true) && empty($value) && empty($this->message[$key])) { + throw new InvalidArgumentException(sprintf('Attribute "%s" can not be empty!', $key)); + } + + $params[$key] = empty($value) ? $this->message[$key] : $value; + } + + foreach ($params['data'] as $key => $value) { + if (is_array($value)) { + if (isset($value['value'])) { + $params['data'][$key] = ['value' => $value['value']]; + + continue; + } + + if (count($value) >= 1) { + $value = [ + 'value' => $value[0], +// 'color' => $value[1],// color unsupported + ]; + } + } else { + $value = [ + 'value' => strval($value), + ]; + } + + $params['data'][$key] = $value; + } + + return $params; + } + + /** + * Restore message. + */ + protected function restoreMessage() + { + $this->message = (new ReflectionClass(static::class))->getDefaultProperties()['message']; + } + + /** + * Combine templates and add them to your personal template library under your account. + * + * @param string $tid + * @param array $kidList + * @param string|null $sceneDesc + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function addTemplate(string $tid, array $kidList, string $sceneDesc = null) + { + $sceneDesc = $sceneDesc ?? ''; + $data = \compact('tid', 'kidList', 'sceneDesc'); + + return $this->httpPost('wxaapi/newtmpl/addtemplate', $data); + } + + /** + * Delete personal template under account. + * + * @param string $id + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function deleteTemplate(string $id) + { + return $this->httpPost('wxaapi/newtmpl/deltemplate', ['priTmplId' => $id]); + } + + /** + * Get keyword list under template title. + * + * @param string $tid + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getTemplateKeywords(string $tid) + { + return $this->httpGet('wxaapi/newtmpl/getpubtemplatekeywords', compact('tid')); + } + + /** + * Get the title of the public template under the category to which the account belongs. + * + * @param array $ids + * @param int $start + * @param int $limit + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getTemplateTitles(array $ids, int $start = 0, int $limit = 30) + { + $ids = \implode(',', $ids); + $query = \compact('ids', 'start', 'limit'); + + return $this->httpGet('wxaapi/newtmpl/getpubtemplatetitles', $query); + } + + /** + * Get list of personal templates under the current account. + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getTemplates() + { + return $this->httpGet('wxaapi/newtmpl/gettemplate'); + } + + /** + * Get the category of the applet account. + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getCategory() + { + return $this->httpGet('wxaapi/newtmpl/getcategory'); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/SubscribeMessage/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/SubscribeMessage/ServiceProvider.php new file mode 100644 index 0000000..726b3ed --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/SubscribeMessage/ServiceProvider.php @@ -0,0 +1,28 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\SubscribeMessage; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['subscribe_message'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/TemplateMessage/Client.php b/vendor/overtrue/wechat/src/MiniProgram/TemplateMessage/Client.php new file mode 100644 index 0000000..08f8a3b --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/TemplateMessage/Client.php @@ -0,0 +1,114 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\TemplateMessage; + +use EasyWeChat\OfficialAccount\TemplateMessage\Client as BaseClient; + +/** + * Class Client. + * + * @author mingyoung + */ +class Client extends BaseClient +{ + const API_SEND = 'cgi-bin/message/wxopen/template/send'; + + /** + * {@inheritdoc}. + */ + protected $message = [ + 'touser' => '', + 'template_id' => '', + 'page' => '', + 'form_id' => '', + 'data' => [], + 'emphasis_keyword' => '', + ]; + + /** + * {@inheritdoc}. + */ + protected $required = ['touser', 'template_id', 'form_id']; + + /** + * @param int $offset + * @param int $count + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function list(int $offset, int $count) + { + return $this->httpPostJson('cgi-bin/wxopen/template/library/list', compact('offset', 'count')); + } + + /** + * @param string $id + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function get(string $id) + { + return $this->httpPostJson('cgi-bin/wxopen/template/library/get', compact('id')); + } + + /** + * @param string $id + * @param array $keyword + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function add(string $id, array $keyword) + { + return $this->httpPostJson('cgi-bin/wxopen/template/add', [ + 'id' => $id, + 'keyword_id_list' => $keyword, + ]); + } + + /** + * @param string $templateId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function delete(string $templateId) + { + return $this->httpPostJson('cgi-bin/wxopen/template/del', [ + 'template_id' => $templateId, + ]); + } + + /** + * @param int $offset + * @param int $count + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getTemplates(int $offset, int $count) + { + return $this->httpPostJson('cgi-bin/wxopen/template/list', compact('offset', 'count')); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/TemplateMessage/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/TemplateMessage/ServiceProvider.php new file mode 100644 index 0000000..776a15e --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/TemplateMessage/ServiceProvider.php @@ -0,0 +1,28 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\TemplateMessage; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['template_message'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/UniformMessage/Client.php b/vendor/overtrue/wechat/src/MiniProgram/UniformMessage/Client.php new file mode 100644 index 0000000..6acb13e --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/UniformMessage/Client.php @@ -0,0 +1,146 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\UniformMessage; + +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; +use EasyWeChat\OfficialAccount\TemplateMessage\Client as BaseClient; + +class Client extends BaseClient +{ + const API_SEND = 'cgi-bin/message/wxopen/template/uniform_send'; + + /** + * {@inheritdoc}. + * + * @var array + */ + protected $message = [ + 'touser' => '', + ]; + + /** + * Weapp Attributes. + * + * @var array + */ + protected $weappMessage = [ + 'template_id' => '', + 'page' => '', + 'form_id' => '', + 'data' => [], + 'emphasis_keyword' => '', + ]; + + /** + * Official account attributes. + * + * @var array + */ + protected $mpMessage = [ + 'appid' => '', + 'template_id' => '', + 'url' => '', + 'miniprogram' => [], + 'data' => [], + ]; + + /** + * Required attributes. + * + * @var array + */ + protected $required = ['touser', 'template_id', 'form_id', 'miniprogram', 'appid']; + + /** + * @param array $data + * + * @return array + * + * @throws InvalidArgumentException + */ + protected function formatMessage(array $data = []) + { + $params = array_merge($this->message, $data); + + if (empty($params['touser'])) { + throw new InvalidArgumentException(sprintf('Attribute "touser" can not be empty!')); + } + + if (!empty($params['weapp_template_msg'])) { + $params['weapp_template_msg'] = $this->formatWeappMessage($params['weapp_template_msg']); + } + + if (!empty($params['mp_template_msg'])) { + $params['mp_template_msg'] = $this->formatMpMessage($params['mp_template_msg']); + } + + return $params; + } + + /** + * @param array $data + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + protected function formatWeappMessage(array $data = []) + { + $params = $this->baseFormat($data, $this->weappMessage); + + $params['data'] = $this->formatData($params['data'] ?? []); + + return $params; + } + + /** + * @param array $data + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + protected function formatMpMessage(array $data = []) + { + $params = $this->baseFormat($data, $this->mpMessage); + + if (empty($params['miniprogram']['appid'])) { + $params['miniprogram']['appid'] = $this->app['config']['app_id']; + } + + $params['data'] = $this->formatData($params['data'] ?? []); + + return $params; + } + + /** + * @param array $data + * @param array $default + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + protected function baseFormat($data = [], $default = []) + { + $params = array_merge($default, $data); + foreach ($params as $key => $value) { + if (in_array($key, $this->required, true) && empty($value) && empty($default[$key])) { + throw new InvalidArgumentException(sprintf('Attribute "%s" can not be empty!', $key)); + } + + $params[$key] = empty($value) ? $default[$key] : $value; + } + + return $params; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/UniformMessage/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/UniformMessage/ServiceProvider.php new file mode 100644 index 0000000..0e86db3 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/UniformMessage/ServiceProvider.php @@ -0,0 +1,28 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\UniformMessage; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['uniform_message'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Application.php b/vendor/overtrue/wechat/src/OfficialAccount/Application.php new file mode 100644 index 0000000..745363f --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Application.php @@ -0,0 +1,88 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount; + +use EasyWeChat\BasicService; +use EasyWeChat\Kernel\ServiceContainer; + +/** + * Class Application. + * + * @author overtrue + * + * @property \EasyWeChat\BasicService\Media\Client $media + * @property \EasyWeChat\BasicService\Url\Client $url + * @property \EasyWeChat\BasicService\QrCode\Client $qrcode + * @property \EasyWeChat\BasicService\Jssdk\Client $jssdk + * @property \EasyWeChat\OfficialAccount\Auth\AccessToken $access_token + * @property \EasyWeChat\OfficialAccount\Server\Guard $server + * @property \EasyWeChat\OfficialAccount\User\UserClient $user + * @property \EasyWeChat\OfficialAccount\User\TagClient $user_tag + * @property \EasyWeChat\OfficialAccount\Menu\Client $menu + * @property \EasyWeChat\OfficialAccount\TemplateMessage\Client $template_message + * @property \EasyWeChat\OfficialAccount\Material\Client $material + * @property \EasyWeChat\OfficialAccount\CustomerService\Client $customer_service + * @property \EasyWeChat\OfficialAccount\CustomerService\SessionClient $customer_service_session + * @property \EasyWeChat\OfficialAccount\Semantic\Client $semantic + * @property \EasyWeChat\OfficialAccount\DataCube\Client $data_cube + * @property \EasyWeChat\OfficialAccount\AutoReply\Client $auto_reply + * @property \EasyWeChat\OfficialAccount\Broadcasting\Client $broadcasting + * @property \EasyWeChat\OfficialAccount\Card\Card $card + * @property \EasyWeChat\OfficialAccount\Device\Client $device + * @property \EasyWeChat\OfficialAccount\ShakeAround\ShakeAround $shake_around + * @property \EasyWeChat\OfficialAccount\POI\Client $poi + * @property \EasyWeChat\OfficialAccount\Store\Client $store + * @property \EasyWeChat\OfficialAccount\Base\Client $base + * @property \EasyWeChat\OfficialAccount\Comment\Client $comment + * @property \EasyWeChat\OfficialAccount\OCR\Client $ocr + * @property \EasyWeChat\OfficialAccount\Goods\Client $goods + * @property \Overtrue\Socialite\Providers\WeChatProvider $oauth + * @property \EasyWeChat\OfficialAccount\WiFi\Client $wifi + * @property \EasyWeChat\OfficialAccount\WiFi\CardClient $wifi_card + * @property \EasyWeChat\OfficialAccount\WiFi\DeviceClient $wifi_device + * @property \EasyWeChat\OfficialAccount\WiFi\ShopClient $wifi_shop + */ +class Application extends ServiceContainer +{ + /** + * @var array + */ + protected $providers = [ + Auth\ServiceProvider::class, + Server\ServiceProvider::class, + User\ServiceProvider::class, + OAuth\ServiceProvider::class, + Menu\ServiceProvider::class, + TemplateMessage\ServiceProvider::class, + Material\ServiceProvider::class, + CustomerService\ServiceProvider::class, + Semantic\ServiceProvider::class, + DataCube\ServiceProvider::class, + POI\ServiceProvider::class, + AutoReply\ServiceProvider::class, + Broadcasting\ServiceProvider::class, + Card\ServiceProvider::class, + Device\ServiceProvider::class, + ShakeAround\ServiceProvider::class, + Store\ServiceProvider::class, + Comment\ServiceProvider::class, + Base\ServiceProvider::class, + OCR\ServiceProvider::class, + Goods\ServiceProvider::class, + WiFi\ServiceProvider::class, + // Base services + BasicService\QrCode\ServiceProvider::class, + BasicService\Media\ServiceProvider::class, + BasicService\Url\ServiceProvider::class, + BasicService\Jssdk\ServiceProvider::class, + ]; +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Auth/AccessToken.php b/vendor/overtrue/wechat/src/OfficialAccount/Auth/AccessToken.php new file mode 100644 index 0000000..aca1aca --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Auth/AccessToken.php @@ -0,0 +1,39 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Auth; + +use EasyWeChat\Kernel\AccessToken as BaseAccessToken; + +/** + * Class AuthorizerAccessToken. + * + * @author overtrue + */ +class AccessToken extends BaseAccessToken +{ + /** + * @var string + */ + protected $endpointToGetToken = 'https://api.weixin.qq.com/cgi-bin/token'; + + /** + * @return array + */ + protected function getCredentials(): array + { + return [ + 'grant_type' => 'client_credential', + 'appid' => $this->app['config']['app_id'], + 'secret' => $this->app['config']['secret'], + ]; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Auth/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Auth/ServiceProvider.php new file mode 100644 index 0000000..e748730 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Auth/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Auth; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + !isset($app['access_token']) && $app['access_token'] = function ($app) { + return new AccessToken($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/AutoReply/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/AutoReply/Client.php new file mode 100644 index 0000000..6d2e4d6 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/AutoReply/Client.php @@ -0,0 +1,34 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\AutoReply; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author overtrue + */ +class Client extends BaseClient +{ + /** + * Get current auto reply settings. + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function current() + { + return $this->httpGet('cgi-bin/get_current_autoreply_info'); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/AutoReply/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/AutoReply/ServiceProvider.php new file mode 100644 index 0000000..4377550 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/AutoReply/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\AutoReply; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['auto_reply'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Base/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/Base/Client.php new file mode 100644 index 0000000..ce8abc5 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Base/Client.php @@ -0,0 +1,84 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Base; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; + +/** + * Class Client. + * + * @author mingyoung + */ +class Client extends BaseClient +{ + /** + * Clear quota. + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function clearQuota() + { + $params = [ + 'appid' => $this->app['config']['app_id'], + ]; + + return $this->httpPostJson('cgi-bin/clear_quota', $params); + } + + /** + * Get wechat callback ip. + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function getValidIps() + { + return $this->httpGet('cgi-bin/getcallbackip'); + } + + /** + * Check the callback address network. + * + * @param string $action + * @param string $operator + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function checkCallbackUrl(string $action = 'all', string $operator = 'DEFAULT') + { + if (!in_array($action, ['dns', 'ping', 'all'], true)) { + throw new InvalidArgumentException('The action must be dns, ping, all.'); + } + + $operator = strtoupper($operator); + + if (!in_array($operator, ['CHINANET', 'UNICOM', 'CAP', 'DEFAULT'], true)) { + throw new InvalidArgumentException('The operator must be CHINANET, UNICOM, CAP, DEFAULT.'); + } + + $params = [ + 'action' => $action, + 'check_operator' => $operator, + ]; + + return $this->httpPostJson('cgi-bin/callback/check', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Base/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Base/ServiceProvider.php new file mode 100644 index 0000000..409593d --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Base/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Base; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author mingyoung + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['base'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Broadcasting/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/Broadcasting/Client.php new file mode 100644 index 0000000..6c2eee1 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Broadcasting/Client.php @@ -0,0 +1,383 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Broadcasting; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\Contracts\MessageInterface; +use EasyWeChat\Kernel\Exceptions\RuntimeException; +use EasyWeChat\Kernel\Messages\Card; +use EasyWeChat\Kernel\Messages\Image; +use EasyWeChat\Kernel\Messages\Media; +use EasyWeChat\Kernel\Messages\Text; +use EasyWeChat\Kernel\Support\Arr; + +/** + * Class Client. + * + * @method \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string previewTextByName($text, $name); + * @method \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string previewNewsByName($mediaId, $name); + * @method \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string previewVoiceByName($mediaId, $name); + * @method \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string previewImageByName($mediaId, $name); + * @method \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string previewVideoByName($message, $name); + * @method \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string previewCardByName($cardId, $name); + * + * @author overtrue + */ +class Client extends BaseClient +{ + const PREVIEW_BY_OPENID = 'touser'; + const PREVIEW_BY_NAME = 'towxname'; + + /** + * Send a message. + * + * @param array $message + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function send(array $message) + { + if (empty($message['filter']) && empty($message['touser'])) { + throw new RuntimeException('The message reception object is not specified'); + } + + $api = Arr::get($message, 'touser') ? 'cgi-bin/message/mass/send' : 'cgi-bin/message/mass/sendall'; + + return $this->httpPostJson($api, $message); + } + + /** + * Preview a message. + * + * @param array $message + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function preview(array $message) + { + return $this->httpPostJson('cgi-bin/message/mass/preview', $message); + } + + /** + * Delete a broadcast. + * + * @param string $msgId + * @param int $index + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function delete(string $msgId, int $index = 0) + { + $options = [ + 'msg_id' => $msgId, + 'article_idx' => $index, + ]; + + return $this->httpPostJson('cgi-bin/message/mass/delete', $options); + } + + /** + * Get a broadcast status. + * + * @param string $msgId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function status(string $msgId) + { + $options = [ + 'msg_id' => $msgId, + ]; + + return $this->httpPostJson('cgi-bin/message/mass/get', $options); + } + + /** + * Send a text message. + * + * @param string $message + * @param mixed $reception + * @param array $attributes + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + public function sendText(string $message, $reception = null, array $attributes = []) + { + return $this->sendMessage(new Text($message), $reception, $attributes); + } + + /** + * Send a news message. + * + * @param string $mediaId + * @param mixed $reception + * @param array $attributes + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + public function sendNews(string $mediaId, $reception = null, array $attributes = []) + { + return $this->sendMessage(new Media($mediaId, 'mpnews'), $reception, $attributes); + } + + /** + * Send a voice message. + * + * @param string $mediaId + * @param mixed $reception + * @param array $attributes + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + public function sendVoice(string $mediaId, $reception = null, array $attributes = []) + { + return $this->sendMessage(new Media($mediaId, 'voice'), $reception, $attributes); + } + + /** + * Send a image message. + * + * @param string $mediaId + * @param mixed $reception + * @param array $attributes + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + public function sendImage(string $mediaId, $reception = null, array $attributes = []) + { + return $this->sendMessage(new Image($mediaId), $reception, $attributes); + } + + /** + * Send a video message. + * + * @param string $mediaId + * @param mixed $reception + * @param array $attributes + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + public function sendVideo(string $mediaId, $reception = null, array $attributes = []) + { + return $this->sendMessage(new Media($mediaId, 'mpvideo'), $reception, $attributes); + } + + /** + * Send a card message. + * + * @param string $cardId + * @param mixed $reception + * @param array $attributes + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + public function sendCard(string $cardId, $reception = null, array $attributes = []) + { + return $this->sendMessage(new Card($cardId), $reception, $attributes); + } + + /** + * Preview a text message. + * + * @param string $message message + * @param string $reception + * @param string $method + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function previewText(string $message, $reception, $method = self::PREVIEW_BY_OPENID) + { + return $this->previewMessage(new Text($message), $reception, $method); + } + + /** + * Preview a news message. + * + * @param string $mediaId message + * @param string $reception + * @param string $method + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function previewNews(string $mediaId, $reception, $method = self::PREVIEW_BY_OPENID) + { + return $this->previewMessage(new Media($mediaId, 'mpnews'), $reception, $method); + } + + /** + * Preview a voice message. + * + * @param string $mediaId message + * @param string $reception + * @param string $method + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function previewVoice(string $mediaId, $reception, $method = self::PREVIEW_BY_OPENID) + { + return $this->previewMessage(new Media($mediaId, 'voice'), $reception, $method); + } + + /** + * Preview a image message. + * + * @param string $mediaId message + * @param string $reception + * @param string $method + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function previewImage(string $mediaId, $reception, $method = self::PREVIEW_BY_OPENID) + { + return $this->previewMessage(new Image($mediaId), $reception, $method); + } + + /** + * Preview a video message. + * + * @param string $mediaId message + * @param string $reception + * @param string $method + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function previewVideo(string $mediaId, $reception, $method = self::PREVIEW_BY_OPENID) + { + return $this->previewMessage(new Media($mediaId, 'mpvideo'), $reception, $method); + } + + /** + * Preview a card message. + * + * @param string $cardId message + * @param string $reception + * @param string $method + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function previewCard(string $cardId, $reception, $method = self::PREVIEW_BY_OPENID) + { + return $this->previewMessage(new Card($cardId), $reception, $method); + } + + /** + * @param \EasyWeChat\Kernel\Contracts\MessageInterface $message + * @param string $reception + * @param string $method + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function previewMessage(MessageInterface $message, string $reception, $method = self::PREVIEW_BY_OPENID) + { + $message = (new MessageBuilder())->message($message)->buildForPreview($method, $reception); + + return $this->preview($message); + } + + /** + * @param \EasyWeChat\Kernel\Contracts\MessageInterface $message + * @param mixed $reception + * @param array $attributes + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + public function sendMessage(MessageInterface $message, $reception = null, $attributes = []) + { + $message = (new MessageBuilder())->message($message)->with($attributes)->toAll(); + + if (\is_int($reception)) { + $message->toTag($reception); + } elseif (\is_array($reception)) { + $message->toUsers($reception); + } + + return $this->send($message->build()); + } + + /** + * @codeCoverageIgnore + * + * @param string $method + * @param array $args + * + * @return mixed + */ + public function __call($method, $args) + { + if (strpos($method, 'ByName') > 0) { + $method = strstr($method, 'ByName', true); + + if (method_exists($this, $method)) { + array_push($args, self::PREVIEW_BY_NAME); + + return $this->$method(...$args); + } + } + + throw new \BadMethodCallException(sprintf('Method %s not exists.', $method)); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Broadcasting/MessageBuilder.php b/vendor/overtrue/wechat/src/OfficialAccount/Broadcasting/MessageBuilder.php new file mode 100644 index 0000000..70465b0 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Broadcasting/MessageBuilder.php @@ -0,0 +1,162 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Broadcasting; + +use EasyWeChat\Kernel\Contracts\MessageInterface; +use EasyWeChat\Kernel\Exceptions\RuntimeException; + +/** + * Class MessageBuilder. + * + * @author overtrue + */ +class MessageBuilder +{ + /** + * @var array + */ + protected $to = []; + + /** + * @var \EasyWeChat\Kernel\Contracts\MessageInterface + */ + protected $message; + + /** + * @var array + */ + protected $attributes = []; + + /** + * Set message. + * + * @param \EasyWeChat\Kernel\Contracts\MessageInterface $message + * + * @return $this + */ + public function message(MessageInterface $message) + { + $this->message = $message; + + return $this; + } + + /** + * Set target user or group. + * + * @param array $to + * + * @return $this + */ + public function to(array $to) + { + $this->to = $to; + + return $this; + } + + /** + * @param int $tagId + * + * @return \EasyWeChat\OfficialAccount\Broadcasting\MessageBuilder + */ + public function toTag(int $tagId) + { + $this->to([ + 'filter' => [ + 'is_to_all' => false, + 'tag_id' => $tagId, + ], + ]); + + return $this; + } + + /** + * @param array $openids + * + * @return \EasyWeChat\OfficialAccount\Broadcasting\MessageBuilder + */ + public function toUsers(array $openids) + { + $this->to([ + 'touser' => $openids, + ]); + + return $this; + } + + /** + * @return $this + */ + public function toAll() + { + $this->to([ + 'filter' => ['is_to_all' => true], + ]); + + return $this; + } + + /** + * @param array $attributes + * + * @return \EasyWeChat\OfficialAccount\Broadcasting\MessageBuilder + */ + public function with(array $attributes) + { + $this->attributes = $attributes; + + return $this; + } + + /** + * Build message. + * + * @param array $prepends + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + public function build(array $prepends = []): array + { + if (empty($this->message)) { + throw new RuntimeException('No message content to send.'); + } + + $content = $this->message->transformForJsonRequest(); + + if (empty($prepends)) { + $prepends = $this->to; + } + + $message = array_merge($prepends, $content, $this->attributes); + + return $message; + } + + /** + * Build preview message. + * + * @param string $by + * @param string $user + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + public function buildForPreview(string $by, string $user): array + { + return $this->build([$by => $user]); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Broadcasting/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Broadcasting/ServiceProvider.php new file mode 100644 index 0000000..1f956cf --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Broadcasting/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Broadcasting; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['broadcasting'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/BoardingPassClient.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/BoardingPassClient.php new file mode 100644 index 0000000..b1575ab --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/BoardingPassClient.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Card; + +/** + * Class BoardingPassClient. + * + * @author overtrue + */ +class BoardingPassClient extends Client +{ + /** + * @param array $params + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function checkin(array $params) + { + return $this->httpPostJson('card/boardingpass/checkin', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/Card.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/Card.php new file mode 100644 index 0000000..14e817b --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/Card.php @@ -0,0 +1,52 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Card; + +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; + +/** + * Class Card. + * + * @author overtrue + * + * @property \EasyWeChat\OfficialAccount\Card\CodeClient $code + * @property \EasyWeChat\OfficialAccount\Card\MeetingTicketClient $meeting_ticket + * @property \EasyWeChat\OfficialAccount\Card\MemberCardClient $member_card + * @property \EasyWeChat\OfficialAccount\Card\GeneralCardClient $general_card + * @property \EasyWeChat\OfficialAccount\Card\MovieTicketClient $movie_ticket + * @property \EasyWeChat\OfficialAccount\Card\CoinClient $coin + * @property \EasyWeChat\OfficialAccount\Card\SubMerchantClient $sub_merchant + * @property \EasyWeChat\OfficialAccount\Card\BoardingPassClient $boarding_pass + * @property \EasyWeChat\OfficialAccount\Card\JssdkClient $jssdk + * @property \EasyWeChat\OfficialAccount\Card\GiftCardClient $gift_card + * @property \EasyWeChat\OfficialAccount\Card\GiftCardOrderClient $gift_card_order + * @property \EasyWeChat\OfficialAccount\Card\GiftCardPageClient $gift_card_page + * @property \EasyWeChat\OfficialAccount\Card\InvoiceClient $invoice + */ +class Card extends Client +{ + /** + * @param string $property + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + public function __get($property) + { + if (isset($this->app["card.{$property}"])) { + return $this->app["card.{$property}"]; + } + + throw new InvalidArgumentException(sprintf('No card service named "%s".', $property)); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/Client.php new file mode 100644 index 0000000..5737d52 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/Client.php @@ -0,0 +1,428 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Card; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\Traits\InteractsWithCache; + +/** + * Class Client. + * + * @author overtrue + */ +class Client extends BaseClient +{ + use InteractsWithCache; + + /** + * @var string + */ + protected $url; + + /** + * Ticket cache key. + * + * @var string + */ + protected $ticketCacheKey; + + /** + * Ticket cache prefix. + * + * @var string + */ + protected $ticketCachePrefix = 'easywechat.official_account.card.api_ticket.'; + + /** + * 获取卡券颜色. + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function colors() + { + return $this->httpGet('card/getcolors'); + } + + /** + * 卡券开放类目查询接口. + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function categories() + { + return $this->httpGet('card/getapplyprotocol'); + } + + /** + * 创建卡券. + * + * @param string $cardType + * @param array $attributes + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function create($cardType = 'member_card', array $attributes) + { + $params = [ + 'card' => [ + 'card_type' => strtoupper($cardType), + strtolower($cardType) => $attributes, + ], + ]; + + return $this->httpPostJson('card/create', $params); + } + + /** + * 查看卡券详情. + * + * @param string $cardId + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function get($cardId) + { + $params = [ + 'card_id' => $cardId, + ]; + + return $this->httpPostJson('card/get', $params); + } + + /** + * 批量查询卡列表. + * + * @param int $offset + * @param int $count + * @param string $statusList + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function list($offset = 0, $count = 10, $statusList = 'CARD_STATUS_VERIFY_OK') + { + $params = [ + 'offset' => $offset, + 'count' => $count, + 'status_list' => $statusList, + ]; + + return $this->httpPostJson('card/batchget', $params); + } + + /** + * 更改卡券信息接口 and 设置跟随推荐接口. + * + * @param string $cardId + * @param string $type + * @param array $attributes + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function update($cardId, $type, array $attributes = []) + { + $card = []; + $card['card_id'] = $cardId; + $card[strtolower($type)] = $attributes; + + return $this->httpPostJson('card/update', $card); + } + + /** + * 删除卡券接口. + * + * @param string $cardId + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function delete($cardId) + { + $params = [ + 'card_id' => $cardId, + ]; + + return $this->httpPostJson('card/delete', $params); + } + + /** + * 创建二维码. + * + * @param array $cards + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function createQrCode(array $cards) + { + return $this->httpPostJson('card/qrcode/create', $cards); + } + + /** + * ticket 换取二维码图片. + * + * @param string $ticket + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getQrCode($ticket) + { + $baseUri = 'https://mp.weixin.qq.com/cgi-bin/showqrcode'; + $params = [ + 'ticket' => $ticket, + ]; + + $response = $this->requestRaw($baseUri, 'GET', $params); + + return [ + 'status' => $response->getStatusCode(), + 'reason' => $response->getReasonPhrase(), + 'headers' => $response->getHeaders(), + 'body' => strval($response->getBody()), + 'url' => $baseUri.'?'.http_build_query($params), + ]; + } + + /** + * 通过ticket换取二维码 链接. + * + * @param string $ticket + * + * @return string + */ + public function getQrCodeUrl($ticket) + { + return sprintf('https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=%s', $ticket); + } + + /** + * 创建货架接口. + * + * @param string $banner + * @param string $pageTitle + * @param bool $canShare + * @param string $scene [SCENE_NEAR_BY 附近,SCENE_MENU 自定义菜单,SCENE_QRCODE 二维码,SCENE_ARTICLE 公众号文章, + * SCENE_H5 h5页面,SCENE_IVR 自动回复,SCENE_CARD_CUSTOM_CELL 卡券自定义cell] + * @param array $cardList + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function createLandingPage($banner, $pageTitle, $canShare, $scene, $cardList) + { + $params = [ + 'banner' => $banner, + 'page_title' => $pageTitle, + 'can_share' => $canShare, + 'scene' => $scene, + 'card_list' => $cardList, + ]; + + return $this->httpPostJson('card/landingpage/create', $params); + } + + /** + * 图文消息群发卡券. + * + * @param string $cardId + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getHtml($cardId) + { + $params = [ + 'card_id' => $cardId, + ]; + + return $this->httpPostJson('card/mpnews/gethtml', $params); + } + + /** + * 设置测试白名单. + * + * @param array $openids + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function setTestWhitelist($openids) + { + $params = [ + 'openid' => $openids, + ]; + + return $this->httpPostJson('card/testwhitelist/set', $params); + } + + /** + * 设置测试白名单(by username). + * + * @param array $usernames + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function setTestWhitelistByName(array $usernames) + { + $params = [ + 'username' => $usernames, + ]; + + return $this->httpPostJson('card/testwhitelist/set', $params); + } + + /** + * 获取用户已领取卡券接口. + * + * @param string $openid + * @param string $cardId + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getUserCards($openid, $cardId = '') + { + $params = [ + 'openid' => $openid, + 'card_id' => $cardId, + ]; + + return $this->httpPostJson('card/user/getcardlist', $params); + } + + /** + * 设置微信买单接口. + * 设置买单的 card_id 必须已经配置了门店,否则会报错. + * + * @param string $cardId + * @param bool $isOpen + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function setPayCell($cardId, $isOpen = true) + { + $params = [ + 'card_id' => $cardId, + 'is_open' => $isOpen, + ]; + + return $this->httpPostJson('card/paycell/set', $params); + } + + /** + * 设置自助核销接口 + * 设置买单的 card_id 必须已经配置了门店,否则会报错. + * + * @param string $cardId + * @param bool $isOpen + * @param bool $verifyCod + * @param bool $remarkAmount + * + * @return mixed + */ + public function setPayConsumeCell($cardId, $isOpen = true, $verifyCod = false, $remarkAmount = false) + { + $params = [ + 'card_id' => $cardId, + 'is_open' => $isOpen, + 'need_verify_cod' => $verifyCod, + 'need_remark_amount' => $remarkAmount, + ]; + + return $this->httpPostJson('card/selfconsumecell/set', $params); + } + + /** + * 增加库存. + * + * @param string $cardId + * @param int $amount + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + */ + public function increaseStock($cardId, $amount) + { + return $this->updateStock($cardId, $amount, 'increase'); + } + + /** + * 减少库存. + * + * @param string $cardId + * @param int $amount + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + */ + public function reduceStock($cardId, $amount) + { + return $this->updateStock($cardId, $amount, 'reduce'); + } + + /** + * 修改库存接口. + * + * @param string $cardId + * @param int $amount + * @param string $action + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function updateStock($cardId, $amount, $action = 'increase') + { + $key = 'increase' === $action ? 'increase_stock_value' : 'reduce_stock_value'; + $params = [ + 'card_id' => $cardId, + $key => abs($amount), + ]; + + return $this->httpPostJson('card/modifystock', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/CodeClient.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/CodeClient.php new file mode 100644 index 0000000..dcf96da --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/CodeClient.php @@ -0,0 +1,193 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Card; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class CodeClient. + * + * @author overtrue + */ +class CodeClient extends BaseClient +{ + /** + * 导入code接口. + * + * @param string $cardId + * @param array $codes + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function deposit(string $cardId, array $codes) + { + $params = [ + 'card_id' => $cardId, + 'code' => $codes, + ]; + + return $this->httpPostJson('card/code/deposit', $params); + } + + /** + * 查询导入code数目. + * + * @param string $cardId + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getDepositedCount(string $cardId) + { + $params = [ + 'card_id' => $cardId, + ]; + + return $this->httpPostJson('card/code/getdepositcount', $params); + } + + /** + * 核查code接口. + * + * @param string $cardId + * @param array $codes + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function check(string $cardId, array $codes) + { + $params = [ + 'card_id' => $cardId, + 'code' => $codes, + ]; + + return $this->httpPostJson('card/code/checkcode', $params); + } + + /** + * 查询 Code 接口. + * + * @param string $code + * @param string $cardId + * @param bool $checkConsume + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function get(string $code, string $cardId = '', bool $checkConsume = true) + { + $params = [ + 'code' => $code, + 'check_consume' => $checkConsume, + 'card_id' => $cardId, + ]; + + return $this->httpPostJson('card/code/get', $params); + } + + /** + * 更改Code接口. + * + * @param string $code + * @param string $newCode + * @param string $cardId + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function update(string $code, string $newCode, string $cardId = '') + { + $params = [ + 'code' => $code, + 'new_code' => $newCode, + 'card_id' => $cardId, + ]; + + return $this->httpPostJson('card/code/update', $params); + } + + /** + * 设置卡券失效. + * + * @param string $code + * @param string $cardId + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function disable(string $code, string $cardId = '') + { + $params = [ + 'code' => $code, + 'card_id' => $cardId, + ]; + + return $this->httpPostJson('card/code/unavailable', $params); + } + + /** + * 核销 Code 接口. + * + * @param string $code + * @param string|null $cardId + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function consume(string $code, string $cardId = null) + { + $params = [ + 'code' => $code, + ]; + + if (!is_null($cardId)) { + $params['card_id'] = $cardId; + } + + return $this->httpPostJson('card/code/consume', $params); + } + + /** + * Code解码接口. + * + * @param string $encryptedCode + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function decrypt(string $encryptedCode) + { + $params = [ + 'encrypt_code' => $encryptedCode, + ]; + + return $this->httpPostJson('card/code/decrypt', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/CoinClient.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/CoinClient.php new file mode 100644 index 0000000..b99d841 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/CoinClient.php @@ -0,0 +1,119 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Card; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class CoinClient. + * + * @author overtrue + */ +class CoinClient extends BaseClient +{ + /** + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function activate() + { + return $this->httpGet('card/pay/activate'); + } + + /** + * @param string $cardId + * @param int $quantity + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getPrice(string $cardId, int $quantity) + { + return $this->httpPostJson('card/pay/getpayprice', [ + 'card_id' => $cardId, + 'quantity' => $quantity, + ]); + } + + /** + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function summary() + { + return $this->httpGet('card/pay/getcoinsinfo'); + } + + /** + * @param int $count + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function recharge(int $count) + { + return $this->httpPostJson('card/pay/recharge', [ + 'coin_count' => $count, + ]); + } + + /** + * @param string $orderId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function order(string $orderId) + { + return $this->httpPostJson('card/pay/getorder', ['order_id' => $orderId]); + } + + /** + * @param array $filters + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function orders(array $filters) + { + return $this->httpPostJson('card/pay/getorderlist', $filters); + } + + /** + * @param string $cardId + * @param string $orderId + * @param int $quantity + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function confirm(string $cardId, string $orderId, int $quantity) + { + return $this->httpPostJson('card/pay/confirm', [ + 'card_id' => $cardId, + 'order_id' => $orderId, + 'quantity' => $quantity, + ]); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/GeneralCardClient.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/GeneralCardClient.php new file mode 100644 index 0000000..1505981 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/GeneralCardClient.php @@ -0,0 +1,71 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Card; + +/** + * Class GeneralCardClient. + * + * @author overtrue + */ +class GeneralCardClient extends Client +{ + /** + * 通用卡接口激活. + * + * @param array $info + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function activate(array $info = []) + { + return $this->httpPostJson('card/generalcard/activate', $info); + } + + /** + * 通用卡撤销激活. + * + * @param string $cardId + * @param string $code + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function deactivate(string $cardId, string $code) + { + $params = [ + 'card_id' => $cardId, + 'code' => $code, + ]; + + return $this->httpPostJson('card/generalcard/unactivate', $params); + } + + /** + * 更新会员信息. + * + * @param array $params + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function updateUser(array $params = []) + { + return $this->httpPostJson('card/generalcard/updateuser', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/GiftCardClient.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/GiftCardClient.php new file mode 100644 index 0000000..d8779d0 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/GiftCardClient.php @@ -0,0 +1,74 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Card; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class GiftCardClient. + * + * @author overtrue + */ +class GiftCardClient extends BaseClient +{ + /** + * 申请微信支付礼品卡权限接口. + * + * @param string $subMchId + * + * @return mixed + */ + public function add(string $subMchId) + { + $params = [ + 'sub_mch_id' => $subMchId, + ]; + + return $this->httpPostJson('card/giftcard/pay/whitelist/add', $params); + } + + /** + * 绑定商户号到礼品卡小程序接口(商户号必须为公众号申请的商户号,否则报错). + * + * @param string $subMchId + * @param string $wxaAppid + * + * @return mixed + */ + public function bind(string $subMchId, string $wxaAppid) + { + $params = [ + 'sub_mch_id' => $subMchId, + 'wxa_appid' => $wxaAppid, + ]; + + return $this->httpPostJson('card/giftcard/pay/submch/bind', $params); + } + + /** + * 上传小程序代码. + * + * @param string $wxaAppid + * @param string $pageId + * + * @return mixed + */ + public function set(string $wxaAppid, string $pageId) + { + $params = [ + 'wxa_appid' => $wxaAppid, + 'page_id' => $pageId, + ]; + + return $this->httpPostJson('card/giftcard/wxa/set', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/GiftCardOrderClient.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/GiftCardOrderClient.php new file mode 100644 index 0000000..b4e1b15 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/GiftCardOrderClient.php @@ -0,0 +1,78 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Card; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class GiftCardOrderClient. + * + * @author overtrue + */ +class GiftCardOrderClient extends BaseClient +{ + /** + * 查询-单个礼品卡订单信息接口. + * + * @param string $orderId + * + * @return mixed + */ + public function get(string $orderId) + { + $params = [ + 'order_id' => $orderId, + ]; + + return $this->httpPostJson('card/giftcard/order/get', $params); + } + + /** + * 查询-批量查询礼品卡订单信息接口. + * + * @param int $beginTime + * @param int $endTime + * @param int $offset + * @param int $count + * @param string $sortType + * + * @return mixed + */ + public function list(int $beginTime, int $endTime, int $offset = 0, int $count = 10, string $sortType = 'ASC') + { + $params = [ + 'begin_time' => $beginTime, + 'end_time' => $endTime, + 'sort_type' => $sortType, + 'offset' => $offset, + 'count' => $count, + ]; + + return $this->httpPostJson('card/giftcard/order/batchget', $params); + } + + /** + * 退款接口. + * + * @param string $orderId + * + * @return mixed + */ + public function refund(string $orderId) + { + $params = [ + 'order_id' => $orderId, + ]; + + return $this->httpPostJson('card/giftcard/order/refund', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/GiftCardPageClient.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/GiftCardPageClient.php new file mode 100644 index 0000000..bf36109 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/GiftCardPageClient.php @@ -0,0 +1,102 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Card; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class GiftCardPageClient. + * + * @author overtrue + */ +class GiftCardPageClient extends BaseClient +{ + /** + * 创建-礼品卡货架接口. + * + * @param array $attributes + * + * @return mixed + */ + public function add(array $attributes) + { + $params = [ + 'page' => $attributes, + ]; + + return $this->httpPostJson('card/giftcard/page/add', $params); + } + + /** + * 查询-礼品卡货架信息接口. + * + * @param string $pageId + * + * @return mixed + */ + public function get(string $pageId) + { + $params = [ + 'page_id' => $pageId, + ]; + + return $this->httpPostJson('card/giftcard/page/get', $params); + } + + /** + * 修改-礼品卡货架信息接口. + * + * @param string $pageId + * @param string $bannerPicUrl + * @param array $themeList + * + * @return mixed + */ + public function update(string $pageId, string $bannerPicUrl, array $themeList) + { + $params = [ + 'page' => [ + 'page_id' => $pageId, + 'banner_pic_url' => $bannerPicUrl, + 'theme_list' => $themeList, + ], + ]; + + return $this->httpPostJson('card/giftcard/page/update', $params); + } + + /** + * 查询-礼品卡货架列表接口. + * + * @return mixed + */ + public function list() + { + return $this->httpPostJson('card/giftcard/page/batchget'); + } + + /** + * 下架-礼品卡货架接口(下架某一个货架或者全部货架). + * + * @param string $pageId + * + * @return mixed + */ + public function setMaintain(string $pageId = '') + { + $params = ($pageId ? ['page_id' => $pageId] : ['all' => true]) + [ + 'maintain' => true, + ]; + + return $this->httpPostJson('card/giftcard/maintain/set', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/InvoiceClient.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/InvoiceClient.php new file mode 100644 index 0000000..127b3af --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/InvoiceClient.php @@ -0,0 +1,113 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Card; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class InvoiceClient. + * + * @author overtrue + */ +class InvoiceClient extends BaseClient +{ + /** + * 设置支付后开票信息接口. + * + * @param string $mchid + * @param string $sPappid + * + * @return mixed + */ + public function set(string $mchid, string $sPappid) + { + $params = [ + 'paymch_info' => [ + 'mchid' => $mchid, + 's_pappid' => $sPappid, + ], + ]; + + return $this->setBizAttr('set_pay_mch', $params); + } + + /** + * 查询支付后开票信息接口. + * + * @return mixed + */ + public function get() + { + return $this->setBizAttr('get_pay_mch'); + } + + /** + * 设置授权页字段信息接口. + * + * @param array $userData + * @param array $bizData + * + * @return mixed + */ + public function setAuthField(array $userData, array $bizData) + { + $params = [ + 'auth_field' => [ + 'user_field' => $userData, + 'biz_field' => $bizData, + ], + ]; + + return $this->setBizAttr('set_auth_field', $params); + } + + /** + * 查询授权页字段信息接口. + * + * @return mixed + */ + public function getAuthField() + { + return $this->setBizAttr('get_auth_field'); + } + + /** + * 查询开票信息. + * + * @param string $orderId + * @param string $appId + * + * @return mixed + */ + public function getAuthData(string $appId, string $orderId) + { + $params = [ + 'order_id' => $orderId, + 's_appid' => $appId, + ]; + + return $this->httpPost('card/invoice/getauthdata', $params); + } + + /** + * @param string $action + * @param array $params + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + private function setBizAttr(string $action, array $params = []) + { + return $this->httpPostJson('card/invoice/setbizattr', $params, ['action' => $action]); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/JssdkClient.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/JssdkClient.php new file mode 100644 index 0000000..9a6bb51 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/JssdkClient.php @@ -0,0 +1,85 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Card; + +use EasyWeChat\BasicService\Jssdk\Client as Jssdk; +use EasyWeChat\Kernel\Support\Arr; +use function EasyWeChat\Kernel\Support\str_random; + +/** + * Class Jssdk. + * + * @author overtrue + */ +class JssdkClient extends Jssdk +{ + /** + * @param bool $refresh + * @param string $type + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + * @throws \Psr\SimpleCache\InvalidArgumentException + */ + public function getTicket(bool $refresh = false, string $type = 'wx_card'): array + { + return parent::getTicket($refresh, $type); + } + + /** + * 微信卡券:JSAPI 卡券发放. + * + * @param array $cards + * + * @return string + */ + public function assign(array $cards) + { + return json_encode(array_map(function ($card) { + return $this->attachExtension($card['card_id'], $card); + }, $cards)); + } + + /** + * 生成 js添加到卡包 需要的 card_list 项. + * + * @param string $cardId + * @param array $extension + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + * @throws \Psr\SimpleCache\InvalidArgumentException + */ + public function attachExtension($cardId, array $extension = []) + { + $timestamp = time(); + $nonce = str_random(6); + $ticket = $this->getTicket()['ticket']; + + $ext = array_merge(['timestamp' => $timestamp, 'nonce_str' => $nonce], Arr::only( + $extension, + ['code', 'openid', 'outer_id', 'balance', 'fixed_begintimestamp', 'outer_str'] + )); + + $ext['signature'] = $this->dictionaryOrderSignature($ticket, $timestamp, $cardId, $ext['code'] ?? '', $ext['openid'] ?? '', $nonce); + + return [ + 'cardId' => $cardId, + 'cardExt' => json_encode($ext), + ]; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/MeetingTicketClient.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/MeetingTicketClient.php new file mode 100644 index 0000000..892c5ad --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/MeetingTicketClient.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Card; + +/** + * Class MeetingTicketClient. + * + * @author overtrue + */ +class MeetingTicketClient extends Client +{ + /** + * @param array $params + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function updateUser(array $params) + { + return $this->httpPostJson('card/meetingticket/updateuser', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/MemberCardClient.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/MemberCardClient.php new file mode 100644 index 0000000..41d051c --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/MemberCardClient.php @@ -0,0 +1,123 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Card; + +/** + * Class MemberCardClient. + * + * @author overtrue + */ +class MemberCardClient extends Client +{ + /** + * 会员卡接口激活. + * + * @param array $info + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function activate(array $info = []) + { + return $this->httpPostJson('card/membercard/activate', $info); + } + + /** + * 设置开卡字段接口. + * + * @param string $cardId + * @param array $settings + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function setActivationForm(string $cardId, array $settings) + { + $params = array_merge(['card_id' => $cardId], $settings); + + return $this->httpPostJson('card/membercard/activateuserform/set', $params); + } + + /** + * 拉取会员信息接口. + * + * @param string $cardId + * @param string $code + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getUser(string $cardId, string $code) + { + $params = [ + 'card_id' => $cardId, + 'code' => $code, + ]; + + return $this->httpPostJson('card/membercard/userinfo/get', $params); + } + + /** + * 更新会员信息. + * + * @param array $params + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function updateUser(array $params = []) + { + return $this->httpPostJson('card/membercard/updateuser', $params); + } + + /** + * 获取用户提交资料. + * + * @param string $activateTicket + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getActivationForm($activateTicket) + { + $params = [ + 'activate_ticket' => $activateTicket, + ]; + + return $this->httpPostJson('card/membercard/activatetempinfo/get', $params); + } + + /** + * 获取开卡组件链接接口. + * + * @param array $params 包含会员卡ID和随机字符串 + * + * @return string 开卡组件链接 + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getActivateUrl(array $params = []) + { + return $this->httpPostJson('card/membercard/activate/geturl', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/MovieTicketClient.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/MovieTicketClient.php new file mode 100644 index 0000000..e6d9036 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/MovieTicketClient.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Card; + +/** + * Class MovieTicketClient. + * + * @author overtrue + */ +class MovieTicketClient extends Client +{ + /** + * @param array $params + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function updateUser(array $params) + { + return $this->httpPostJson('card/movieticket/updateuser', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/ServiceProvider.php new file mode 100644 index 0000000..3807fa3 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/ServiceProvider.php @@ -0,0 +1,89 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Card; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['card'] = function ($app) { + return new Card($app); + }; + + $app['card.client'] = function ($app) { + return new Client($app); + }; + + $app['card.coin'] = function ($app) { + return new CoinClient($app); + }; + + $app['card.sub_merchant'] = function ($app) { + return new SubMerchantClient($app); + }; + + $app['card.code'] = function ($app) { + return new CodeClient($app); + }; + + $app['card.movie_ticket'] = function ($app) { + return new MovieTicketClient($app); + }; + + $app['card.member_card'] = function ($app) { + return new MemberCardClient($app); + }; + + $app['card.general_card'] = function ($app) { + return new GeneralCardClient($app); + }; + + $app['card.boarding_pass'] = function ($app) { + return new BoardingPassClient($app); + }; + + $app['card.meeting_ticket'] = function ($app) { + return new MeetingTicketClient($app); + }; + + $app['card.jssdk'] = function ($app) { + return new JssdkClient($app); + }; + + $app['card.gift_card'] = function ($app) { + return new GiftCardClient($app); + }; + + $app['card.gift_card_order'] = function ($app) { + return new GiftCardOrderClient($app); + }; + + $app['card.gift_card_page'] = function ($app) { + return new GiftCardPageClient($app); + }; + + $app['card.invoice'] = function ($app) { + return new InvoiceClient($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/SubMerchantClient.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/SubMerchantClient.php new file mode 100644 index 0000000..f058492 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/SubMerchantClient.php @@ -0,0 +1,121 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Card; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\Support\Arr; + +/** + * Class SubMerchantClient. + * + * @author overtrue + */ +class SubMerchantClient extends BaseClient +{ + /** + * 添加子商户. + * + * @param array $info + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function create(array $info = []) + { + $params = [ + 'info' => Arr::only($info, [ + 'brand_name', + 'logo_url', + 'protocol', + 'end_time', + 'primary_category_id', + 'secondary_category_id', + 'agreement_media_id', + 'operator_media_id', + 'app_id', + ]), + ]; + + return $this->httpPostJson('card/submerchant/submit', $params); + } + + /** + * 更新子商户. + * + * @param int $merchantId + * @param array $info + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function update(int $merchantId, array $info = []) + { + $params = [ + 'info' => array_merge(['merchant_id' => $merchantId], + Arr::only($info, [ + 'brand_name', + 'logo_url', + 'protocol', + 'end_time', + 'primary_category_id', + 'secondary_category_id', + 'agreement_media_id', + 'operator_media_id', + 'app_id', + ])), + ]; + + return $this->httpPostJson('card/submerchant/update', $params); + } + + /** + * 获取子商户信息. + * + * @param int $merchantId + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function get(int $merchantId) + { + return $this->httpPostJson('card/submerchant/get', ['merchant_id' => $merchantId]); + } + + /** + * 批量获取子商户信息. + * + * @param int $beginId + * @param int $limit + * @param string $status + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function list(int $beginId = 0, int $limit = 50, string $status = 'CHECKING') + { + $params = [ + 'begin_id' => $beginId, + 'limit' => $limit, + 'status' => $status, + ]; + + return $this->httpPostJson('card/submerchant/batchget', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Comment/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/Comment/Client.php new file mode 100644 index 0000000..4f45b74 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Comment/Client.php @@ -0,0 +1,208 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Comment; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author overtrue + */ +class Client extends BaseClient +{ + /** + * Open article comment. + * + * @param string $msgId + * @param int|null $index + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function open(string $msgId, int $index = null) + { + $params = [ + 'msg_data_id' => $msgId, + 'index' => $index, + ]; + + return $this->httpPostJson('cgi-bin/comment/open', $params); + } + + /** + * Close comment. + * + * @param string $msgId + * @param int|null $index + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function close(string $msgId, int $index = null) + { + $params = [ + 'msg_data_id' => $msgId, + 'index' => $index, + ]; + + return $this->httpPostJson('cgi-bin/comment/close', $params); + } + + /** + * Get article comments. + * + * @param string $msgId + * @param int $index + * @param int $begin + * @param int $count + * @param int $type + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function list(string $msgId, int $index, int $begin, int $count, int $type = 0) + { + $params = [ + 'msg_data_id' => $msgId, + 'index' => $index, + 'begin' => $begin, + 'count' => $count, + 'type' => $type, + ]; + + return $this->httpPostJson('cgi-bin/comment/list', $params); + } + + /** + * Mark elect comment. + * + * @param string $msgId + * @param int $index + * @param int $commentId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function markElect(string $msgId, int $index, int $commentId) + { + $params = [ + 'msg_data_id' => $msgId, + 'index' => $index, + 'user_comment_id' => $commentId, + ]; + + return $this->httpPostJson('cgi-bin/comment/markelect', $params); + } + + /** + * Unmark elect comment. + * + * @param string $msgId + * @param int $index + * @param int $commentId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function unmarkElect(string $msgId, int $index, int $commentId) + { + $params = [ + 'msg_data_id' => $msgId, + 'index' => $index, + 'user_comment_id' => $commentId, + ]; + + return $this->httpPostJson('cgi-bin/comment/unmarkelect', $params); + } + + /** + * Delete comment. + * + * @param string $msgId + * @param int $index + * @param int $commentId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function delete(string $msgId, int $index, int $commentId) + { + $params = [ + 'msg_data_id' => $msgId, + 'index' => $index, + 'user_comment_id' => $commentId, + ]; + + return $this->httpPostJson('cgi-bin/comment/delete', $params); + } + + /** + * Reply to a comment. + * + * @param string $msgId + * @param int $index + * @param int $commentId + * @param string $content + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function reply(string $msgId, int $index, int $commentId, string $content) + { + $params = [ + 'msg_data_id' => $msgId, + 'index' => $index, + 'user_comment_id' => $commentId, + 'content' => $content, + ]; + + return $this->httpPostJson('cgi-bin/comment/reply/add', $params); + } + + /** + * Delete a reply. + * + * @param string $msgId + * @param int $index + * @param int $commentId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function deleteReply(string $msgId, int $index, int $commentId) + { + $params = [ + 'msg_data_id' => $msgId, + 'index' => $index, + 'user_comment_id' => $commentId, + ]; + + return $this->httpPostJson('cgi-bin/comment/reply/delete', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Comment/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Comment/ServiceProvider.php new file mode 100644 index 0000000..8d6806c --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Comment/ServiceProvider.php @@ -0,0 +1,44 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * ServiceProvider.php. + * + * This file is part of the wechat. + * + * (c) overtrue + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Comment; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['comment'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/CustomerService/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/CustomerService/Client.php new file mode 100644 index 0000000..64e43e5 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/CustomerService/Client.php @@ -0,0 +1,230 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\CustomerService; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author overtrue + */ +class Client extends BaseClient +{ + /** + * List all staffs. + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function list() + { + return $this->httpGet('cgi-bin/customservice/getkflist'); + } + + /** + * List all online staffs. + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function online() + { + return $this->httpGet('cgi-bin/customservice/getonlinekflist'); + } + + /** + * Create a staff. + * + * @param string $account + * @param string $nickname + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function create(string $account, string $nickname) + { + $params = [ + 'kf_account' => $account, + 'nickname' => $nickname, + ]; + + return $this->httpPostJson('customservice/kfaccount/add', $params); + } + + /** + * Update a staff. + * + * @param string $account + * @param string $nickname + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function update(string $account, string $nickname) + { + $params = [ + 'kf_account' => $account, + 'nickname' => $nickname, + ]; + + return $this->httpPostJson('customservice/kfaccount/update', $params); + } + + /** + * Delete a staff. + * + * @param string $account + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function delete(string $account) + { + return $this->httpPostJson('customservice/kfaccount/del', [], ['kf_account' => $account]); + } + + /** + * Invite a staff. + * + * @param string $account + * @param string $wechatId + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function invite(string $account, string $wechatId) + { + $params = [ + 'kf_account' => $account, + 'invite_wx' => $wechatId, + ]; + + return $this->httpPostJson('customservice/kfaccount/inviteworker', $params); + } + + /** + * Set staff avatar. + * + * @param string $account + * @param string $path + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function setAvatar(string $account, string $path) + { + return $this->httpUpload('customservice/kfaccount/uploadheadimg', ['media' => $path], [], ['kf_account' => $account]); + } + + /** + * Get message builder. + * + * @param \EasyWeChat\Kernel\Messages\Message|string $message + * + * @return \EasyWeChat\OfficialAccount\CustomerService\Messenger + */ + public function message($message) + { + $messageBuilder = new Messenger($this); + + return $messageBuilder->message($message); + } + + /** + * Send a message. + * + * @param array $message + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function send(array $message) + { + return $this->httpPostJson('cgi-bin/message/custom/send', $message); + } + + /** + * Show typing status. + * + * @param string $openid + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function showTypingStatusToUser(string $openid) + { + return $this->httpPostJson('cgi-bin/message/custom/typing', [ + 'touser' => $openid, + 'command' => 'Typing', + ]); + } + + /** + * Hide typing status. + * + * @param string $openid + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function hideTypingStatusToUser(string $openid) + { + return $this->httpPostJson('cgi-bin/message/custom/typing', [ + 'touser' => $openid, + 'command' => 'CancelTyping', + ]); + } + + /** + * Get messages history. + * + * @param int $startTime + * @param int $endTime + * @param int $msgId + * @param int $number + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function messages($startTime, $endTime, int $msgId = 1, int $number = 10000) + { + $params = [ + 'starttime' => is_numeric($startTime) ? $startTime : strtotime($startTime), + 'endtime' => is_numeric($endTime) ? $endTime : strtotime($endTime), + 'msgid' => $msgId, + 'number' => $number, + ]; + + return $this->httpPostJson('customservice/msgrecord/getmsglist', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/CustomerService/Messenger.php b/vendor/overtrue/wechat/src/OfficialAccount/CustomerService/Messenger.php new file mode 100644 index 0000000..69bf2f3 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/CustomerService/Messenger.php @@ -0,0 +1,165 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\CustomerService; + +use EasyWeChat\Kernel\Exceptions\RuntimeException; +use EasyWeChat\Kernel\Messages\Message; +use EasyWeChat\Kernel\Messages\Raw as RawMessage; +use EasyWeChat\Kernel\Messages\Text; + +/** + * Class MessageBuilder. + * + * @author overtrue + */ +class Messenger +{ + /** + * Messages to send. + * + * @var \EasyWeChat\Kernel\Messages\Message; + */ + protected $message; + + /** + * Messages target user open id. + * + * @var string + */ + protected $to; + + /** + * Messages sender staff id. + * + * @var string + */ + protected $account; + + /** + * Customer service instance. + * + * @var \EasyWeChat\OfficialAccount\CustomerService\Client + */ + protected $client; + + /** + * MessageBuilder constructor. + * + * @param \EasyWeChat\OfficialAccount\CustomerService\Client $client + */ + public function __construct(Client $client) + { + $this->client = $client; + } + + /** + * Set message to send. + * + * @param string|Message $message + * + * @return Messenger + */ + public function message($message) + { + if (is_string($message)) { + $message = new Text($message); + } + + $this->message = $message; + + return $this; + } + + /** + * Set staff account to send message. + * + * @param string $account + * + * @return Messenger + */ + public function by(string $account) + { + $this->account = $account; + + return $this; + } + + /** + * @param string $account + * + * @return Messenger + */ + public function from(string $account) + { + return $this->by($account); + } + + /** + * Set target user open id. + * + * @param string $openid + * + * @return Messenger + */ + public function to($openid) + { + $this->to = $openid; + + return $this; + } + + /** + * Send the message. + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + public function send() + { + if (empty($this->message)) { + throw new RuntimeException('No message to send.'); + } + + if ($this->message instanceof RawMessage) { + $message = json_decode($this->message->get('content'), true); + } else { + $prepends = [ + 'touser' => $this->to, + ]; + if ($this->account) { + $prepends['customservice'] = ['kf_account' => $this->account]; + } + $message = $this->message->transformForJsonRequest($prepends); + } + + return $this->client->send($message); + } + + /** + * Return property. + * + * @param string $property + * + * @return mixed + */ + public function __get(string $property) + { + if (property_exists($this, $property)) { + return $this->$property; + } + + return null; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/CustomerService/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/CustomerService/ServiceProvider.php new file mode 100644 index 0000000..a879ce8 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/CustomerService/ServiceProvider.php @@ -0,0 +1,37 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\CustomerService; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['customer_service'] = function ($app) { + return new Client($app); + }; + + $app['customer_service_session'] = function ($app) { + return new SessionClient($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/CustomerService/SessionClient.php b/vendor/overtrue/wechat/src/OfficialAccount/CustomerService/SessionClient.php new file mode 100644 index 0000000..b92b6db --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/CustomerService/SessionClient.php @@ -0,0 +1,104 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\CustomerService; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class SessionClient. + * + * @author overtrue + */ +class SessionClient extends BaseClient +{ + /** + * List all sessions of $account. + * + * @param string $account + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function list(string $account) + { + return $this->httpGet('customservice/kfsession/getsessionlist', ['kf_account' => $account]); + } + + /** + * List all the people waiting. + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function waiting() + { + return $this->httpGet('customservice/kfsession/getwaitcase'); + } + + /** + * Create a session. + * + * @param string $account + * @param string $openid + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function create(string $account, string $openid) + { + $params = [ + 'kf_account' => $account, + 'openid' => $openid, + ]; + + return $this->httpPostJson('customservice/kfsession/create', $params); + } + + /** + * Close a session. + * + * @param string $account + * @param string $openid + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function close(string $account, string $openid) + { + $params = [ + 'kf_account' => $account, + 'openid' => $openid, + ]; + + return $this->httpPostJson('customservice/kfsession/close', $params); + } + + /** + * Get a session. + * + * @param string $openid + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function get(string $openid) + { + return $this->httpGet('customservice/kfsession/getsession', ['openid' => $openid]); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/DataCube/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/DataCube/Client.php new file mode 100644 index 0000000..0bf1c21 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/DataCube/Client.php @@ -0,0 +1,340 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\DataCube; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author overtrue + */ +class Client extends BaseClient +{ + /** + * 获取用户增减数据. + * + * @param string $from + * @param string $to + * + * @return mixed + */ + public function userSummary(string $from, string $to) + { + return $this->query('datacube/getusersummary', $from, $to); + } + + /** + * 获取累计用户数据. + * + * @param string $from + * @param string $to + * + * @return mixed + */ + public function userCumulate(string $from, string $to) + { + return $this->query('datacube/getusercumulate', $from, $to); + } + + /** + * 获取图文群发每日数据. + * + * @param string $from + * @param string $to + * + * @return mixed + */ + public function articleSummary(string $from, string $to) + { + return $this->query('datacube/getarticlesummary', $from, $to); + } + + /** + * 获取图文群发总数据. + * + * @param string $from + * @param string $to + * + * @return mixed + */ + public function articleTotal(string $from, string $to) + { + return $this->query('datacube/getarticletotal', $from, $to); + } + + /** + * 获取图文统计数据. + * + * @param string $from + * @param string $to + * + * @return mixed + */ + public function userReadSummary(string $from, string $to) + { + return $this->query('datacube/getuserread', $from, $to); + } + + /** + * 获取图文统计分时数据. + * + * @param string $from + * @param string $to + * + * @return mixed + */ + public function userReadHourly(string $from, string $to) + { + return $this->query('datacube/getuserreadhour', $from, $to); + } + + /** + * 获取图文分享转发数据. + * + * @param string $from + * @param string $to + * + * @return mixed + */ + public function userShareSummary(string $from, string $to) + { + return $this->query('datacube/getusershare', $from, $to); + } + + /** + * 获取图文分享转发分时数据. + * + * @param string $from + * @param string $to + * + * @return mixed + */ + public function userShareHourly(string $from, string $to) + { + return $this->query('datacube/getusersharehour', $from, $to); + } + + /** + * 获取消息发送概况数据. + * + * @param string $from + * @param string $to + * + * @return mixed + */ + public function upstreamMessageSummary(string $from, string $to) + { + return $this->query('datacube/getupstreammsg', $from, $to); + } + + /** + * 获取消息分送分时数据. + * + * @param string $from + * @param string $to + * + * @return mixed + */ + public function upstreamMessageHourly(string $from, string $to) + { + return $this->query('datacube/getupstreammsghour', $from, $to); + } + + /** + * 获取消息发送周数据. + * + * @param string $from + * @param string $to + * + * @return mixed + */ + public function upstreamMessageWeekly(string $from, string $to) + { + return $this->query('datacube/getupstreammsgweek', $from, $to); + } + + /** + * 获取消息发送月数据. + * + * @param string $from + * @param string $to + * + * @return mixed + */ + public function upstreamMessageMonthly(string $from, string $to) + { + return $this->query('datacube/getupstreammsgmonth', $from, $to); + } + + /** + * 获取消息发送分布数据. + * + * @param string $from + * @param string $to + * + * @return mixed + */ + public function upstreamMessageDistSummary(string $from, string $to) + { + return $this->query('datacube/getupstreammsgdist', $from, $to); + } + + /** + * 获取消息发送分布周数据. + * + * @param string $from + * @param string $to + * + * @return mixed + */ + public function upstreamMessageDistWeekly(string $from, string $to) + { + return $this->query('datacube/getupstreammsgdistweek', $from, $to); + } + + /** + * 获取消息发送分布月数据. + * + * @param string $from + * @param string $to + * + * @return mixed + */ + public function upstreamMessageDistMonthly(string $from, string $to) + { + return $this->query('datacube/getupstreammsgdistmonth', $from, $to); + } + + /** + * 获取接口分析数据. + * + * @param string $from + * @param string $to + * + * @return mixed + */ + public function interfaceSummary(string $from, string $to) + { + return $this->query('datacube/getinterfacesummary', $from, $to); + } + + /** + * 获取接口分析分时数据. + * + * @param string $from + * @param string $to + * + * @return mixed + */ + public function interfaceSummaryHourly(string $from, string $to) + { + return $this->query('datacube/getinterfacesummaryhour', $from, $to); + } + + /** + * 拉取卡券概况数据接口. + * + * @param string $from + * @param string $to + * @param int $condSource + * + * @return mixed + */ + public function cardSummary(string $from, string $to, $condSource = 0) + { + $ext = [ + 'cond_source' => intval($condSource), + ]; + + return $this->query('datacube/getcardbizuininfo', $from, $to, $ext); + } + + /** + * 获取免费券数据接口. + * + * @param string $from + * @param string $to + * @param int $condSource + * @param string $cardId + * + * @return mixed + */ + public function freeCardSummary(string $from, string $to, int $condSource = 0, string $cardId = '') + { + $ext = [ + 'cond_source' => intval($condSource), + 'card_id' => $cardId, + ]; + + return $this->query('datacube/getcardcardinfo', $from, $to, $ext); + } + + /** + * 拉取会员卡数据接口. + * + * @param string $from + * @param string $to + * @param int $condSource + * + * @return mixed + */ + public function memberCardSummary(string $from, string $to, $condSource = 0) + { + $ext = [ + 'cond_source' => intval($condSource), + ]; + + return $this->query('datacube/getcardmembercardinfo', $from, $to, $ext); + } + + /** + * 拉取单张会员卡数据接口. + * + * @param string $from + * @param string $to + * @param string $cardId + * + * @return mixed + */ + public function memberCardSummaryById(string $from, string $to, string $cardId) + { + $ext = [ + 'card_id' => $cardId, + ]; + + return $this->query('datacube/getcardmembercarddetail', $from, $to, $ext); + } + + /** + * 查询数据. + * + * @param string $api + * @param string $from + * @param string $to + * @param array $ext + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function query(string $api, string $from, string $to, array $ext = []) + { + $params = array_merge([ + 'begin_date' => $from, + 'end_date' => $to, + ], $ext); + + return $this->httpPostJson($api, $params); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/DataCube/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/DataCube/ServiceProvider.php new file mode 100644 index 0000000..bfec89a --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/DataCube/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\DataCube; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['data_cube'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Device/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/Device/Client.php new file mode 100644 index 0000000..8dc9d73 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Device/Client.php @@ -0,0 +1,251 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Device; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @see http://iot.weixin.qq.com/wiki/new/index.html + * + * @author soone <66812590@qq.com> + */ +class Client extends BaseClient +{ + /** + * @param string $deviceId + * @param string $openid + * @param string $content + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function message(string $deviceId, string $openid, string $content) + { + $params = [ + 'device_type' => $this->app['config']['device_type'], + 'device_id' => $deviceId, + 'open_id' => $openid, + 'content' => base64_encode($content), + ]; + + return $this->httpPostJson('device/transmsg', $params); + } + + /** + * Get device qrcode. + * + * @param array $deviceIds + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function qrCode(array $deviceIds) + { + $params = [ + 'device_num' => count($deviceIds), + 'device_id_list' => $deviceIds, + ]; + + return $this->httpPostJson('device/create_qrcode', $params); + } + + /** + * @param array $devices + * @param string $productId + * @param int $opType + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function authorize(array $devices, string $productId, int $opType = 0) + { + $params = [ + 'device_num' => count($devices), + 'device_list' => $devices, + 'op_type' => $opType, + 'product_id' => $productId, + ]; + + return $this->httpPostJson('device/authorize_device', $params); + } + + /** + * 获取 device id 和二维码 + * + * @param string $productId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function createId(string $productId) + { + $params = [ + 'product_id' => $productId, + ]; + + return $this->httpGet('device/getqrcode', $params); + } + + /** + * @param string $openid + * @param string $deviceId + * @param string $ticket + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function bind(string $openid, string $deviceId, string $ticket) + { + $params = [ + 'ticket' => $ticket, + 'device_id' => $deviceId, + 'openid' => $openid, + ]; + + return $this->httpPostJson('device/bind', $params); + } + + /** + * @param string $openid + * @param string $deviceId + * @param string $ticket + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function unbind(string $openid, string $deviceId, string $ticket) + { + $params = [ + 'ticket' => $ticket, + 'device_id' => $deviceId, + 'openid' => $openid, + ]; + + return $this->httpPostJson('device/unbind', $params); + } + + /** + * @param string $openid + * @param string $deviceId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function forceBind(string $openid, string $deviceId) + { + $params = [ + 'device_id' => $deviceId, + 'openid' => $openid, + ]; + + return $this->httpPostJson('device/compel_bind', $params); + } + + /** + * @param string $openid + * @param string $deviceId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function forceUnbind(string $openid, string $deviceId) + { + $params = [ + 'device_id' => $deviceId, + 'openid' => $openid, + ]; + + return $this->httpPostJson('device/compel_unbind', $params); + } + + /** + * @param string $deviceId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function status(string $deviceId) + { + $params = [ + 'device_id' => $deviceId, + ]; + + return $this->httpGet('device/get_stat', $params); + } + + /** + * @param string $ticket + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function verify(string $ticket) + { + $params = [ + 'ticket' => $ticket, + ]; + + return $this->httpPost('device/verify_qrcode', $params); + } + + /** + * @param string $deviceId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function openid(string $deviceId) + { + $params = [ + 'device_type' => $this->app['config']['device_type'], + 'device_id' => $deviceId, + ]; + + return $this->httpGet('device/get_openid', $params); + } + + /** + * @param string $openid + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function listByOpenid(string $openid) + { + $params = [ + 'openid' => $openid, + ]; + + return $this->httpGet('device/get_bind_device', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Device/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Device/ServiceProvider.php new file mode 100644 index 0000000..e3dce8e --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Device/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Device; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author soone <66812590@qq.com + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['device'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Goods/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/Goods/Client.php new file mode 100644 index 0000000..c720e31 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Goods/Client.php @@ -0,0 +1,113 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Goods; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author her-cat + */ +class Client extends BaseClient +{ + /** + * Add the goods. + * + * @param array $data + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function add(array $data) + { + return $this->httpPostJson('scan/product/v2/add', [ + 'product' => $data, + ]); + } + + /** + * Update the goods. + * + * @param array $data + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function update(array $data) + { + return $this->httpPostJson('scan/product/v2/add', [ + 'product' => $data, + ]); + } + + /** + * Get add or update goods results. + * + * @param string $ticket + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function status(string $ticket) + { + return $this->httpPostJson('scan/product/v2/status', [ + 'status_ticket' => $ticket, + ]); + } + + /** + * Get goods information. + * + * @param string $pid + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function get(string $pid) + { + return $this->httpPostJson('scan/product/v2/getinfo', [ + 'product' => [ + 'pid' => $pid, + ], + ]); + } + + /** + * Get a list of goods. + * + * @param string $context + * @param int $page + * @param int $size + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function list(string $context = '', int $page = 1, int $size = 10) + { + return $this->httpPostJson('scan/product/v2/getinfobypage', [ + 'page_context' => $context, + 'page_num' => $page, + 'page_size' => $size, + ]); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Goods/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Goods/ServiceProvider.php new file mode 100644 index 0000000..38a0902 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Goods/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Goods; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author her-cat + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['goods'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Material/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/Material/Client.php new file mode 100644 index 0000000..a62fded --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Material/Client.php @@ -0,0 +1,301 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Material; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; +use EasyWeChat\Kernel\Http\StreamResponse; +use EasyWeChat\Kernel\Messages\Article; + +/** + * Class Client. + * + * @author overtrue + */ +class Client extends BaseClient +{ + /** + * Allow media type. + * + * @var array + */ + protected $allowTypes = ['image', 'voice', 'video', 'thumb', 'news_image']; + + /** + * Upload image. + * + * @param string $path + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function uploadImage(string $path) + { + return $this->upload('image', $path); + } + + /** + * Upload voice. + * + * @param string $path + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function uploadVoice(string $path) + { + return $this->upload('voice', $path); + } + + /** + * Upload thumb. + * + * @param string $path + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function uploadThumb(string $path) + { + return $this->upload('thumb', $path); + } + + /** + * Upload video. + * + * @param string $path + * @param string $title + * @param string $description + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function uploadVideo(string $path, string $title, string $description) + { + $params = [ + 'description' => json_encode( + [ + 'title' => $title, + 'introduction' => $description, + ], JSON_UNESCAPED_UNICODE), + ]; + + return $this->upload('video', $path, $params); + } + + /** + * Upload articles. + * + * @param array|Article $articles + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function uploadArticle($articles) + { + if ($articles instanceof Article || !empty($articles['title'])) { + $articles = [$articles]; + } + + $params = ['articles' => array_map(function ($article) { + if ($article instanceof Article) { + return $article->transformForJsonRequestWithoutType(); + } + + return $article; + }, $articles)]; + + return $this->httpPostJson('cgi-bin/material/add_news', $params); + } + + /** + * Update article. + * + * @param string $mediaId + * @param array|Article $article + * @param int $index + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function updateArticle(string $mediaId, $article, int $index = 0) + { + if ($article instanceof Article) { + $article = $article->transformForJsonRequestWithoutType(); + } + + $params = [ + 'media_id' => $mediaId, + 'index' => $index, + 'articles' => isset($article['title']) ? $article : (isset($article[$index]) ? $article[$index] : []), + ]; + + return $this->httpPostJson('cgi-bin/material/update_news', $params); + } + + /** + * Upload image for article. + * + * @param string $path + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function uploadArticleImage(string $path) + { + return $this->upload('news_image', $path); + } + + /** + * Fetch material. + * + * @param string $mediaId + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function get(string $mediaId) + { + $response = $this->requestRaw('cgi-bin/material/get_material', 'POST', ['json' => ['media_id' => $mediaId]]); + + if (false !== stripos($response->getHeaderLine('Content-disposition'), 'attachment')) { + return StreamResponse::buildFromPsrResponse($response); + } + + return $this->castResponseToType($response, $this->app['config']->get('response_type')); + } + + /** + * Delete material by media ID. + * + * @param string $mediaId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function delete(string $mediaId) + { + return $this->httpPostJson('cgi-bin/material/del_material', ['media_id' => $mediaId]); + } + + /** + * List materials. + * + * example: + * + * { + * "total_count": TOTAL_COUNT, + * "item_count": ITEM_COUNT, + * "item": [{ + * "media_id": MEDIA_ID, + * "name": NAME, + * "update_time": UPDATE_TIME + * }, + * // more... + * ] + * } + * + * @param string $type + * @param int $offset + * @param int $count + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function list(string $type, int $offset = 0, int $count = 20) + { + $params = [ + 'type' => $type, + 'offset' => $offset, + 'count' => $count, + ]; + + return $this->httpPostJson('cgi-bin/material/batchget_material', $params); + } + + /** + * Get stats of materials. + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function stats() + { + return $this->httpGet('cgi-bin/material/get_materialcount'); + } + + /** + * Upload material. + * + * @param string $type + * @param string $path + * @param array $form + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function upload(string $type, string $path, array $form = []) + { + if (!file_exists($path) || !is_readable($path)) { + throw new InvalidArgumentException(sprintf('File does not exist, or the file is unreadable: "%s"', $path)); + } + + $form['type'] = $type; + + return $this->httpUpload($this->getApiByType($type), ['media' => $path], $form); + } + + /** + * Get API by type. + * + * @param string $type + * + * @return string + */ + public function getApiByType(string $type) + { + switch ($type) { + case 'news_image': + return 'cgi-bin/media/uploadimg'; + default: + return 'cgi-bin/material/add_material'; + } + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Material/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Material/ServiceProvider.php new file mode 100644 index 0000000..089a8f8 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Material/ServiceProvider.php @@ -0,0 +1,44 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * ServiceProvider.php. + * + * This file is part of the wechat. + * + * (c) overtrue + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Material; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['material'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Menu/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/Menu/Client.php new file mode 100644 index 0000000..9c132c6 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Menu/Client.php @@ -0,0 +1,103 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Menu; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author overtrue + */ +class Client extends BaseClient +{ + /** + * Get all menus. + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function list() + { + return $this->httpGet('cgi-bin/menu/get'); + } + + /** + * Get current menus. + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function current() + { + return $this->httpGet('cgi-bin/get_current_selfmenu_info'); + } + + /** + * Add menu. + * + * @param array $buttons + * @param array $matchRule + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function create(array $buttons, array $matchRule = []) + { + if (!empty($matchRule)) { + return $this->httpPostJson('cgi-bin/menu/addconditional', [ + 'button' => $buttons, + 'matchrule' => $matchRule, + ]); + } + + return $this->httpPostJson('cgi-bin/menu/create', ['button' => $buttons]); + } + + /** + * Destroy menu. + * + * @param int $menuId + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function delete(int $menuId = null) + { + if (is_null($menuId)) { + return $this->httpGet('cgi-bin/menu/delete'); + } + + return $this->httpPostJson('cgi-bin/menu/delconditional', ['menuid' => $menuId]); + } + + /** + * Test conditional menu. + * + * @param string $userId + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function match(string $userId) + { + return $this->httpPostJson('cgi-bin/menu/trymatch', ['user_id' => $userId]); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Menu/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Menu/ServiceProvider.php new file mode 100644 index 0000000..e79b105 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Menu/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Menu; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['menu'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/OAuth/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/OAuth/ServiceProvider.php new file mode 100644 index 0000000..ba176dc --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/OAuth/ServiceProvider.php @@ -0,0 +1,66 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\OAuth; + +use Overtrue\Socialite\SocialiteManager as Socialite; +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['oauth'] = function ($app) { + $socialite = (new Socialite([ + 'wechat' => [ + 'client_id' => $app['config']['app_id'], + 'client_secret' => $app['config']['secret'], + 'redirect' => $this->prepareCallbackUrl($app), + ], + ], $app['request']))->driver('wechat'); + + $scopes = (array) $app['config']->get('oauth.scopes', ['snsapi_userinfo']); + + if (!empty($scopes)) { + $socialite->scopes($scopes); + } + + return $socialite; + }; + } + + /** + * Prepare the OAuth callback url for wechat. + * + * @param Container $app + * + * @return string + */ + private function prepareCallbackUrl($app) + { + $callback = $app['config']->get('oauth.callback'); + if (0 === stripos($callback, 'http')) { + return $callback; + } + $baseUrl = $app['request']->getSchemeAndHttpHost(); + + return $baseUrl.'/'.ltrim($callback, '/'); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/OCR/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/OCR/Client.php new file mode 100644 index 0000000..6aa44bc --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/OCR/Client.php @@ -0,0 +1,85 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\OCR; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; + +/** + * Class Client. + * + * @author joyeekk + */ +class Client extends BaseClient +{ + /** + * Allow image parameter type. + * + * @var array + */ + protected $allowTypes = ['photo', 'scan']; + + /** + * ID card OCR. + * + * @param string $path + * @param string $type + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function idCard(string $path, string $type = 'photo') + { + if (!\in_array($type, $this->allowTypes, true)) { + throw new InvalidArgumentException(sprintf("Unsupported type: '%s'", $type)); + } + + return $this->httpGet('cv/ocr/idcard', [ + 'type' => $type, + 'img_url' => $path, + ]); + } + + /** + * Bank card OCR. + * + * @param string $path + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function bankCard(string $path) + { + return $this->httpGet('cv/ocr/bankcard', [ + 'img_url' => $path, + ]); + } + + /** + * Vehicle license OCR. + * + * @param string $path + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function vehicleLicense(string $path) + { + return $this->httpGet('cv/ocr/driving', [ + 'img_url' => $path, + ]); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/OCR/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/OCR/ServiceProvider.php new file mode 100644 index 0000000..1773079 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/OCR/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\OCR; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author joyeekk + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['ocr'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/POI/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/POI/Client.php new file mode 100644 index 0000000..6a8e570 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/POI/Client.php @@ -0,0 +1,145 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\POI; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author overtrue + */ +class Client extends BaseClient +{ + /** + * Get POI supported categories. + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function categories() + { + return $this->httpGet('cgi-bin/poi/getwxcategory'); + } + + /** + * Get POI by ID. + * + * @param int $poiId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function get(int $poiId) + { + return $this->httpPostJson('cgi-bin/poi/getpoi', ['poi_id' => $poiId]); + } + + /** + * List POI. + * + * @param int $offset + * @param int $limit + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function list(int $offset = 0, int $limit = 10) + { + $params = [ + 'begin' => $offset, + 'limit' => $limit, + ]; + + return $this->httpPostJson('cgi-bin/poi/getpoilist', $params); + } + + /** + * Create a POI. + * + * @param array $baseInfo + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function create(array $baseInfo) + { + $params = [ + 'business' => [ + 'base_info' => $baseInfo, + ], + ]; + + return $this->httpPostJson('cgi-bin/poi/addpoi', $params); + } + + /** + * @param array $databaseInfo + * + * @return int + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + public function createAndGetId(array $databaseInfo) + { + /** @var array $response */ + $response = $this->detectAndCastResponseToType($this->create($databaseInfo), 'array'); + + return $response['poi_id']; + } + + /** + * Update a POI. + * + * @param int $poiId + * @param array $baseInfo + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function update(int $poiId, array $baseInfo) + { + $params = [ + 'business' => [ + 'base_info' => array_merge($baseInfo, ['poi_id' => $poiId]), + ], + ]; + + return $this->httpPostJson('cgi-bin/poi/updatepoi', $params); + } + + /** + * Delete a POI. + * + * @param int $poiId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function delete(int $poiId) + { + return $this->httpPostJson('cgi-bin/poi/delpoi', ['poi_id' => $poiId]); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/POI/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/POI/ServiceProvider.php new file mode 100644 index 0000000..156440b --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/POI/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\POI; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['poi'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Semantic/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/Semantic/Client.php new file mode 100644 index 0000000..e83792f --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Semantic/Client.php @@ -0,0 +1,45 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Semantic; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author overtrue + */ +class Client extends BaseClient +{ + /** + * Get the semantic content of giving string. + * + * @param string $keyword + * @param string $categories + * @param array $optional + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function query(string $keyword, string $categories, array $optional = []) + { + $params = [ + 'query' => $keyword, + 'category' => $categories, + 'appid' => $this->app['config']['app_id'], + ]; + + return $this->httpPostJson('semantic/semproxy/search', array_merge($params, $optional)); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Semantic/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Semantic/ServiceProvider.php new file mode 100644 index 0000000..835b7fc --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Semantic/ServiceProvider.php @@ -0,0 +1,31 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Semantic; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['semantic'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Server/Guard.php b/vendor/overtrue/wechat/src/OfficialAccount/Server/Guard.php new file mode 100644 index 0000000..c723847 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Server/Guard.php @@ -0,0 +1,30 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Server; + +use EasyWeChat\Kernel\ServerGuard; + +/** + * Class Guard. + * + * @author overtrue + */ +class Guard extends ServerGuard +{ + /** + * @return bool + */ + protected function shouldReturnRawResponse(): bool + { + return !is_null($this->app['request']->get('echostr')); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Server/Handlers/EchoStrHandler.php b/vendor/overtrue/wechat/src/OfficialAccount/Server/Handlers/EchoStrHandler.php new file mode 100644 index 0000000..f076abf --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Server/Handlers/EchoStrHandler.php @@ -0,0 +1,51 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Server\Handlers; + +use EasyWeChat\Kernel\Contracts\EventHandlerInterface; +use EasyWeChat\Kernel\Decorators\FinallyResult; +use EasyWeChat\Kernel\ServiceContainer; + +/** + * Class EchoStrHandler. + * + * @author overtrue + */ +class EchoStrHandler implements EventHandlerInterface +{ + /** + * @var ServiceContainer + */ + protected $app; + + /** + * EchoStrHandler constructor. + * + * @param ServiceContainer $app + */ + public function __construct(ServiceContainer $app) + { + $this->app = $app; + } + + /** + * @param mixed $payload + * + * @return FinallyResult|null + */ + public function handle($payload = null) + { + if ($str = $this->app['request']->get('echostr')) { + return new FinallyResult($str); + } + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Server/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Server/ServiceProvider.php new file mode 100644 index 0000000..0c6716b --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Server/ServiceProvider.php @@ -0,0 +1,46 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Server; + +use EasyWeChat\Kernel\Encryptor; +use EasyWeChat\OfficialAccount\Server\Handlers\EchoStrHandler; +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + !isset($app['encryptor']) && $app['encryptor'] = function ($app) { + return new Encryptor( + $app['config']['app_id'], + $app['config']['token'], + $app['config']['aes_key'] + ); + }; + + !isset($app['server']) && $app['server'] = function ($app) { + $guard = new Guard($app); + $guard->push(new EchoStrHandler($app)); + + return $guard; + }; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/Client.php new file mode 100644 index 0000000..c401008 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/Client.php @@ -0,0 +1,81 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\ShakeAround; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author overtrue + */ +class Client extends BaseClient +{ + /** + * @param array $data + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function register($data) + { + return $this->httpPostJson('shakearound/account/register', $data); + } + + /** + * Get audit status. + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function status() + { + return $this->httpGet('shakearound/account/auditstatus'); + } + + /** + * Get shake info. + * + * @param string $ticket + * @param bool $needPoi + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function user(string $ticket, bool $needPoi = false) + { + $params = [ + 'ticket' => $ticket, + ]; + + if ($needPoi) { + $params['need_poi'] = 1; + } + + return $this->httpPostJson('shakearound/user/getshakeinfo', $params); + } + + /** + * @param string $ticket + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + */ + public function userWithPoi(string $ticket) + { + return $this->user($ticket, true); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/DeviceClient.php b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/DeviceClient.php new file mode 100644 index 0000000..859a120 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/DeviceClient.php @@ -0,0 +1,190 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\ShakeAround; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class DeviceClient. + * + * @author allen05ren + */ +class DeviceClient extends BaseClient +{ + /** + * @param array $data + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function apply(array $data) + { + return $this->httpPostJson('shakearound/device/applyid', $data); + } + + /** + * Get audit status. + * + * @param int $applyId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function status(int $applyId) + { + $params = [ + 'apply_id' => $applyId, + ]; + + return $this->httpPostJson('shakearound/device/applystatus', $params); + } + + /** + * Update a device comment. + * + * @param array $deviceIdentifier + * @param string $comment + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function update(array $deviceIdentifier, string $comment) + { + $params = [ + 'device_identifier' => $deviceIdentifier, + 'comment' => $comment, + ]; + + return $this->httpPostJson('shakearound/device/update', $params); + } + + /** + * Bind location for device. + * + * @param array $deviceIdentifier + * @param int $poiId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function bindPoi(array $deviceIdentifier, int $poiId) + { + $params = [ + 'device_identifier' => $deviceIdentifier, + 'poi_id' => $poiId, + ]; + + return $this->httpPostJson('shakearound/device/bindlocation', $params); + } + + /** + * @param array $deviceIdentifier + * @param int $poiId + * @param string $appId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function bindThirdPoi(array $deviceIdentifier, int $poiId, string $appId) + { + $params = [ + 'device_identifier' => $deviceIdentifier, + 'poi_id' => $poiId, + 'type' => 2, + 'poi_appid' => $appId, + ]; + + return $this->httpPostJson('shakearound/device/bindlocation', $params); + } + + /** + * Fetch batch of devices by deviceIds. + * + * @param array $deviceIdentifiers + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + */ + public function listByIds(array $deviceIdentifiers) + { + $params = [ + 'type' => 1, + 'device_identifiers' => $deviceIdentifiers, + ]; + + return $this->search($params); + } + + /** + * Pagination to get batch of devices. + * + * @param int $lastId + * @param int $count + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + */ + public function list(int $lastId, int $count) + { + $params = [ + 'type' => 2, + 'last_seen' => $lastId, + 'count' => $count, + ]; + + return $this->search($params); + } + + /** + * Fetch batch of devices by applyId. + * + * @param int $applyId + * @param int $lastId + * @param int $count + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + */ + public function listByApplyId(int $applyId, int $lastId, int $count) + { + $params = [ + 'type' => 3, + 'apply_id' => $applyId, + 'last_seen' => $lastId, + 'count' => $count, + ]; + + return $this->search($params); + } + + /** + * Fetch batch of devices. + * + * @param array $params + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function search(array $params) + { + return $this->httpPostJson('shakearound/device/search', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/GroupClient.php b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/GroupClient.php new file mode 100644 index 0000000..9fd578d --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/GroupClient.php @@ -0,0 +1,167 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\ShakeAround; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class GroupClient. + * + * @author allen05ren + */ +class GroupClient extends BaseClient +{ + /** + * Add device group. + * + * @param string $name + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function create(string $name) + { + $params = [ + 'group_name' => $name, + ]; + + return $this->httpPostJson('shakearound/device/group/add', $params); + } + + /** + * Update a device group name. + * + * @param int $groupId + * @param string $name + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function update(int $groupId, string $name) + { + $params = [ + 'group_id' => $groupId, + 'group_name' => $name, + ]; + + return $this->httpPostJson('shakearound/device/group/update', $params); + } + + /** + * Delete device group. + * + * @param int $groupId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function delete(int $groupId) + { + $params = [ + 'group_id' => $groupId, + ]; + + return $this->httpPostJson('shakearound/device/group/delete', $params); + } + + /** + * List all device groups. + * + * @param int $begin + * @param int $count + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function list(int $begin, int $count) + { + $params = [ + 'begin' => $begin, + 'count' => $count, + ]; + + return $this->httpPostJson('shakearound/device/group/getlist', $params); + } + + /** + * Get detail of a device group. + * + * @param int $groupId + * @param int $begin + * @param int $count + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function get(int $groupId, int $begin, int $count) + { + $params = [ + 'group_id' => $groupId, + 'begin' => $begin, + 'count' => $count, + ]; + + return $this->httpPostJson('shakearound/device/group/getdetail', $params); + } + + /** + * Add one or more devices to a device group. + * + * @param int $groupId + * @param array $deviceIdentifiers + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function addDevices(int $groupId, array $deviceIdentifiers) + { + $params = [ + 'group_id' => $groupId, + 'device_identifiers' => $deviceIdentifiers, + ]; + + return $this->httpPostJson('shakearound/device/group/adddevice', $params); + } + + /** + * Remove one or more devices from a device group. + * + * @param int $groupId + * @param array $deviceIdentifiers + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function removeDevices(int $groupId, array $deviceIdentifiers) + { + $params = [ + 'group_id' => $groupId, + 'device_identifiers' => $deviceIdentifiers, + ]; + + return $this->httpPostJson('shakearound/device/group/deletedevice', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/MaterialClient.php b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/MaterialClient.php new file mode 100644 index 0000000..edc40b2 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/MaterialClient.php @@ -0,0 +1,44 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\ShakeAround; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; + +/** + * Class MaterialClient. + * + * @author allen05ren + */ +class MaterialClient extends BaseClient +{ + /** + * Upload image material. + * + * @param string $path + * @param string $type + * + * @return string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function uploadImage(string $path, string $type = 'icon') + { + if (!file_exists($path) || !is_readable($path)) { + throw new InvalidArgumentException(sprintf('File does not exist, or the file is unreadable: "%s"', $path)); + } + + return $this->httpUpload('shakearound/material/add', ['media' => $path], [], ['type' => strtolower($type)]); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/PageClient.php b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/PageClient.php new file mode 100644 index 0000000..73ba1ac --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/PageClient.php @@ -0,0 +1,110 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\ShakeAround; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class PageClient. + * + * @author allen05ren + */ +class PageClient extends BaseClient +{ + /** + * @param array $data + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function create(array $data) + { + return $this->httpPostJson('shakearound/page/add', $data); + } + + /** + * @param int $pageId + * @param array $data + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function update(int $pageId, array $data) + { + return $this->httpPostJson('shakearound/page/update', array_merge(['page_id' => $pageId], $data)); + } + + /** + * Fetch batch of pages by pageIds. + * + * @param array $pageIds + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function listByIds(array $pageIds) + { + $params = [ + 'type' => 1, + 'page_ids' => $pageIds, + ]; + + return $this->httpPostJson('shakearound/page/search', $params); + } + + /** + * Pagination to get batch of pages. + * + * @param int $begin + * @param int $count + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function list(int $begin, int $count) + { + $params = [ + 'type' => 2, + 'begin' => $begin, + 'count' => $count, + ]; + + return $this->httpPostJson('shakearound/page/search', $params); + } + + /** + * delete a page. + * + * @param int $pageId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function delete(int $pageId) + { + $params = [ + 'page_id' => $pageId, + ]; + + return $this->httpPostJson('shakearound/page/delete', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/RelationClient.php b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/RelationClient.php new file mode 100644 index 0000000..de80911 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/RelationClient.php @@ -0,0 +1,87 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\ShakeAround; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class RelationClient. + * + * @author allen05ren + */ +class RelationClient extends BaseClient +{ + /** + * Bind pages for device. + * + * @param array $deviceIdentifier + * @param array $pageIds + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function bindPages(array $deviceIdentifier, array $pageIds) + { + $params = [ + 'device_identifier' => $deviceIdentifier, + 'page_ids' => $pageIds, + ]; + + return $this->httpPostJson('shakearound/device/bindpage', $params); + } + + /** + * Get pageIds by deviceId. + * + * @param array $deviceIdentifier + * + * @return array|\EasyWeChat\Kernel\Support\Collection + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function listByDeviceId(array $deviceIdentifier) + { + $params = [ + 'type' => 1, + 'device_identifier' => $deviceIdentifier, + ]; + + return $this->httpPostJson('shakearound/relation/search', $params); + } + + /** + * Get devices by pageId. + * + * @param int $pageId + * @param int $begin + * @param int $count + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function listByPageId(int $pageId, int $begin, int $count) + { + $params = [ + 'type' => 2, + 'page_id' => $pageId, + 'begin' => $begin, + 'count' => $count, + ]; + + return $this->httpPostJson('shakearound/relation/search', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/ServiceProvider.php new file mode 100644 index 0000000..a13d2b0 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/ServiceProvider.php @@ -0,0 +1,57 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\ShakeAround; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author allen05ren + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['shake_around'] = function ($app) { + return new ShakeAround($app); + }; + + $app['shake_around.device'] = function ($app) { + return new DeviceClient($app); + }; + + $app['shake_around.page'] = function ($app) { + return new PageClient($app); + }; + + $app['shake_around.material'] = function ($app) { + return new MaterialClient($app); + }; + + $app['shake_around.group'] = function ($app) { + return new GroupClient($app); + }; + + $app['shake_around.relation'] = function ($app) { + return new RelationClient($app); + }; + + $app['shake_around.stats'] = function ($app) { + return new StatsClient($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/ShakeAround.php b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/ShakeAround.php new file mode 100644 index 0000000..666a38a --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/ShakeAround.php @@ -0,0 +1,44 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\ShakeAround; + +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; + +/** + * Class Card. + * + * @author overtrue + * + * @property \EasyWeChat\OfficialAccount\ShakeAround\DeviceClient $device + * @property \EasyWeChat\OfficialAccount\ShakeAround\GroupClient $group + * @property \EasyWeChat\OfficialAccount\ShakeAround\MaterialClient $material + * @property \EasyWeChat\OfficialAccount\ShakeAround\RelationClient $relation + * @property \EasyWeChat\OfficialAccount\ShakeAround\StatsClient $stats + */ +class ShakeAround extends Client +{ + /** + * @param string $property + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + public function __get($property) + { + if (isset($this->app["shake_around.{$property}"])) { + return $this->app["shake_around.{$property}"]; + } + + throw new InvalidArgumentException(sprintf('No shake_around service named "%s".', $property)); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/StatsClient.php b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/StatsClient.php new file mode 100644 index 0000000..a4b55eb --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/StatsClient.php @@ -0,0 +1,110 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\ShakeAround; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class StatsClient. + * + * @author allen05ren + */ +class StatsClient extends BaseClient +{ + /** + * Fetch statistics data by deviceId. + * + * @param array $deviceIdentifier + * @param int $beginTime (Unix timestamp) + * @param int $endTime (Unix timestamp) + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function deviceSummary(array $deviceIdentifier, int $beginTime, int $endTime) + { + $params = [ + 'device_identifier' => $deviceIdentifier, + 'begin_date' => $beginTime, + 'end_date' => $endTime, + ]; + + return $this->httpPostJson('shakearound/statistics/device', $params); + } + + /** + * Fetch all devices statistics data by date. + * + * @param int $timestamp + * @param int $pageIndex + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function devicesSummary(int $timestamp, int $pageIndex) + { + $params = [ + 'date' => $timestamp, + 'page_index' => $pageIndex, + ]; + + return $this->httpPostJson('shakearound/statistics/devicelist', $params); + } + + /** + * Fetch statistics data by pageId. + * + * @param int $pageId + * @param int $beginTime (Unix timestamp) + * @param int $endTime (Unix timestamp) + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function pageSummary(int $pageId, int $beginTime, int $endTime) + { + $params = [ + 'page_id' => $pageId, + 'begin_date' => $beginTime, + 'end_date' => $endTime, + ]; + + return $this->httpPostJson('shakearound/statistics/page', $params); + } + + /** + * Fetch all pages statistics data by date. + * + * @param int $timestamp + * @param int $pageIndex + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function pagesSummary(int $timestamp, int $pageIndex) + { + $params = [ + 'date' => $timestamp, + 'page_index' => $pageIndex, + ]; + + return $this->httpPostJson('shakearound/statistics/pagelist', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Store/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/Store/Client.php new file mode 100644 index 0000000..3643096 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Store/Client.php @@ -0,0 +1,209 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Store; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author bigface + */ +class Client extends BaseClient +{ + /** + * Get WXA supported categories. + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function categories() + { + return $this->httpGet('wxa/get_merchant_category'); + } + + /** + * Get district from tencent map . + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function districts() + { + return $this->httpGet('wxa/get_district'); + } + + /** + * Search store from tencent map. + * + * @param int $districtId + * @param string $keyword + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function searchFromMap(int $districtId, string $keyword) + { + $params = [ + 'districtid' => $districtId, + 'keyword' => $keyword, + ]; + + return $this->httpPostJson('wxa/search_map_poi', $params); + } + + /** + * Get store check status. + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getStatus() + { + return $this->httpPostJson('wxa/get_merchant_audit_info'); + } + + /** + * Create a merchant. + * + * @param array $baseInfo + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function createMerchant(array $baseInfo) + { + return $this->httpPostJson('wxa/apply_merchant', $baseInfo); + } + + /** + * Update a merchant. + * + * @param array $params + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function updateMerchant(array $params) + { + return $this->httpPostJson('wxa/modify_merchant', $params); + } + + /** + * Create a store from tencent map. + * + * @param array $baseInfo + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function createFromMap(array $baseInfo) + { + return $this->httpPostJson('wxa/create_map_poi', $baseInfo); + } + + /** + * Create a store. + * + * @param array $baseInfo + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function create(array $baseInfo) + { + return $this->httpPostJson('wxa/add_store', $baseInfo); + } + + /** + * Update a store. + * + * @param int $poiId + * @param array $baseInfo + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function update(int $poiId, array $baseInfo) + { + $params = array_merge($baseInfo, ['poi_id' => $poiId]); + + return $this->httpPostJson('wxa/update_store', $params); + } + + /** + * Get store by ID. + * + * @param int $poiId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function get(int $poiId) + { + return $this->httpPostJson('wxa/get_store_info', ['poi_id' => $poiId]); + } + + /** + * List store. + * + * @param int $offset + * @param int $limit + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function list(int $offset = 0, int $limit = 10) + { + $params = [ + 'offset' => $offset, + 'limit' => $limit, + ]; + + return $this->httpPostJson('wxa/get_store_list', $params); + } + + /** + * Delete a store. + * + * @param int $poiId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function delete(int $poiId) + { + return $this->httpPostJson('wxa/del_store', ['poi_id' => $poiId]); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Store/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Store/ServiceProvider.php new file mode 100644 index 0000000..f5c48d7 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Store/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Store; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author bigface + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['store'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/TemplateMessage/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/TemplateMessage/Client.php new file mode 100644 index 0000000..be1fa4d --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/TemplateMessage/Client.php @@ -0,0 +1,234 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\TemplateMessage; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; +use ReflectionClass; + +/** + * Class Client. + * + * @author overtrue + */ +class Client extends BaseClient +{ + const API_SEND = 'cgi-bin/message/template/send'; + + /** + * Attributes. + * + * @var array + */ + protected $message = [ + 'touser' => '', + 'template_id' => '', + 'url' => '', + 'data' => [], + 'miniprogram' => '', + ]; + + /** + * Required attributes. + * + * @var array + */ + protected $required = ['touser', 'template_id']; + + /** + * Set industry. + * + * @param int $industryOne + * @param int $industryTwo + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function setIndustry($industryOne, $industryTwo) + { + $params = [ + 'industry_id1' => $industryOne, + 'industry_id2' => $industryTwo, + ]; + + return $this->httpPostJson('cgi-bin/template/api_set_industry', $params); + } + + /** + * Get industry. + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getIndustry() + { + return $this->httpPostJson('cgi-bin/template/get_industry'); + } + + /** + * Add a template and get template ID. + * + * @param string $shortId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function addTemplate($shortId) + { + $params = ['template_id_short' => $shortId]; + + return $this->httpPostJson('cgi-bin/template/api_add_template', $params); + } + + /** + * Get private templates. + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getPrivateTemplates() + { + return $this->httpPostJson('cgi-bin/template/get_all_private_template'); + } + + /** + * Delete private template. + * + * @param string $templateId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function deletePrivateTemplate($templateId) + { + $params = ['template_id' => $templateId]; + + return $this->httpPostJson('cgi-bin/template/del_private_template', $params); + } + + /** + * Send a template message. + * + * @param array $data + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function send(array $data = []) + { + $params = $this->formatMessage($data); + + $this->restoreMessage(); + + return $this->httpPostJson(static::API_SEND, $params); + } + + /** + * Send template-message for subscription. + * + * @param array $data + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function sendSubscription(array $data = []) + { + $params = $this->formatMessage($data); + + $this->restoreMessage(); + + return $this->httpPostJson('cgi-bin/message/template/subscribe', $params); + } + + /** + * @param array $data + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + protected function formatMessage(array $data = []) + { + $params = array_merge($this->message, $data); + + foreach ($params as $key => $value) { + if (in_array($key, $this->required, true) && empty($value) && empty($this->message[$key])) { + throw new InvalidArgumentException(sprintf('Attribute "%s" can not be empty!', $key)); + } + + $params[$key] = empty($value) ? $this->message[$key] : $value; + } + + $params['data'] = $this->formatData($params['data'] ?? []); + + return $params; + } + + /** + * @param array $data + * + * @return array + */ + protected function formatData(array $data) + { + $formatted = []; + + foreach ($data as $key => $value) { + if (is_array($value)) { + if (isset($value['value'])) { + $formatted[$key] = $value; + + continue; + } + + if (count($value) >= 2) { + $value = [ + 'value' => $value[0], + 'color' => $value[1], + ]; + } + } else { + $value = [ + 'value' => strval($value), + ]; + } + + $formatted[$key] = $value; + } + + return $formatted; + } + + /** + * Restore message. + */ + protected function restoreMessage() + { + $this->message = (new ReflectionClass(static::class))->getDefaultProperties()['message']; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/TemplateMessage/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/TemplateMessage/ServiceProvider.php new file mode 100644 index 0000000..98476fc --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/TemplateMessage/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\TemplateMessage; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['template_message'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/User/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/User/ServiceProvider.php new file mode 100644 index 0000000..c11d8b3 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/User/ServiceProvider.php @@ -0,0 +1,35 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\User; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['user'] = function ($app) { + return new UserClient($app); + }; + + $app['user_tag'] = function ($app) { + return new TagClient($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/User/TagClient.php b/vendor/overtrue/wechat/src/OfficialAccount/User/TagClient.php new file mode 100644 index 0000000..d41e363 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/User/TagClient.php @@ -0,0 +1,175 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\User; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class TagClient. + * + * @author overtrue + */ +class TagClient extends BaseClient +{ + /** + * Create tag. + * + * @param string $name + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function create(string $name) + { + $params = [ + 'tag' => ['name' => $name], + ]; + + return $this->httpPostJson('cgi-bin/tags/create', $params); + } + + /** + * List all tags. + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function list() + { + return $this->httpGet('cgi-bin/tags/get'); + } + + /** + * Update a tag name. + * + * @param int $tagId + * @param string $name + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function update(int $tagId, string $name) + { + $params = [ + 'tag' => [ + 'id' => $tagId, + 'name' => $name, + ], + ]; + + return $this->httpPostJson('cgi-bin/tags/update', $params); + } + + /** + * Delete tag. + * + * @param int $tagId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function delete(int $tagId) + { + $params = [ + 'tag' => ['id' => $tagId], + ]; + + return $this->httpPostJson('cgi-bin/tags/delete', $params); + } + + /** + * Get user tags. + * + * @param string $openid + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function userTags(string $openid) + { + $params = ['openid' => $openid]; + + return $this->httpPostJson('cgi-bin/tags/getidlist', $params); + } + + /** + * Get users from a tag. + * + * @param int $tagId + * @param string $nextOpenId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function usersOfTag(int $tagId, string $nextOpenId = '') + { + $params = [ + 'tagid' => $tagId, + 'next_openid' => $nextOpenId, + ]; + + return $this->httpPostJson('cgi-bin/user/tag/get', $params); + } + + /** + * Batch tag users. + * + * @param array $openids + * @param int $tagId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function tagUsers(array $openids, int $tagId) + { + $params = [ + 'openid_list' => $openids, + 'tagid' => $tagId, + ]; + + return $this->httpPostJson('cgi-bin/tags/members/batchtagging', $params); + } + + /** + * Untag users from a tag. + * + * @param array $openids + * @param int $tagId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function untagUsers(array $openids, int $tagId) + { + $params = [ + 'openid_list' => $openids, + 'tagid' => $tagId, + ]; + + return $this->httpPostJson('cgi-bin/tags/members/batchuntagging', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/User/UserClient.php b/vendor/overtrue/wechat/src/OfficialAccount/User/UserClient.php new file mode 100644 index 0000000..6b9491f --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/User/UserClient.php @@ -0,0 +1,172 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\User; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class UserClient. + * + * @author overtrue + */ +class UserClient extends BaseClient +{ + /** + * Fetch a user by open id. + * + * @param string $openid + * @param string $lang + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function get(string $openid, string $lang = 'zh_CN') + { + $params = [ + 'openid' => $openid, + 'lang' => $lang, + ]; + + return $this->httpGet('cgi-bin/user/info', $params); + } + + /** + * Batch get users. + * + * @param array $openids + * @param string $lang + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function select(array $openids, string $lang = 'zh_CN') + { + return $this->httpPostJson('cgi-bin/user/info/batchget', [ + 'user_list' => array_map(function ($openid) use ($lang) { + return [ + 'openid' => $openid, + 'lang' => $lang, + ]; + }, $openids), + ]); + } + + /** + * List users. + * + * @param string $nextOpenId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function list(string $nextOpenId = null) + { + $params = ['next_openid' => $nextOpenId]; + + return $this->httpGet('cgi-bin/user/get', $params); + } + + /** + * Set user remark. + * + * @param string $openid + * @param string $remark + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function remark(string $openid, string $remark) + { + $params = [ + 'openid' => $openid, + 'remark' => $remark, + ]; + + return $this->httpPostJson('cgi-bin/user/info/updateremark', $params); + } + + /** + * Get black list. + * + * @param string|null $beginOpenid + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function blacklist(string $beginOpenid = null) + { + $params = ['begin_openid' => $beginOpenid]; + + return $this->httpPostJson('cgi-bin/tags/members/getblacklist', $params); + } + + /** + * Batch block user. + * + * @param array|string $openidList + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function block($openidList) + { + $params = ['openid_list' => (array) $openidList]; + + return $this->httpPostJson('cgi-bin/tags/members/batchblacklist', $params); + } + + /** + * Batch unblock user. + * + * @param array $openidList + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function unblock($openidList) + { + $params = ['openid_list' => (array) $openidList]; + + return $this->httpPostJson('cgi-bin/tags/members/batchunblacklist', $params); + } + + /** + * @param string $oldAppId + * @param array $openidList + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function changeOpenid(string $oldAppId, array $openidList) + { + $params = [ + 'from_appid' => $oldAppId, + 'openid_list' => $openidList, + ]; + + return $this->httpPostJson('cgi-bin/changeopenid', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/WiFi/CardClient.php b/vendor/overtrue/wechat/src/OfficialAccount/WiFi/CardClient.php new file mode 100644 index 0000000..76a4e6a --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/WiFi/CardClient.php @@ -0,0 +1,52 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\WiFi; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class CardClient. + * + * @author her-cat + */ +class CardClient extends BaseClient +{ + /** + * Set shop card coupon delivery information. + * + * @param array $data + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function set(array $data) + { + return $this->httpPostJson('bizwifi/couponput/set', $data); + } + + /** + * Get shop card coupon delivery information. + * + * @param int $shopId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function get(int $shopId = 0) + { + return $this->httpPostJson('bizwifi/couponput/get', ['shop_id' => $shopId]); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/WiFi/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/WiFi/Client.php new file mode 100644 index 0000000..076957d --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/WiFi/Client.php @@ -0,0 +1,98 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\WiFi; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author her-cat + */ +class Client extends BaseClient +{ + /** + * Get Wi-Fi statistics. + * + * @param string $beginDate + * @param string $endDate + * @param int $shopId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function summary(string $beginDate, string $endDate, int $shopId = -1) + { + $data = [ + 'begin_date' => $beginDate, + 'end_date' => $endDate, + 'shop_id' => $shopId, + ]; + + return $this->httpPostJson('bizwifi/statistics/list', $data); + } + + /** + * Get the material QR code. + * + * @param int $shopId + * @param string $ssid + * @param int $type + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getQrCodeUrl(int $shopId, string $ssid, int $type = 0) + { + $data = [ + 'shop_id' => $shopId, + 'ssid' => $ssid, + 'img_id' => $type, + ]; + + return $this->httpPostJson('bizwifi/qrcode/get', $data); + } + + /** + * Wi-Fi completion page jump applet. + * + * @param array $data + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function setFinishPage(array $data) + { + return $this->httpPostJson('bizwifi/finishpage/set', $data); + } + + /** + * Set the top banner jump applet. + * + * @param array $data + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function setHomePage(array $data) + { + return $this->httpPostJson('bizwifi/homepage/set', $data); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/WiFi/DeviceClient.php b/vendor/overtrue/wechat/src/OfficialAccount/WiFi/DeviceClient.php new file mode 100644 index 0000000..8fecbf7 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/WiFi/DeviceClient.php @@ -0,0 +1,127 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\WiFi; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class DeviceClient. + * + * @author her-cat + */ +class DeviceClient extends BaseClient +{ + /** + * Add a password device. + * + * @param int $shopId + * @param string $ssid + * @param string $password + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function addPasswordDevice(int $shopId, string $ssid, string $password) + { + $data = [ + 'shop_id' => $shopId, + 'ssid' => $ssid, + 'password' => $password, + ]; + + return $this->httpPostJson('bizwifi/device/add', $data); + } + + /** + * Add a portal device. + * + * @param int $shopId + * @param string $ssid + * @param bool $reset + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function addPortalDevice(int $shopId, string $ssid, bool $reset = false) + { + $data = [ + 'shop_id' => $shopId, + 'ssid' => $ssid, + 'reset' => $reset, + ]; + + return $this->httpPostJson('bizwifi/apportal/register', $data); + } + + /** + * Delete device by MAC address. + * + * @param string $macAddress + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function delete(string $macAddress) + { + return $this->httpPostJson('bizwifi/device/delete', ['bssid' => $macAddress]); + } + + /** + * Get a list of devices. + * + * @param int $page + * @param int $size + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function list(int $page = 1, int $size = 10) + { + $data = [ + 'pageindex' => $page, + 'pagesize' => $size, + ]; + + return $this->httpPostJson('bizwifi/device/list', $data); + } + + /** + * Get a list of devices by shop ID. + * + * @param int $shopId + * @param int $page + * @param int $size + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function listByShopId(int $shopId, int $page = 1, int $size = 10) + { + $data = [ + 'shop_id' => $shopId, + 'pageindex' => $page, + 'pagesize' => $size, + ]; + + return $this->httpPostJson('bizwifi/device/list', $data); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/WiFi/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/WiFi/ServiceProvider.php new file mode 100644 index 0000000..7a28b51 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/WiFi/ServiceProvider.php @@ -0,0 +1,45 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\WiFi; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author her-cat + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc} + */ + public function register(Container $app) + { + $app['wifi'] = function ($app) { + return new Client($app); + }; + + $app['wifi_card'] = function ($app) { + return new CardClient($app); + }; + + $app['wifi_device'] = function ($app) { + return new DeviceClient($app); + }; + + $app['wifi_shop'] = function ($app) { + return new ShopClient($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/WiFi/ShopClient.php b/vendor/overtrue/wechat/src/OfficialAccount/WiFi/ShopClient.php new file mode 100644 index 0000000..34f1c97 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/WiFi/ShopClient.php @@ -0,0 +1,100 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\WiFi; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class ShopClient. + * + * @author her-cat + */ +class ShopClient extends BaseClient +{ + /** + * Get shop Wi-Fi information. + * + * @param int $shopId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function get(int $shopId) + { + return $this->httpPostJson('bizwifi/shop/get', ['shop_id' => $shopId]); + } + + /** + * Get a list of Wi-Fi shops. + * + * @param int $page + * @param int $size + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function list(int $page = 1, int $size = 10) + { + $data = [ + 'pageindex' => $page, + 'pagesize' => $size, + ]; + + return $this->httpPostJson('bizwifi/shop/list', $data); + } + + /** + * Update shop Wi-Fi information. + * + * @param int $shopId + * @param array $data + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function update(int $shopId, array $data) + { + $data = array_merge(['shop_id' => $shopId], $data); + + return $this->httpPostJson('bizwifi/shop/update', $data); + } + + /** + * Clear shop network and equipment. + * + * @param int $shopId + * @param string|null $ssid + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function clearDevice(int $shopId, string $ssid = null) + { + $data = [ + 'shop_id' => $shopId, + ]; + + if (!is_null($ssid)) { + $data['ssid'] = $ssid; + } + + return $this->httpPostJson('bizwifi/shop/clean', $data); + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Application.php b/vendor/overtrue/wechat/src/OpenPlatform/Application.php new file mode 100644 index 0000000..459bb96 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Application.php @@ -0,0 +1,220 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform; + +use EasyWeChat\Kernel\ServiceContainer; +use EasyWeChat\MiniProgram\Encryptor; +use EasyWeChat\OpenPlatform\Authorizer\Auth\AccessToken; +use EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Application as MiniProgram; +use EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Auth\Client; +use EasyWeChat\OpenPlatform\Authorizer\OfficialAccount\Account\Client as AccountClient; +use EasyWeChat\OpenPlatform\Authorizer\OfficialAccount\Application as OfficialAccount; +use EasyWeChat\OpenPlatform\Authorizer\OfficialAccount\OAuth\ComponentDelegate; +use EasyWeChat\OpenPlatform\Authorizer\Server\Guard; + +/** + * Class Application. + * + * @property \EasyWeChat\OpenPlatform\Server\Guard $server + * @property \EasyWeChat\OpenPlatform\Auth\AccessToken $access_token + * @property \EasyWeChat\OpenPlatform\CodeTemplate\Client $code_template + * @property \EasyWeChat\OpenPlatform\Component\Client $component + * + * @method mixed handleAuthorize(string $authCode = null) + * @method mixed getAuthorizer(string $appId) + * @method mixed getAuthorizerOption(string $appId, string $name) + * @method mixed setAuthorizerOption(string $appId, string $name, string $value) + * @method mixed getAuthorizers(int $offset = 0, int $count = 500) + * @method mixed createPreAuthorizationCode() + */ +class Application extends ServiceContainer +{ + /** + * @var array + */ + protected $providers = [ + Auth\ServiceProvider::class, + Base\ServiceProvider::class, + Server\ServiceProvider::class, + CodeTemplate\ServiceProvider::class, + Component\ServiceProvider::class, + ]; + + /** + * @var array + */ + protected $defaultConfig = [ + 'http' => [ + 'timeout' => 5.0, + 'base_uri' => 'https://api.weixin.qq.com/', + ], + ]; + + /** + * Creates the officialAccount application. + * + * @param string $appId + * @param string|null $refreshToken + * @param \EasyWeChat\OpenPlatform\Authorizer\Auth\AccessToken|null $accessToken + * + * @return \EasyWeChat\OpenPlatform\Authorizer\OfficialAccount\Application + */ + public function officialAccount(string $appId, string $refreshToken = null, AccessToken $accessToken = null): OfficialAccount + { + $application = new OfficialAccount($this->getAuthorizerConfig($appId, $refreshToken), $this->getReplaceServices($accessToken) + [ + 'encryptor' => $this['encryptor'], + + 'account' => function ($app) { + return new AccountClient($app, $this); + }, + ]); + + $application->extend('oauth', function ($socialite) { + /* @var \Overtrue\Socialite\Providers\WeChatProvider $socialite */ + return $socialite->component(new ComponentDelegate($this)); + }); + + return $application; + } + + /** + * Creates the miniProgram application. + * + * @param string $appId + * @param string|null $refreshToken + * @param \EasyWeChat\OpenPlatform\Authorizer\Auth\AccessToken|null $accessToken + * + * @return \EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Application + */ + public function miniProgram(string $appId, string $refreshToken = null, AccessToken $accessToken = null): MiniProgram + { + return new MiniProgram($this->getAuthorizerConfig($appId, $refreshToken), $this->getReplaceServices($accessToken) + [ + 'encryptor' => function () { + return new Encryptor($this['config']['app_id'], $this['config']['token'], $this['config']['aes_key']); + }, + + 'auth' => function ($app) { + return new Client($app, $this); + }, + ]); + } + + /** + * Return the pre-authorization login page url. + * + * @param string $callbackUrl + * @param string|array|null $optional + * + * @return string + */ + public function getPreAuthorizationUrl(string $callbackUrl, $optional = []): string + { + // 兼容旧版 API 设计 + if (\is_string($optional)) { + $optional = [ + 'pre_auth_code' => $optional, + ]; + } else { + $optional['pre_auth_code'] = $this->createPreAuthorizationCode()['pre_auth_code']; + } + + $queries = \array_merge($optional, [ + 'component_appid' => $this['config']['app_id'], + 'redirect_uri' => $callbackUrl, + ]); + + return 'https://mp.weixin.qq.com/cgi-bin/componentloginpage?'.http_build_query($queries); + } + + /** + * Return the pre-authorization login page url (mobile). + * + * @param string $callbackUrl + * @param string|array|null $optional + * + * @return string + */ + public function getMobilePreAuthorizationUrl(string $callbackUrl, $optional = []): string + { + // 兼容旧版 API 设计 + if (\is_string($optional)) { + $optional = [ + 'pre_auth_code' => $optional, + ]; + } else { + $optional['pre_auth_code'] = $this->createPreAuthorizationCode()['pre_auth_code']; + } + + $queries = \array_merge($optional, [ + 'component_appid' => $this['config']['app_id'], + 'redirect_uri' => $callbackUrl, + 'action' => 'bindcomponent', + 'no_scan' => 1, + ]); + + return 'https://mp.weixin.qq.com/safe/bindcomponent?'.http_build_query($queries).'#wechat_redirect'; + } + + /** + * @param string $appId + * @param string|null $refreshToken + * + * @return array + */ + protected function getAuthorizerConfig(string $appId, string $refreshToken = null): array + { + return $this['config']->merge([ + 'component_app_id' => $this['config']['app_id'], + 'app_id' => $appId, + 'refresh_token' => $refreshToken, + ])->toArray(); + } + + /** + * @param \EasyWeChat\OpenPlatform\Authorizer\Auth\AccessToken|null $accessToken + * + * @return array + */ + protected function getReplaceServices(AccessToken $accessToken = null): array + { + $services = [ + 'access_token' => $accessToken ?: function ($app) { + return new AccessToken($app, $this); + }, + + 'server' => function ($app) { + return new Guard($app); + }, + ]; + + foreach (['cache', 'http_client', 'log', 'logger', 'request'] as $reuse) { + if (isset($this[$reuse])) { + $services[$reuse] = $this[$reuse]; + } + } + + return $services; + } + + /** + * Handle dynamic calls. + * + * @param string $method + * @param array $args + * + * @return mixed + */ + public function __call($method, $args) + { + return $this->base->$method(...$args); + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Auth/AccessToken.php b/vendor/overtrue/wechat/src/OpenPlatform/Auth/AccessToken.php new file mode 100644 index 0000000..c15d78d --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Auth/AccessToken.php @@ -0,0 +1,49 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Auth; + +use EasyWeChat\Kernel\AccessToken as BaseAccessToken; + +/** + * Class AccessToken. + * + * @author mingyoung + */ +class AccessToken extends BaseAccessToken +{ + /** + * @var string + */ + protected $requestMethod = 'POST'; + + /** + * @var string + */ + protected $tokenKey = 'component_access_token'; + + /** + * @var string + */ + protected $endpointToGetToken = 'cgi-bin/component/api_component_token'; + + /** + * @return array + */ + protected function getCredentials(): array + { + return [ + 'component_appid' => $this->app['config']['app_id'], + 'component_appsecret' => $this->app['config']['secret'], + 'component_verify_ticket' => $this->app['verify_ticket']->getTicket(), + ]; + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Auth/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Auth/ServiceProvider.php new file mode 100644 index 0000000..c60784b --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Auth/ServiceProvider.php @@ -0,0 +1,37 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Auth; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author mingyoung + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['verify_ticket'] = function ($app) { + return new VerifyTicket($app); + }; + + $app['access_token'] = function ($app) { + return new AccessToken($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Auth/VerifyTicket.php b/vendor/overtrue/wechat/src/OpenPlatform/Auth/VerifyTicket.php new file mode 100644 index 0000000..9ad04c7 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Auth/VerifyTicket.php @@ -0,0 +1,91 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Auth; + +use EasyWeChat\Kernel\Exceptions\RuntimeException; +use EasyWeChat\Kernel\Traits\InteractsWithCache; +use EasyWeChat\OpenPlatform\Application; + +/** + * Class VerifyTicket. + * + * @author mingyoung + */ +class VerifyTicket +{ + use InteractsWithCache; + + /** + * @var \EasyWeChat\OpenPlatform\Application + */ + protected $app; + + /** + * Constructor. + * + * @param \EasyWeChat\OpenPlatform\Application $app + */ + public function __construct(Application $app) + { + $this->app = $app; + } + + /** + * Put the credential `component_verify_ticket` in cache. + * + * @param string $ticket + * + * @return $this + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + * @throws \Psr\SimpleCache\InvalidArgumentException + */ + public function setTicket(string $ticket) + { + $this->getCache()->set($this->getCacheKey(), $ticket, 3600); + + if (!$this->getCache()->has($this->getCacheKey())) { + throw new RuntimeException('Failed to cache verify ticket.'); + } + + return $this; + } + + /** + * Get the credential `component_verify_ticket` from cache. + * + * @return string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + * @throws \Psr\SimpleCache\InvalidArgumentException + */ + public function getTicket(): string + { + if ($cached = $this->getCache()->get($this->getCacheKey())) { + return $cached; + } + + throw new RuntimeException('Credential "component_verify_ticket" does not exist in cache.'); + } + + /** + * Get cache key. + * + * @return string + */ + protected function getCacheKey(): string + { + return 'easywechat.open_platform.verify_ticket.'.$this->app['config']['app_id']; + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Aggregate/Account/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Aggregate/Account/Client.php new file mode 100644 index 0000000..b062e3a --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Aggregate/Account/Client.php @@ -0,0 +1,96 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Authorizer\Aggregate\Account; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author Scholer + */ +class Client extends BaseClient +{ + /** + * 创建开放平台帐号并绑定公众号/小程序. + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function create() + { + $params = [ + 'appid' => $this->app['config']['app_id'], + ]; + + return $this->httpPostJson('cgi-bin/open/create', $params); + } + + /** + * 将公众号/小程序绑定到开放平台帐号下. + * + * @param string $openAppId 开放平台帐号appid + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function bindTo(string $openAppId) + { + $params = [ + 'appid' => $this->app['config']['app_id'], + 'open_appid' => $openAppId, + ]; + + return $this->httpPostJson('cgi-bin/open/bind', $params); + } + + /** + * 将公众号/小程序从开放平台帐号下解绑. + * + * @param string $openAppId 开放平台帐号appid + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function unbindFrom(string $openAppId) + { + $params = [ + 'appid' => $this->app['config']['app_id'], + 'open_appid' => $openAppId, + ]; + + return $this->httpPostJson('cgi-bin/open/unbind', $params); + } + + /** + * 获取公众号/小程序所绑定的开放平台帐号. + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getBinding() + { + $params = [ + 'appid' => $this->app['config']['app_id'], + ]; + + return $this->httpPostJson('cgi-bin/open/get', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Aggregate/AggregateServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Aggregate/AggregateServiceProvider.php new file mode 100644 index 0000000..d93293d --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Aggregate/AggregateServiceProvider.php @@ -0,0 +1,22 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Authorizer\Aggregate; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +class AggregateServiceProvider implements ServiceProviderInterface +{ + public function register(Container $app) + { + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Auth/AccessToken.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Auth/AccessToken.php new file mode 100644 index 0000000..07b808c --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Auth/AccessToken.php @@ -0,0 +1,79 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Authorizer\Auth; + +use EasyWeChat\Kernel\AccessToken as BaseAccessToken; +use EasyWeChat\OpenPlatform\Application; +use Pimple\Container; + +/** + * Class AccessToken. + * + * @author mingyoung + */ +class AccessToken extends BaseAccessToken +{ + /** + * @var string + */ + protected $requestMethod = 'POST'; + + /** + * @var string + */ + protected $queryName = 'access_token'; + + /** + * {@inheritdoc}. + */ + protected $tokenKey = 'authorizer_access_token'; + + /** + * @var \EasyWeChat\OpenPlatform\Application + */ + protected $component; + + /** + * AuthorizerAccessToken constructor. + * + * @param \Pimple\Container $app + * @param \EasyWeChat\OpenPlatform\Application $component + */ + public function __construct(Container $app, Application $component) + { + parent::__construct($app); + + $this->component = $component; + } + + /** + * {@inheritdoc}. + */ + protected function getCredentials(): array + { + return [ + 'component_appid' => $this->component['config']['app_id'], + 'authorizer_appid' => $this->app['config']['app_id'], + 'authorizer_refresh_token' => $this->app['config']['refresh_token'], + ]; + } + + /** + * @return string + */ + public function getEndpoint(): string + { + return 'cgi-bin/component/api_authorizer_token?'.http_build_query([ + 'component_access_token' => $this->component['access_token']->getToken()['component_access_token'], + ]); + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Account/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Account/Client.php new file mode 100644 index 0000000..c23772d --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Account/Client.php @@ -0,0 +1,76 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Account; + +use EasyWeChat\OpenPlatform\Authorizer\Aggregate\Account\Client as BaseClient; + +/** + * Class Client. + * + * @author ClouderSky + */ +class Client extends BaseClient +{ + /** + * 获取账号基本信息. + */ + public function getBasicInfo() + { + return $this->httpPostJson('cgi-bin/account/getaccountbasicinfo'); + } + + /** + * 修改头像. + * + * @param string $mediaId 头像素材mediaId + * @param float $left 剪裁框左上角x坐标(取值范围:[0, 1]) + * @param float $top 剪裁框左上角y坐标(取值范围:[0, 1]) + * @param float $right 剪裁框右下角x坐标(取值范围:[0, 1]) + * @param float $bottom 剪裁框右下角y坐标(取值范围:[0, 1]) + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function updateAvatar( + string $mediaId, + float $left = 0.0, + float $top = 0.0, + float $right = 1.0, + float $bottom = 1.0 + ) { + $params = [ + 'head_img_media_id' => $mediaId, + 'x1' => $left, 'y1' => $top, 'x2' => $right, 'y2' => $bottom, + ]; + + return $this->httpPostJson('cgi-bin/account/modifyheadimage', $params); + } + + /** + * 修改功能介绍. + * + * @param string $signature 功能介绍(简介) + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function updateSignature(string $signature) + { + $params = ['signature' => $signature]; + + return $this->httpPostJson('cgi-bin/account/modifysignature', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Account/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Account/ServiceProvider.php new file mode 100644 index 0000000..f062954 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Account/ServiceProvider.php @@ -0,0 +1,25 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Account; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +class ServiceProvider implements ServiceProviderInterface +{ + public function register(Container $app) + { + $app['account'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Application.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Application.php new file mode 100644 index 0000000..b4b8c13 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Application.php @@ -0,0 +1,53 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram; + +use EasyWeChat\MiniProgram\Application as MiniProgram; +use EasyWeChat\OpenPlatform\Authorizer\Aggregate\AggregateServiceProvider; + +/** + * Class Application. + * + * @author mingyoung + * + * @property \EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Account\Client $account + * @property \EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Code\Client $code + * @property \EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Domain\Client $domain + * @property \EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Setting\Client $setting + * @property \EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Tester\Client $tester + */ +class Application extends MiniProgram +{ + /** + * Application constructor. + * + * @param array $config + * @param array $prepends + */ + public function __construct(array $config = [], array $prepends = []) + { + parent::__construct($config, $prepends); + + $providers = [ + AggregateServiceProvider::class, + Code\ServiceProvider::class, + Domain\ServiceProvider::class, + Account\ServiceProvider::class, + Setting\ServiceProvider::class, + Tester\ServiceProvider::class, + ]; + + foreach ($providers as $provider) { + $this->register(new $provider()); + } + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Auth/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Auth/Client.php new file mode 100644 index 0000000..05bb704 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Auth/Client.php @@ -0,0 +1,64 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Auth; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\ServiceContainer; +use EasyWeChat\OpenPlatform\Application; + +/** + * Class Client. + * + * @author mingyoung + */ +class Client extends BaseClient +{ + /** + * @var \EasyWeChat\OpenPlatform\Application + */ + protected $component; + + /** + * Client constructor. + * + * @param \EasyWeChat\Kernel\ServiceContainer $app + * @param \EasyWeChat\OpenPlatform\Application $component + */ + public function __construct(ServiceContainer $app, Application $component) + { + parent::__construct($app); + + $this->component = $component; + } + + /** + * Get session info by code. + * + * @param string $code + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function session(string $code) + { + $params = [ + 'appid' => $this->app['config']['app_id'], + 'js_code' => $code, + 'grant_type' => 'authorization_code', + 'component_appid' => $this->component['config']['app_id'], + 'component_access_token' => $this->component['access_token']->getToken()['component_access_token'], + ]; + + return $this->httpGet('sns/component/jscode2session', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Code/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Code/Client.php new file mode 100644 index 0000000..700e76a --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Code/Client.php @@ -0,0 +1,267 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Code; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author mingyoung + */ +class Client extends BaseClient +{ + /** + * @param int $templateId + * @param string $extJson + * @param string $version + * @param string $description + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function commit(int $templateId, string $extJson, string $version, string $description) + { + return $this->httpPostJson('wxa/commit', [ + 'template_id' => $templateId, + 'ext_json' => $extJson, + 'user_version' => $version, + 'user_desc' => $description, + ]); + } + + /** + * @param string|null $path + * + * @return \EasyWeChat\Kernel\Http\Response + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getQrCode(string $path = null) + { + return $this->requestRaw('wxa/get_qrcode', 'GET', [ + 'query' => ['path' => $path], + ]); + } + + /** + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function getCategory() + { + return $this->httpGet('wxa/get_category'); + } + + /** + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function getPage() + { + return $this->httpGet('wxa/get_page'); + } + + /** + * @param array $itemList + * @param string|null $feedbackInfo + * @param string|null $feedbackStuff + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function submitAudit(array $itemList, string $feedbackInfo = null, string $feedbackStuff = null) + { + return $this->httpPostJson('wxa/submit_audit', [ + 'item_list' => $itemList, + 'feedback_info' => $feedbackInfo, + 'feedback_stuff' => $feedbackStuff, + ]); + } + + /** + * @param int $auditId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getAuditStatus(int $auditId) + { + return $this->httpPostJson('wxa/get_auditstatus', [ + 'auditid' => $auditId, + ]); + } + + /** + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function getLatestAuditStatus() + { + return $this->httpGet('wxa/get_latest_auditstatus'); + } + + /** + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function release() + { + return $this->httpPostJson('wxa/release'); + } + + /** + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function withdrawAudit() + { + return $this->httpGet('wxa/undocodeaudit'); + } + + /** + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function rollbackRelease() + { + return $this->httpGet('wxa/revertcoderelease'); + } + + /** + * @param string $action + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function changeVisitStatus(string $action) + { + return $this->httpPostJson('wxa/change_visitstatus', [ + 'action' => $action, + ]); + } + + /** + * 分阶段发布. + * + * @param int $grayPercentage + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function grayRelease(int $grayPercentage) + { + return $this->httpPostJson('wxa/grayrelease', [ + 'gray_percentage' => $grayPercentage, + ]); + } + + /** + * 取消分阶段发布. + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function revertGrayRelease() + { + return $this->httpGet('wxa/revertgrayrelease'); + } + + /** + * 查询当前分阶段发布详情. + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function getGrayRelease() + { + return $this->httpGet('wxa/getgrayreleaseplan'); + } + + /** + * 查询当前设置的最低基础库版本及各版本用户占比. + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getSupportVersion() + { + return $this->httpPostJson('cgi-bin/wxopen/getweappsupportversion'); + } + + /** + * 设置最低基础库版本. + * + * @param string $version + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function setSupportVersion(string $version) + { + return $this->httpPostJson('cgi-bin/wxopen/setweappsupportversion', [ + 'version' => $version, + ]); + } + + /** + * 查询服务商的当月提审限额(quota)和加急次数. + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function queryQuota() + { + return $this->httpGet('wxa/queryquota'); + } + + /** + * 加急审核申请. + * + * @param int $auditId 审核单ID + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function speedupAudit(int $auditId) + { + return $this->httpPostJson('wxa/speedupaudit', [ + 'auditid' => $auditId, + ]); + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Code/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Code/ServiceProvider.php new file mode 100644 index 0000000..bce8611 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Code/ServiceProvider.php @@ -0,0 +1,25 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Code; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +class ServiceProvider implements ServiceProviderInterface +{ + public function register(Container $app) + { + $app['code'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Domain/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Domain/Client.php new file mode 100644 index 0000000..2a6547c --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Domain/Client.php @@ -0,0 +1,54 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Domain; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author mingyoung + */ +class Client extends BaseClient +{ + /** + * @param array $params + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function modify(array $params) + { + return $this->httpPostJson('wxa/modify_domain', $params); + } + + /** + * 设置小程序业务域名. + * + * @param array $domains + * @param string $action + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function setWebviewDomain(array $domains, $action = 'add') + { + return $this->httpPostJson('wxa/setwebviewdomain', [ + 'action' => $action, + 'webviewdomain' => $domains, + ]); + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Domain/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Domain/ServiceProvider.php new file mode 100644 index 0000000..4eaef13 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Domain/ServiceProvider.php @@ -0,0 +1,25 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Domain; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +class ServiceProvider implements ServiceProviderInterface +{ + public function register(Container $app) + { + $app['domain'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Setting/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Setting/Client.php new file mode 100644 index 0000000..77be099 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Setting/Client.php @@ -0,0 +1,248 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Setting; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author ClouderSky + */ +class Client extends BaseClient +{ + /** + * 获取账号可以设置的所有类目. + */ + public function getAllCategories() + { + return $this->httpPostJson('cgi-bin/wxopen/getallcategories'); + } + + /** + * 添加类目. + * + * @param array $categories 类目数组 + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function addCategories(array $categories) + { + $params = ['categories' => $categories]; + + return $this->httpPostJson('cgi-bin/wxopen/addcategory', $params); + } + + /** + * 删除类目. + * + * @param int $firstId 一级类目ID + * @param int $secondId 二级类目ID + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function deleteCategories(int $firstId, int $secondId) + { + $params = ['first' => $firstId, 'second' => $secondId]; + + return $this->httpPostJson('cgi-bin/wxopen/deletecategory', $params); + } + + /** + * 获取账号已经设置的所有类目. + */ + public function getCategories() + { + return $this->httpPostJson('cgi-bin/wxopen/getcategory'); + } + + /** + * 修改类目. + * + * @param array $category 单个类目 + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function updateCategory(array $category) + { + return $this->httpPostJson('cgi-bin/wxopen/modifycategory', $category); + } + + /** + * 小程序名称设置及改名. + * + * @param string $nickname 昵称 + * @param string $idCardMediaId 身份证照片素材ID + * @param string $licenseMediaId 组织机构代码证或营业执照素材ID + * @param array $otherStuffs 其他证明材料素材ID + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function setNickname( + string $nickname, + string $idCardMediaId = '', + string $licenseMediaId = '', + array $otherStuffs = [] + ) { + $params = [ + 'nick_name' => $nickname, + 'id_card' => $idCardMediaId, + 'license' => $licenseMediaId, + ]; + + for ($i = \count($otherStuffs) - 1; $i >= 0; --$i) { + $params['naming_other_stuff_'.($i + 1)] = $otherStuffs[$i]; + } + + return $this->httpPostJson('wxa/setnickname', $params); + } + + /** + * 小程序改名审核状态查询. + * + * @param int $auditId 审核单id + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getNicknameAuditStatus($auditId) + { + $params = ['audit_id' => $auditId]; + + return $this->httpPostJson('wxa/api_wxa_querynickname', $params); + } + + /** + * 微信认证名称检测. + * + * @param string $nickname 名称(昵称) + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function isAvailableNickname($nickname) + { + $params = ['nick_name' => $nickname]; + + return $this->httpPostJson( + 'cgi-bin/wxverify/checkwxverifynickname', $params); + } + + /** + * 查询小程序是否可被搜索. + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getSearchStatus() + { + return $this->httpGet('wxa/getwxasearchstatus'); + } + + /** + * 设置小程序可被搜素. + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function setSearchable() + { + return $this->httpPostJson('wxa/changewxasearchstatus', [ + 'status' => 0, + ]); + } + + /** + * 设置小程序不可被搜素. + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function setUnsearchable() + { + return $this->httpPostJson('wxa/changewxasearchstatus', [ + 'status' => 1, + ]); + } + + /** + * 获取展示的公众号信息. + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getDisplayedOfficialAccount() + { + return $this->httpGet('wxa/getshowwxaitem'); + } + + /** + * 设置展示的公众号. + * + * @param string|bool $appid + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function setDisplayedOfficialAccount($appid) + { + return $this->httpPostJson('wxa/updateshowwxaitem', [ + 'appid' => $appid ?: null, + 'wxa_subscribe_biz_flag' => $appid ? 1 : 0, + ]); + } + + /** + * 获取可以用来设置的公众号列表. + * + * @param int $page + * @param int $num + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getDisplayableOfficialAccounts(int $page, int $num) + { + return $this->httpGet('wxa/getwxamplinkforshow', [ + 'page' => $page, + 'num' => $num, + ]); + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Setting/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Setting/ServiceProvider.php new file mode 100644 index 0000000..917aec5 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Setting/ServiceProvider.php @@ -0,0 +1,25 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Setting; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +class ServiceProvider implements ServiceProviderInterface +{ + public function register(Container $app) + { + $app['setting'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Tester/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Tester/Client.php new file mode 100644 index 0000000..011c933 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Tester/Client.php @@ -0,0 +1,72 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Tester; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author caikeal + */ +class Client extends BaseClient +{ + /** + * 绑定小程序体验者. + * + * @param string $wechatId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function bind(string $wechatId) + { + return $this->httpPostJson('wxa/bind_tester', [ + 'wechatid' => $wechatId, + ]); + } + + /** + * 解绑小程序体验者. + * + * @param string $wechatId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function unbind(string $wechatId) + { + return $this->httpPostJson('wxa/unbind_tester', [ + 'wechatid' => $wechatId, + ]); + } + + /** + * 获取体验者列表. + * + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function list() + { + return $this->httpPostJson('wxa/memberauth', [ + 'action' => 'get_experiencer', + ]); + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Tester/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Tester/ServiceProvider.php new file mode 100644 index 0000000..ff1ffb3 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Tester/ServiceProvider.php @@ -0,0 +1,25 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Tester; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +class ServiceProvider implements ServiceProviderInterface +{ + public function register(Container $app) + { + $app['tester'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/Account/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/Account/Client.php new file mode 100644 index 0000000..9e7b38b --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/Account/Client.php @@ -0,0 +1,81 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Authorizer\OfficialAccount\Account; + +use EasyWeChat\Kernel\ServiceContainer; +use EasyWeChat\OpenPlatform\Application; +use EasyWeChat\OpenPlatform\Authorizer\Aggregate\Account\Client as BaseClient; + +/** + * Class Client. + * + * @author Keal + */ +class Client extends BaseClient +{ + /** + * @var \EasyWeChat\OpenPlatform\Application + */ + protected $component; + + /** + * Client constructor. + * + * @param \EasyWeChat\Kernel\ServiceContainer $app + * @param \EasyWeChat\OpenPlatform\Application $component + */ + public function __construct(ServiceContainer $app, Application $component) + { + parent::__construct($app); + + $this->component = $component; + } + + /** + * 从第三方平台跳转至微信公众平台授权注册页面, 授权注册小程序. + * + * @param string $callbackUrl + * @param bool $copyWxVerify + * + * @return string + */ + public function getFastRegistrationUrl(string $callbackUrl, bool $copyWxVerify = true): string + { + $queries = [ + 'copy_wx_verify' => $copyWxVerify, + 'component_appid' => $this->component['config']['app_id'], + 'appid' => $this->app['config']['app_id'], + 'redirect_uri' => $callbackUrl, + ]; + + return 'https://mp.weixin.qq.com/cgi-bin/fastregisterauth?'.http_build_query($queries); + } + + /** + * 小程序快速注册. + * + * @param string $ticket + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function register(string $ticket) + { + $params = [ + 'ticket' => $ticket, + ]; + + return $this->httpPostJson('cgi-bin/account/fastregister', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/Application.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/Application.php new file mode 100644 index 0000000..08509d2 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/Application.php @@ -0,0 +1,46 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Authorizer\OfficialAccount; + +use EasyWeChat\OfficialAccount\Application as OfficialAccount; +use EasyWeChat\OpenPlatform\Authorizer\Aggregate\AggregateServiceProvider; + +/** + * Class Application. + * + * @author mingyoung + * + * @property \EasyWeChat\OpenPlatform\Authorizer\OfficialAccount\Account\Client $account + * @property \EasyWeChat\OpenPlatform\Authorizer\OfficialAccount\MiniProgram\Client $mini_program + */ +class Application extends OfficialAccount +{ + /** + * Application constructor. + * + * @param array $config + * @param array $prepends + */ + public function __construct(array $config = [], array $prepends = []) + { + parent::__construct($config, $prepends); + + $providers = [ + AggregateServiceProvider::class, + MiniProgram\ServiceProvider::class, + ]; + + foreach ($providers as $provider) { + $this->register(new $provider()); + } + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/MiniProgram/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/MiniProgram/Client.php new file mode 100644 index 0000000..d32b51b --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/MiniProgram/Client.php @@ -0,0 +1,77 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Authorizer\OfficialAccount\MiniProgram; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author Keal + */ +class Client extends BaseClient +{ + /** + * 获取公众号关联的小程序. + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function list() + { + return $this->httpPostJson('cgi-bin/wxopen/wxamplinkget'); + } + + /** + * 关联小程序. + * + * @param string $appId + * @param bool $notifyUsers + * @param bool $showProfile + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function link(string $appId, bool $notifyUsers = true, bool $showProfile = false) + { + $params = [ + 'appid' => $appId, + 'notify_users' => (string) $notifyUsers, + 'show_profile' => (string) $showProfile, + ]; + + return $this->httpPostJson('cgi-bin/wxopen/wxamplink', $params); + } + + /** + * 解除已关联的小程序. + * + * @param string $appId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function unlink(string $appId) + { + $params = [ + 'appid' => $appId, + ]; + + return $this->httpPostJson('cgi-bin/wxopen/wxampunlink', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/MiniProgram/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/MiniProgram/ServiceProvider.php new file mode 100644 index 0000000..31ce10b --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/MiniProgram/ServiceProvider.php @@ -0,0 +1,25 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Authorizer\OfficialAccount\MiniProgram; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +class ServiceProvider implements ServiceProviderInterface +{ + public function register(Container $app) + { + $app['mini_program'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/OAuth/ComponentDelegate.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/OAuth/ComponentDelegate.php new file mode 100644 index 0000000..a79d177 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/OAuth/ComponentDelegate.php @@ -0,0 +1,54 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Authorizer\OfficialAccount\OAuth; + +use EasyWeChat\OpenPlatform\Application; +use Overtrue\Socialite\WeChatComponentInterface; + +/** + * Class ComponentDelegate. + * + * @author mingyoung + */ +class ComponentDelegate implements WeChatComponentInterface +{ + /** + * @var \EasyWeChat\OpenPlatform\Application + */ + protected $app; + + /** + * ComponentDelegate Constructor. + * + * @param \EasyWeChat\OpenPlatform\Application $app + */ + public function __construct(Application $app) + { + $this->app = $app; + } + + /** + * @return string + */ + public function getAppId() + { + return $this->app['config']['app_id']; + } + + /** + * @return string + */ + public function getToken() + { + return $this->app['access_token']->getToken()['component_access_token']; + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Server/Guard.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Server/Guard.php new file mode 100644 index 0000000..d7efbd3 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Server/Guard.php @@ -0,0 +1,32 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Authorizer\Server; + +use EasyWeChat\Kernel\ServerGuard; + +/** + * Class Guard. + * + * @author mingyoung + */ +class Guard extends ServerGuard +{ + /** + * Get token from OpenPlatform encryptor. + * + * @return string + */ + protected function getToken() + { + return $this->app['encryptor']->getToken(); + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Base/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Base/Client.php new file mode 100644 index 0000000..ec78a5f --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Base/Client.php @@ -0,0 +1,166 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Base; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author mingyoung + */ +class Client extends BaseClient +{ + /** + * Get authorization info. + * + * @param string|null $authCode + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function handleAuthorize(string $authCode = null) + { + $params = [ + 'component_appid' => $this->app['config']['app_id'], + 'authorization_code' => $authCode ?? $this->app['request']->get('auth_code'), + ]; + + return $this->httpPostJson('cgi-bin/component/api_query_auth', $params); + } + + /** + * Get authorizer info. + * + * @param string $appId + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getAuthorizer(string $appId) + { + $params = [ + 'component_appid' => $this->app['config']['app_id'], + 'authorizer_appid' => $appId, + ]; + + return $this->httpPostJson('cgi-bin/component/api_get_authorizer_info', $params); + } + + /** + * Get options. + * + * @param string $appId + * @param string $name + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getAuthorizerOption(string $appId, string $name) + { + $params = [ + 'component_appid' => $this->app['config']['app_id'], + 'authorizer_appid' => $appId, + 'option_name' => $name, + ]; + + return $this->httpPostJson('cgi-bin/component/api_get_authorizer_option', $params); + } + + /** + * Set authorizer option. + * + * @param string $appId + * @param string $name + * @param string $value + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function setAuthorizerOption(string $appId, string $name, string $value) + { + $params = [ + 'component_appid' => $this->app['config']['app_id'], + 'authorizer_appid' => $appId, + 'option_name' => $name, + 'option_value' => $value, + ]; + + return $this->httpPostJson('cgi-bin/component/api_set_authorizer_option', $params); + } + + /** + * Get authorizer list. + * + * @param int $offset + * @param int $count + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getAuthorizers($offset = 0, $count = 500) + { + $params = [ + 'component_appid' => $this->app['config']['app_id'], + 'offset' => $offset, + 'count' => $count, + ]; + + return $this->httpPostJson('cgi-bin/component/api_get_authorizer_list', $params); + } + + /** + * Create pre-authorization code. + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function createPreAuthorizationCode() + { + $params = [ + 'component_appid' => $this->app['config']['app_id'], + ]; + + return $this->httpPostJson('cgi-bin/component/api_create_preauthcode', $params); + } + + /** + * OpenPlatform Clear quota. + * + * @see https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419318587 + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function clearQuota() + { + $params = [ + 'component_appid' => $this->app['config']['app_id'], + ]; + + return $this->httpPostJson('cgi-bin/component/clear_quota', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Base/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Base/ServiceProvider.php new file mode 100644 index 0000000..e647c41 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Base/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Base; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author mingyoung + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['base'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/CodeTemplate/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/CodeTemplate/Client.php new file mode 100644 index 0000000..2929cb6 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/CodeTemplate/Client.php @@ -0,0 +1,86 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\CodeTemplate; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author caikeal + */ +class Client extends BaseClient +{ + /** + * 获取草稿箱内的所有临时代码草稿 + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getDrafts() + { + return $this->httpGet('wxa/gettemplatedraftlist'); + } + + /** + * 将草稿箱的草稿选为小程序代码模版. + * + * @param int $draftId + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function createFromDraft(int $draftId) + { + $params = [ + 'draft_id' => $draftId, + ]; + + return $this->httpPostJson('wxa/addtotemplate', $params); + } + + /** + * 获取代码模版库中的所有小程序代码模版. + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function list() + { + return $this->httpGet('wxa/gettemplatelist'); + } + + /** + * 删除指定小程序代码模版. + * + * @param string $templateId + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function delete($templateId) + { + $params = [ + 'template_id' => $templateId, + ]; + + return $this->httpPostJson('wxa/deletetemplate', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/CodeTemplate/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/CodeTemplate/ServiceProvider.php new file mode 100644 index 0000000..ab543a7 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/CodeTemplate/ServiceProvider.php @@ -0,0 +1,25 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\CodeTemplate; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +class ServiceProvider implements ServiceProviderInterface +{ + public function register(Container $app) + { + $app['code_template'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Component/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Component/Client.php new file mode 100644 index 0000000..eafb3f5 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Component/Client.php @@ -0,0 +1,60 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Component; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author dudashuang + */ +class Client extends BaseClient +{ + /** + * 通过法人微信快速创建小程序. + * + * @param array $params + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function registerMiniProgram(array $params) + { + return $this->httpPostJson('cgi-bin/component/fastregisterweapp', $params, ['action' => 'create']); + } + + /** + * 查询创建任务状态. + * + * @param string $companyName + * @param string $legalPersonaWechat + * @param string $legalPersonaName + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getRegistrationStatus(string $companyName, string $legalPersonaWechat, string $legalPersonaName) + { + $params = [ + 'name' => $companyName, + 'legal_persona_wechat' => $legalPersonaWechat, + 'legal_persona_name' => $legalPersonaName, + ]; + + return $this->httpPostJson('cgi-bin/component/fastregisterweapp', $params, ['action' => 'search']); + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Component/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Component/ServiceProvider.php new file mode 100644 index 0000000..83e309f --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Component/ServiceProvider.php @@ -0,0 +1,25 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Component; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +class ServiceProvider implements ServiceProviderInterface +{ + public function register(Container $app) + { + $app['component'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Server/Guard.php b/vendor/overtrue/wechat/src/OpenPlatform/Server/Guard.php new file mode 100644 index 0000000..ee306ab --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Server/Guard.php @@ -0,0 +1,65 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Server; + +use function EasyWeChat\Kernel\data_get; +use EasyWeChat\Kernel\ServerGuard; +use EasyWeChat\OpenPlatform\Server\Handlers\Authorized; +use EasyWeChat\OpenPlatform\Server\Handlers\Unauthorized; +use EasyWeChat\OpenPlatform\Server\Handlers\UpdateAuthorized; +use EasyWeChat\OpenPlatform\Server\Handlers\VerifyTicketRefreshed; +use Symfony\Component\HttpFoundation\Response; + +/** + * Class Guard. + * + * @author mingyoung + */ +class Guard extends ServerGuard +{ + const EVENT_AUTHORIZED = 'authorized'; + const EVENT_UNAUTHORIZED = 'unauthorized'; + const EVENT_UPDATE_AUTHORIZED = 'updateauthorized'; + const EVENT_COMPONENT_VERIFY_TICKET = 'component_verify_ticket'; + const EVENT_THIRD_FAST_REGISTERED = 'notify_third_fasteregister'; + + /** + * @return \Symfony\Component\HttpFoundation\Response + * + * @throws \EasyWeChat\Kernel\Exceptions\BadRequestException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + protected function resolve(): Response + { + $this->registerHandlers(); + + $message = $this->getMessage(); + + if ($infoType = data_get($message, 'InfoType')) { + $this->dispatch($infoType, $message); + } + + return new Response(static::SUCCESS_EMPTY_RESPONSE); + } + + /** + * Register event handlers. + */ + protected function registerHandlers() + { + $this->on(self::EVENT_AUTHORIZED, Authorized::class); + $this->on(self::EVENT_UNAUTHORIZED, Unauthorized::class); + $this->on(self::EVENT_UPDATE_AUTHORIZED, UpdateAuthorized::class); + $this->on(self::EVENT_COMPONENT_VERIFY_TICKET, VerifyTicketRefreshed::class); + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/Authorized.php b/vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/Authorized.php new file mode 100644 index 0000000..fe7f9c6 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/Authorized.php @@ -0,0 +1,30 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Server\Handlers; + +use EasyWeChat\Kernel\Contracts\EventHandlerInterface; + +/** + * Class Authorized. + * + * @author mingyoung + */ +class Authorized implements EventHandlerInterface +{ + /** + * {@inheritdoc}. + */ + public function handle($payload = null) + { + // Do nothing for the time being. + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/Unauthorized.php b/vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/Unauthorized.php new file mode 100644 index 0000000..158228b --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/Unauthorized.php @@ -0,0 +1,30 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Server\Handlers; + +use EasyWeChat\Kernel\Contracts\EventHandlerInterface; + +/** + * Class Unauthorized. + * + * @author mingyoung + */ +class Unauthorized implements EventHandlerInterface +{ + /** + * {@inheritdoc}. + */ + public function handle($payload = null) + { + // Do nothing for the time being. + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/UpdateAuthorized.php b/vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/UpdateAuthorized.php new file mode 100644 index 0000000..e73caa5 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/UpdateAuthorized.php @@ -0,0 +1,30 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Server\Handlers; + +use EasyWeChat\Kernel\Contracts\EventHandlerInterface; + +/** + * Class UpdateAuthorized. + * + * @author mingyoung + */ +class UpdateAuthorized implements EventHandlerInterface +{ + /** + * {@inheritdoc} + */ + public function handle($payload = null) + { + // Do nothing for the time being. + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/VerifyTicketRefreshed.php b/vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/VerifyTicketRefreshed.php new file mode 100644 index 0000000..e5ce58d --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/VerifyTicketRefreshed.php @@ -0,0 +1,48 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Server\Handlers; + +use EasyWeChat\Kernel\Contracts\EventHandlerInterface; +use EasyWeChat\OpenPlatform\Application; + +/** + * Class VerifyTicketRefreshed. + * + * @author mingyoung + */ +class VerifyTicketRefreshed implements EventHandlerInterface +{ + /** + * @var \EasyWeChat\OpenPlatform\Application + */ + protected $app; + + /** + * Constructor. + * + * @param \EasyWeChat\OpenPlatform\Application $app + */ + public function __construct(Application $app) + { + $this->app = $app; + } + + /** + * {@inheritdoc}. + */ + public function handle($payload = null) + { + if (!empty($payload['ComponentVerifyTicket'])) { + $this->app['verify_ticket']->setTicket($payload['ComponentVerifyTicket']); + } + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Server/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Server/ServiceProvider.php new file mode 100644 index 0000000..7747061 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Server/ServiceProvider.php @@ -0,0 +1,34 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Server; + +use EasyWeChat\Kernel\Encryptor; +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +class ServiceProvider implements ServiceProviderInterface +{ + public function register(Container $app) + { + $app['encryptor'] = function ($app) { + return new Encryptor( + $app['config']['app_id'], + $app['config']['token'], + $app['config']['aes_key'] + ); + }; + + $app['server'] = function ($app) { + return new Guard($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OpenWork/Application.php b/vendor/overtrue/wechat/src/OpenWork/Application.php new file mode 100644 index 0000000..bde1421 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenWork/Application.php @@ -0,0 +1,85 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenWork; + +use EasyWeChat\Kernel\ServiceContainer; +use EasyWeChat\OpenWork\Work\Application as Work; + +/** + * Application. + * + * @author xiaomin + * + * @property \EasyWeChat\OpenWork\Server\Guard $server + * @property \EasyWeChat\OpenWork\Corp\Client $corp + * @property \EasyWeChat\OpenWork\Provider\Client $provider + * @property \EasyWeChat\OpenWork\SuiteAuth\AccessToken $suite_access_token + * @property \EasyWeChat\OpenWork\Auth\AccessToken $provider_access_token + * @property \EasyWeChat\OpenWork\SuiteAuth\SuiteTicket $suite_ticket + * @property \EasyWeChat\OpenWork\MiniProgram\Auth\Client $mini_program + */ +class Application extends ServiceContainer +{ + /** + * @var array + */ + protected $providers = [ + Auth\ServiceProvider::class, + SuiteAuth\ServiceProvider::class, + Server\ServiceProvider::class, + Corp\ServiceProvider::class, + Provider\ServiceProvider::class, + MiniProgram\ServiceProvider::class, + ]; + + /** + * @var array + */ + protected $defaultConfig = [ + // http://docs.guzzlephp.org/en/stable/request-options.html + 'http' => [ + 'base_uri' => 'https://qyapi.weixin.qq.com/', + ], + ]; + + /** + * Creates the miniProgram application. + * + * @return \EasyWeChat\Work\MiniProgram\Application + */ + public function miniProgram(): \EasyWeChat\Work\MiniProgram\Application + { + return new \EasyWeChat\Work\MiniProgram\Application($this->getConfig()); + } + + /** + * @param string $authCorpId 企业 corp_id + * @param string $permanentCode 企业永久授权码 + * + * @return Work + */ + public function work(string $authCorpId, string $permanentCode): Work + { + return new Work($authCorpId, $permanentCode, $this); + } + + /** + * @param string $method + * @param array $arguments + * + * @return mixed + */ + public function __call($method, $arguments) + { + return $this['base']->$method(...$arguments); + } +} diff --git a/vendor/overtrue/wechat/src/OpenWork/Auth/AccessToken.php b/vendor/overtrue/wechat/src/OpenWork/Auth/AccessToken.php new file mode 100644 index 0000000..3f00a25 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenWork/Auth/AccessToken.php @@ -0,0 +1,52 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenWork\Auth; + +use EasyWeChat\Kernel\AccessToken as BaseAccessToken; + +/** + * AccessToken. + * + * @author xiaomin + */ +class AccessToken extends BaseAccessToken +{ + protected $requestMethod = 'POST'; + + /** + * @var string + */ + protected $endpointToGetToken = 'cgi-bin/service/get_provider_token'; + + /** + * @var string + */ + protected $tokenKey = 'provider_access_token'; + + /** + * @var string + */ + protected $cachePrefix = 'easywechat.kernel.provider_access_token.'; + + /** + * Credential for get token. + * + * @return array + */ + protected function getCredentials(): array + { + return [ + 'corpid' => $this->app['config']['corp_id'], //服务商的corpid + 'provider_secret' => $this->app['config']['secret'], + ]; + } +} diff --git a/vendor/overtrue/wechat/src/OpenWork/Auth/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenWork/Auth/ServiceProvider.php new file mode 100644 index 0000000..78aba05 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenWork/Auth/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenWork\Auth; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * ServiceProvider. + * + * @author xiaomin + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + isset($app['provider_access_token']) || $app['provider_access_token'] = function ($app) { + return new AccessToken($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OpenWork/Corp/Client.php b/vendor/overtrue/wechat/src/OpenWork/Corp/Client.php new file mode 100644 index 0000000..689f573 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenWork/Corp/Client.php @@ -0,0 +1,217 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenWork\Corp; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\ServiceContainer; + +/** + * Client. + * + * @author xiaomin + */ +class Client extends BaseClient +{ + /** + * Client constructor. + * 三方接口有三个access_token,这里用的是suite_access_token. + * + * @param \EasyWeChat\Kernel\ServiceContainer $app + */ + public function __construct(ServiceContainer $app) + { + parent::__construct($app, $app['suite_access_token']); + } + + /** + * 企业微信安装应用授权 url. + * + * @param string $preAuthCode 预授权码 + * @param string $redirectUri 回调地址 + * @param string $state + * + * @return string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function getPreAuthorizationUrl(string $preAuthCode = '', string $redirectUri = '', string $state = '') + { + $redirectUri || $redirectUri = $this->app->config['redirect_uri_install']; + $preAuthCode || $preAuthCode = $this->getPreAuthCode()['pre_auth_code']; + $state || $state = rand(); + + $params = [ + 'suite_id' => $this->app['config']['suite_id'], + 'redirect_uri' => $redirectUri, + 'pre_auth_code' => $preAuthCode, + 'state' => $state, + ]; + + return 'https://open.work.weixin.qq.com/3rdapp/install?'.http_build_query($params); + } + + /** + * 获取预授权码. + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function getPreAuthCode() + { + return $this->httpGet('cgi-bin/service/get_pre_auth_code'); + } + + /** + * 设置授权配置. + * 该接口可对某次授权进行配置. + * + * @param string $preAuthCode + * @param array $sessionInfo + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function setSession(string $preAuthCode, array $sessionInfo) + { + $params = [ + 'pre_auth_code' => $preAuthCode, + 'session_info' => $sessionInfo, + ]; + + return $this->httpPostJson('cgi-bin/service/set_session_info', $params); + } + + /** + * 获取企业永久授权码. + * + * @param string $authCode 临时授权码,会在授权成功时附加在redirect_uri中跳转回第三方服务商网站,或通过回调推送给服务商。长度为64至512个字节 + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getPermanentByCode(string $authCode) + { + $params = [ + 'auth_code' => $authCode, + ]; + + return $this->httpPostJson('cgi-bin/service/get_permanent_code', $params); + } + + /** + * 获取企业授权信息. + * + * @param string $authCorpId + * @param string $permanentCode + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getAuthorization(string $authCorpId, string $permanentCode) + { + $params = [ + 'auth_corpid' => $authCorpId, + 'permanent_code' => $permanentCode, + ]; + + return $this->httpPostJson('cgi-bin/service/get_auth_info', $params); + } + + /** + * 获取应用的管理员列表. + * + * @param string $authCorpId + * @param string $agentId + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getManagers(string $authCorpId, string $agentId) + { + $params = [ + 'auth_corpid' => $authCorpId, + 'agentid' => $agentId, + ]; + + return $this->httpPostJson('cgi-bin/service/get_admin_list', $params); + } + + /** + * 获取登录url. + * + * @param string $redirectUri + * @param string $scope + * @param string|null $state + * + * @return string + */ + public function getOAuthRedirectUrl(string $redirectUri = '', string $scope = 'snsapi_userinfo', string $state = null) + { + $redirectUri || $redirectUri = $this->app->config['redirect_uri_oauth']; + $state || $state = rand(); + $params = [ + 'appid' => $this->app['config']['suite_id'], + 'redirect_uri' => $redirectUri, + 'response_type' => 'code', + 'scope' => $scope, + 'state' => $state, + ]; + + return 'https://open.weixin.qq.com/connect/oauth2/authorize?'.http_build_query($params).'#wechat_redirect'; + } + + /** + * 第三方根据code获取企业成员信息. + * + * @param string $code + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function getUserByCode(string $code) + { + $params = [ + 'code' => $code, + ]; + + return $this->httpGet('cgi-bin/service/getuserinfo3rd', $params); + } + + /** + * 第三方使用user_ticket获取成员详情. + * + * @param string $userTicket + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getUserByTicket(string $userTicket) + { + $params = [ + 'user_ticket' => $userTicket, + ]; + + return $this->httpPostJson('cgi-bin/service/getuserdetail3rd', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OpenWork/Corp/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenWork/Corp/ServiceProvider.php new file mode 100644 index 0000000..0cc8ea9 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenWork/Corp/ServiceProvider.php @@ -0,0 +1,38 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenWork\Corp; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * ServiceProvider. + * + * @author xiaomin + */ +class ServiceProvider 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 \Pimple\Container $app + */ + public function register(Container $app) + { + isset($app['corp']) || $app['corp'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OpenWork/MiniProgram/Client.php b/vendor/overtrue/wechat/src/OpenWork/MiniProgram/Client.php new file mode 100644 index 0000000..29dae19 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenWork/MiniProgram/Client.php @@ -0,0 +1,50 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenWork\MiniProgram; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\ServiceContainer; + +/** + * Class Client. + */ +class Client extends BaseClient +{ + /** + * Client constructor. + * + * @param \EasyWeChat\Kernel\ServiceContainer $app + */ + public function __construct(ServiceContainer $app) + { + parent::__construct($app, $app['suite_access_token']); + } + + /** + * Get session info by code. + * + * @param string $code + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function session(string $code) + { + $params = [ + 'js_code' => $code, + 'grant_type' => 'authorization_code', + ]; + + return $this->httpGet('cgi-bin/service/miniprogram/jscode2session', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OpenWork/MiniProgram/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenWork/MiniProgram/ServiceProvider.php new file mode 100644 index 0000000..8e7dba2 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenWork/MiniProgram/ServiceProvider.php @@ -0,0 +1,31 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenWork\MiniProgram; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * ServiceProvider. + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + !isset($app['mini_program']) && $app['mini_program'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OpenWork/Provider/Client.php b/vendor/overtrue/wechat/src/OpenWork/Provider/Client.php new file mode 100644 index 0000000..690d988 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenWork/Provider/Client.php @@ -0,0 +1,206 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenWork\Provider; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\ServiceContainer; + +/** + * Client. + * + * @author xiaomin + */ +class Client extends BaseClient +{ + /** + * Client constructor. + * + * + * @param ServiceContainer $app + */ + public function __construct(ServiceContainer $app) + { + parent::__construct($app, $app['provider_access_token']); + } + + /** + * 单点登录 - 获取登录的地址. + * + * @param string $redirectUri + * @param string $userType + * @param string $state + * + * @return string + */ + public function getLoginUrl(string $redirectUri = '', string $userType = 'admin', string $state = '') + { + $redirectUri || $redirectUri = $this->app->config['redirect_uri_single']; + $state || $state = rand(); + $params = [ + 'appid' => $this->app['config']['corp_id'], + 'redirect_uri' => $redirectUri, + 'usertype' => $userType, + 'state' => $state, + ]; + + return 'https://open.work.weixin.qq.com/wwopen/sso/3rd_qrConnect?'.http_build_query($params); + } + + /** + * 单点登录 - 获取登录用户信息. + * + * @param string $authCode + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getLoginInfo(string $authCode) + { + $params = [ + 'auth_code' => $authCode, + ]; + + return $this->httpPostJson('cgi-bin/service/get_login_info', $params); + } + + /** + * 获取注册定制化URL. + * + * @param string $registerCode + * + * @return string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + public function getRegisterUri(string $registerCode = '') + { + if (!$registerCode) { + /** @var array $response */ + $response = $this->detectAndCastResponseToType($this->getRegisterCode(), 'array'); + + $registerCode = $response['register_code']; + } + + $params = ['register_code' => $registerCode]; + + return 'https://open.work.weixin.qq.com/3rdservice/wework/register?'.http_build_query($params); + } + + /** + * 获取注册码. + * + * @param string $corpName + * @param string $adminName + * @param string $adminMobile + * @param string $state + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getRegisterCode( + string $corpName = '', + string $adminName = '', + string $adminMobile = '', + string $state = '' + ) { + $params = []; + $params['template_id'] = $this->app['config']['reg_template_id']; + !empty($corpName) && $params['corp_name'] = $corpName; + !empty($adminName) && $params['admin_name'] = $adminName; + !empty($adminMobile) && $params['admin_mobile'] = $adminMobile; + !empty($state) && $params['state'] = $state; + + return $this->httpPostJson('cgi-bin/service/get_register_code', $params); + } + + /** + * 查询注册状态. + * + * Desc:该API用于查询企业注册状态,企业注册成功返回注册信息. + * + * @param string $registerCode + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getRegisterInfo(string $registerCode) + { + $params = [ + 'register_code' => $registerCode, + ]; + + return $this->httpPostJson('cgi-bin/service/get_register_info', $params); + } + + /** + * 设置授权应用可见范围. + * + * Desc:调用该接口前提是开启通讯录迁移,收到授权成功通知后可调用。 + * 企业注册初始化安装应用后,应用默认可见范围为根部门。 + * 如需修改应用可见范围,服务商可以调用该接口设置授权应用的可见范围。 + * 该接口只能使用注册完成回调事件或者查询注册状态返回的access_token。 + * 调用设置通讯录同步完成后或者access_token超过30分钟失效(即解除通讯录锁定状态)则不能继续调用该接口。 + * + * @param string $accessToken + * @param string $agentId + * @param array $allowUser + * @param array $allowParty + * @param array $allowTag + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function setAgentScope( + string $accessToken, + string $agentId, + array $allowUser = [], + array $allowParty = [], + array $allowTag = [] + ) { + $params = [ + 'agentid' => $agentId, + 'allow_user' => $allowUser, + 'allow_party' => $allowParty, + 'allow_tag' => $allowTag, + 'access_token' => $accessToken, + ]; + + return $this->httpGet('cgi-bin/agent/set_scope', $params); + } + + /** + * 设置通讯录同步完成. + * + * Desc:该API用于设置通讯录同步完成,解除通讯录锁定状态,同时使通讯录迁移access_token失效。 + * + * @param string $accessToken + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function contactSyncSuccess(string $accessToken) + { + $params = ['access_token' => $accessToken]; + + return $this->httpGet('cgi-bin/sync/contact_sync_success', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OpenWork/Provider/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenWork/Provider/ServiceProvider.php new file mode 100644 index 0000000..2185fec --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenWork/Provider/ServiceProvider.php @@ -0,0 +1,36 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenWork\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * ServiceProvider. + * + * @author xiaomin + */ +class ServiceProvider implements ServiceProviderInterface +{ + protected $app; + + /** + * @param Container $app + */ + public function register(Container $app) + { + $this->app = $app; + isset($app['provider']) || $app['provider'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OpenWork/Server/Guard.php b/vendor/overtrue/wechat/src/OpenWork/Server/Guard.php new file mode 100644 index 0000000..5700a77 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenWork/Server/Guard.php @@ -0,0 +1,68 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenWork\Server; + +use EasyWeChat\Kernel\Encryptor; +use EasyWeChat\Kernel\ServerGuard; + +/** + * Guard. + * + * @author xiaomin + */ +class Guard extends ServerGuard +{ + /** + * @var bool + */ + protected $alwaysValidate = true; + + /** + * @return $this + */ + public function validate() + { + return $this; + } + + /** + * @return bool + */ + protected function shouldReturnRawResponse(): bool + { + return !is_null($this->app['request']->get('echostr')); + } + + protected function isSafeMode(): bool + { + return true; + } + + /** + * @param array $message + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + protected function decryptMessage(array $message) + { + $encryptor = new Encryptor($message['ToUserName'], $this->app['config']->get('token'), $this->app['config']->get('aes_key')); + + return $message = $encryptor->decrypt( + $message['Encrypt'], + $this->app['request']->get('msg_signature'), + $this->app['request']->get('nonce'), + $this->app['request']->get('timestamp') + ); + } +} diff --git a/vendor/overtrue/wechat/src/OpenWork/Server/Handlers/EchoStrHandler.php b/vendor/overtrue/wechat/src/OpenWork/Server/Handlers/EchoStrHandler.php new file mode 100644 index 0000000..78af701 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenWork/Server/Handlers/EchoStrHandler.php @@ -0,0 +1,62 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenWork\Server\Handlers; + +use EasyWeChat\Kernel\Contracts\EventHandlerInterface; +use EasyWeChat\Kernel\Decorators\FinallyResult; +use EasyWeChat\Kernel\ServiceContainer; + +/** + * EchoStrHandler. + * + * @author xiaomin + */ +class EchoStrHandler implements EventHandlerInterface +{ + /** + * @var ServiceContainer + */ + protected $app; + + /** + * EchoStrHandler constructor. + * + * @param ServiceContainer $app + */ + public function __construct(ServiceContainer $app) + { + $this->app = $app; + } + + /** + * @param mixed $payload + * + * @return FinallyResult|null + */ + public function handle($payload = null) + { + if ($decrypted = $this->app['request']->get('echostr')) { + $str = $this->app['encryptor_corp']->decrypt( + $decrypted, + $this->app['request']->get('msg_signature'), + $this->app['request']->get('nonce'), + $this->app['request']->get('timestamp') + ); + + return new FinallyResult($str); + } + //把SuiteTicket缓存起来 + if (!empty($payload['SuiteTicket'])) { + $this->app['suite_ticket']->setTicket($payload['SuiteTicket']); + } + } +} diff --git a/vendor/overtrue/wechat/src/OpenWork/Server/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenWork/Server/ServiceProvider.php new file mode 100644 index 0000000..d33d5c8 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenWork/Server/ServiceProvider.php @@ -0,0 +1,56 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenWork\Server; + +use EasyWeChat\Kernel\Encryptor; +use EasyWeChat\OpenWork\Server\Handlers\EchoStrHandler; +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * ServiceProvider. + * + * @author xiaomin + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + //微信第三方在校验url是使用的是GET方式请求和corp_id进行加密 + !isset($app['encryptor_corp']) && $app['encryptor_corp'] = function ($app) { + return new Encryptor( + $app['config']['corp_id'], + $app['config']['token'], + $app['config']['aes_key'] + ); + }; + + //微信第三方推送数据时使用的是suite_id进行加密 + !isset($app['encryptor']) && $app['encryptor'] = function ($app) { + return new Encryptor( + $app['config']['suite_id'], + $app['config']['token'], + $app['config']['aes_key'] + ); + }; + + !isset($app['server']) && $app['server'] = function ($app) { + $guard = new Guard($app); + $guard->push(new EchoStrHandler($app)); + + return $guard; + }; + } +} diff --git a/vendor/overtrue/wechat/src/OpenWork/SuiteAuth/AccessToken.php b/vendor/overtrue/wechat/src/OpenWork/SuiteAuth/AccessToken.php new file mode 100644 index 0000000..e4e248d --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenWork/SuiteAuth/AccessToken.php @@ -0,0 +1,56 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenWork\SuiteAuth; + +use EasyWeChat\Kernel\AccessToken as BaseAccessToken; + +/** + * AccessToken. + * + * @author xiaomin + */ +class AccessToken extends BaseAccessToken +{ + /** + * @var string + */ + protected $requestMethod = 'POST'; + + /** + * @var string + */ + protected $endpointToGetToken = 'cgi-bin/service/get_suite_token'; + + /** + * @var string + */ + protected $tokenKey = 'suite_access_token'; + + /** + * @var string + */ + protected $cachePrefix = 'easywechat.kernel.suite_access_token.'; + + /** + * Credential for get token. + * + * @return array + */ + protected function getCredentials(): array + { + return [ + 'suite_id' => $this->app['config']['suite_id'], + 'suite_secret' => $this->app['config']['suite_secret'], + 'suite_ticket' => $this->app['suite_ticket']->getTicket(), + ]; + } +} diff --git a/vendor/overtrue/wechat/src/OpenWork/SuiteAuth/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenWork/SuiteAuth/ServiceProvider.php new file mode 100644 index 0000000..a4f5386 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenWork/SuiteAuth/ServiceProvider.php @@ -0,0 +1,37 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenWork\SuiteAuth; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * ServiceProvider. + * + * @author xiaomin + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['suite_ticket'] = function ($app) { + return new SuiteTicket($app); + }; + + isset($app['suite_access_token']) || $app['suite_access_token'] = function ($app) { + return new AccessToken($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OpenWork/SuiteAuth/SuiteTicket.php b/vendor/overtrue/wechat/src/OpenWork/SuiteAuth/SuiteTicket.php new file mode 100644 index 0000000..4c01710 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenWork/SuiteAuth/SuiteTicket.php @@ -0,0 +1,85 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenWork\SuiteAuth; + +use EasyWeChat\Kernel\Exceptions\RuntimeException; +use EasyWeChat\Kernel\Traits\InteractsWithCache; +use EasyWeChat\OpenWork\Application; + +/** + * SuiteTicket. + * + * @author xiaomin + */ +class SuiteTicket +{ + use InteractsWithCache; + + /** + * @var Application + */ + protected $app; + + /** + * SuiteTicket constructor. + * + * @param Application $app + */ + public function __construct(Application $app) + { + $this->app = $app; + } + + /** + * @param string $ticket + * + * @return $this + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + * @throws \Psr\SimpleCache\InvalidArgumentException + */ + public function setTicket(string $ticket) + { + $this->getCache()->set($this->getCacheKey(), $ticket, 1800); + + if (!$this->getCache()->has($this->getCacheKey())) { + throw new RuntimeException('Failed to cache suite ticket.'); + } + + return $this; + } + + /** + * @return string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + * @throws \Psr\SimpleCache\InvalidArgumentException + */ + public function getTicket(): string + { + if ($cached = $this->getCache()->get($this->getCacheKey())) { + return $cached; + } + + throw new RuntimeException('Credential "suite_ticket" does not exist in cache.'); + } + + /** + * @return string + */ + protected function getCacheKey(): string + { + return 'easywechat.open_work.suite_ticket.'.$this->app['config']['suite_id']; + } +} diff --git a/vendor/overtrue/wechat/src/OpenWork/Work/Application.php b/vendor/overtrue/wechat/src/OpenWork/Work/Application.php new file mode 100644 index 0000000..676013d --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenWork/Work/Application.php @@ -0,0 +1,41 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenWork\Work; + +use EasyWeChat\OpenWork\Application as OpenWork; +use EasyWeChat\OpenWork\Work\Auth\AccessToken; +use EasyWeChat\Work\Application as Work; + +/** + * Application. + * + * @author xiaomin + */ +class Application extends Work +{ + /** + * Application constructor. + * + * @param string $authCorpId + * @param string $permanentCode + * @param OpenWork $component + * @param array $prepends + */ + public function __construct(string $authCorpId, string $permanentCode, OpenWork $component, array $prepends = []) + { + parent::__construct($component->getConfig(), $prepends + [ + 'access_token' => function ($app) use ($authCorpId, $permanentCode, $component) { + return new AccessToken($app, $authCorpId, $permanentCode, $component); + }, + ]); + } +} diff --git a/vendor/overtrue/wechat/src/OpenWork/Work/Auth/AccessToken.php b/vendor/overtrue/wechat/src/OpenWork/Work/Auth/AccessToken.php new file mode 100644 index 0000000..e80758a --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenWork/Work/Auth/AccessToken.php @@ -0,0 +1,80 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenWork\Work\Auth; + +use EasyWeChat\Kernel\AccessToken as BaseAccessToken; +use EasyWeChat\OpenWork\Application; +use Pimple\Container; + +/** + * AccessToken. + * + * @author xiaomin + */ +class AccessToken extends BaseAccessToken +{ + /** + * @var string + */ + protected $requestMethod = 'POST'; + + /** + * @var string 授权方企业ID + */ + protected $authCorpid; + + /** + * @var string 授权方企业永久授权码,通过get_permanent_code获取 + */ + protected $permanentCode; + + protected $component; + + /** + * AccessToken constructor. + * + * @param Container $app + * @param string $authCorpId + * @param string $permanentCode + * @param Application $component + */ + public function __construct(Container $app, string $authCorpId, string $permanentCode, Application $component) + { + $this->authCorpid = $authCorpId; + $this->permanentCode = $permanentCode; + $this->component = $component; + parent::__construct($app); + } + + /** + * Credential for get token. + * + * @return array + */ + protected function getCredentials(): array + { + return [ + 'auth_corpid' => $this->authCorpid, + 'permanent_code' => $this->permanentCode, + ]; + } + + /** + * @return string + */ + public function getEndpoint(): string + { + return 'cgi-bin/service/get_corp_token?'.http_build_query([ + 'suite_access_token' => $this->component['suite_access_token']->getToken()['suite_access_token'], + ]); + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Application.php b/vendor/overtrue/wechat/src/Payment/Application.php new file mode 100644 index 0000000..7013f74 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Application.php @@ -0,0 +1,206 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment; + +use Closure; +use EasyWeChat\BasicService; +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; +use EasyWeChat\Kernel\ServiceContainer; +use EasyWeChat\Kernel\Support; +use EasyWeChat\OfficialAccount; + +/** + * Class Application. + * + * @property \EasyWeChat\Payment\Bill\Client $bill + * @property \EasyWeChat\Payment\Jssdk\Client $jssdk + * @property \EasyWeChat\Payment\Order\Client $order + * @property \EasyWeChat\Payment\Refund\Client $refund + * @property \EasyWeChat\Payment\Coupon\Client $coupon + * @property \EasyWeChat\Payment\Reverse\Client $reverse + * @property \EasyWeChat\Payment\Redpack\Client $redpack + * @property \EasyWeChat\BasicService\Url\Client $url + * @property \EasyWeChat\Payment\Transfer\Client $transfer + * @property \EasyWeChat\Payment\Security\Client $security + * @property \EasyWeChat\Payment\ProfitSharing\Client $profit_sharing + * @property \EasyWeChat\OfficialAccount\Auth\AccessToken $access_token + * + * @method mixed pay(array $attributes) + * @method mixed authCodeToOpenid(string $authCode) + */ +class Application extends ServiceContainer +{ + /** + * @var array + */ + protected $providers = [ + OfficialAccount\Auth\ServiceProvider::class, + BasicService\Url\ServiceProvider::class, + Base\ServiceProvider::class, + Bill\ServiceProvider::class, + Coupon\ServiceProvider::class, + Jssdk\ServiceProvider::class, + Merchant\ServiceProvider::class, + Order\ServiceProvider::class, + Redpack\ServiceProvider::class, + Refund\ServiceProvider::class, + Reverse\ServiceProvider::class, + Sandbox\ServiceProvider::class, + Transfer\ServiceProvider::class, + Security\ServiceProvider::class, + ProfitSharing\ServiceProvider::class, + ]; + + /** + * @var array + */ + protected $defaultConfig = [ + 'http' => [ + 'base_uri' => 'https://api.mch.weixin.qq.com/', + ], + ]; + + /** + * Build payment scheme for product. + * + * @param string $productId + * + * @return string + */ + public function scheme(string $productId): string + { + $params = [ + 'appid' => $this['config']->app_id, + 'mch_id' => $this['config']->mch_id, + 'time_stamp' => time(), + 'nonce_str' => uniqid(), + 'product_id' => $productId, + ]; + + $params['sign'] = Support\generate_sign($params, $this['config']->key); + + return 'weixin://wxpay/bizpayurl?'.http_build_query($params); + } + + /** + * @param string $codeUrl + * + * @return string + */ + public function codeUrlScheme(string $codeUrl) + { + return \sprintf('weixin://wxpay/bizpayurl?sr=%s', $codeUrl); + } + + /** + * @param \Closure $closure + * + * @return \Symfony\Component\HttpFoundation\Response + * + * @codeCoverageIgnore + * + * @throws \EasyWeChat\Kernel\Exceptions\Exception + */ + public function handlePaidNotify(Closure $closure) + { + return (new Notify\Paid($this))->handle($closure); + } + + /** + * @param \Closure $closure + * + * @return \Symfony\Component\HttpFoundation\Response + * + * @codeCoverageIgnore + * + * @throws \EasyWeChat\Kernel\Exceptions\Exception + */ + public function handleRefundedNotify(Closure $closure) + { + return (new Notify\Refunded($this))->handle($closure); + } + + /** + * @param \Closure $closure + * + * @return \Symfony\Component\HttpFoundation\Response + * + * @codeCoverageIgnore + * + * @throws \EasyWeChat\Kernel\Exceptions\Exception + */ + public function handleScannedNotify(Closure $closure) + { + return (new Notify\Scanned($this))->handle($closure); + } + + /** + * Set sub-merchant. + * + * @param string $mchId + * @param string|null $appId + * + * @return $this + */ + public function setSubMerchant(string $mchId, string $appId = null) + { + $this['config']->set('sub_mch_id', $mchId); + $this['config']->set('sub_appid', $appId); + + return $this; + } + + /** + * @return bool + */ + public function inSandbox(): bool + { + return (bool) $this['config']->get('sandbox'); + } + + /** + * @param string|null $endpoint + * + * @return string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + public function getKey(string $endpoint = null) + { + if ('sandboxnew/pay/getsignkey' === $endpoint) { + return $this['config']->key; + } + + $key = $this->inSandbox() ? $this['sandbox']->getKey() : $this['config']->key; + + if (empty($key)) { + throw new InvalidArgumentException('config key should not empty.'); + } + + if (32 !== strlen($key)) { + throw new InvalidArgumentException(sprintf("'%s' should be 32 chars length.", $key)); + } + + return $key; + } + + /** + * @param string $name + * @param array $arguments + * + * @return mixed + */ + public function __call($name, $arguments) + { + return call_user_func_array([$this['base'], $name], $arguments); + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Base/Client.php b/vendor/overtrue/wechat/src/Payment/Base/Client.php new file mode 100644 index 0000000..75d4c9a --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Base/Client.php @@ -0,0 +1,54 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Base; + +use EasyWeChat\Payment\Kernel\BaseClient; + +class Client extends BaseClient +{ + /** + * Pay the order. + * + * @param array $params + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function pay(array $params) + { + $params['appid'] = $this->app['config']->app_id; + + return $this->request($this->wrap('pay/micropay'), $params); + } + + /** + * Get openid by auth code. + * + * @param string $authCode + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function authCodeToOpenid(string $authCode) + { + return $this->request('tools/authcodetoopenid', [ + 'appid' => $this->app['config']->app_id, + 'auth_code' => $authCode, + ]); + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Base/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Base/ServiceProvider.php new file mode 100644 index 0000000..71aebd9 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Base/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Base; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['base'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Bill/Client.php b/vendor/overtrue/wechat/src/Payment/Bill/Client.php new file mode 100644 index 0000000..20a9b63 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Bill/Client.php @@ -0,0 +1,48 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Bill; + +use EasyWeChat\Kernel\Http\StreamResponse; +use EasyWeChat\Payment\Kernel\BaseClient; + +class Client extends BaseClient +{ + /** + * Download bill history as a table file. + * + * @param string $date + * @param string $type + * @param array $optional + * + * @return \EasyWeChat\Kernel\Http\StreamResponse|\Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function get(string $date, string $type = 'ALL', array $optional = []) + { + $params = [ + 'appid' => $this->app['config']->app_id, + 'bill_date' => $date, + 'bill_type' => $type, + ] + $optional; + + $response = $this->requestRaw($this->wrap('pay/downloadbill'), $params); + + if (0 === strpos($response->getBody()->getContents(), '')) { + return $this->castResponseToType($response, $this->app['config']->get('response_type')); + } + + return StreamResponse::buildFromPsrResponse($response); + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Bill/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Bill/ServiceProvider.php new file mode 100644 index 0000000..3fd98d4 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Bill/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Bill; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['bill'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Coupon/Client.php b/vendor/overtrue/wechat/src/Payment/Coupon/Client.php new file mode 100644 index 0000000..101a42b --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Coupon/Client.php @@ -0,0 +1,77 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Coupon; + +use EasyWeChat\Payment\Kernel\BaseClient; + +/** + * Class Client. + * + * @author tianyong90 <412039588@qq.com> + */ +class Client extends BaseClient +{ + /** + * send a cash coupon. + * + * @param array $params + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function send(array $params) + { + $params['appid'] = $this->app['config']->app_id; + $params['openid_count'] = 1; + + return $this->safeRequest('mmpaymkttransfers/send_coupon', $params); + } + + /** + * query a coupon stock. + * + * @param array $params + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function stock(array $params) + { + $params['appid'] = $this->app['config']->app_id; + + return $this->request('mmpaymkttransfers/query_coupon_stock', $params); + } + + /** + * query a info of coupon. + * + * @param array $params + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function info(array $params) + { + $params['appid'] = $this->app['config']->app_id; + + return $this->request('mmpaymkttransfers/querycouponsinfo', $params); + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Coupon/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Coupon/ServiceProvider.php new file mode 100644 index 0000000..513734b --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Coupon/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Coupon; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['coupon'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Jssdk/Client.php b/vendor/overtrue/wechat/src/Payment/Jssdk/Client.php new file mode 100644 index 0000000..c507453 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Jssdk/Client.php @@ -0,0 +1,135 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Jssdk; + +use EasyWeChat\BasicService\Jssdk\Client as JssdkClient; +use EasyWeChat\Kernel\Support; +use Overtrue\Socialite\AccessTokenInterface; + +/** + * Class Client. + * + * @author overtrue + */ +class Client extends JssdkClient +{ + /** + * [WeixinJSBridge] Generate js config for payment. + * + *
    +     * WeixinJSBridge.invoke(
    +     *  'getBrandWCPayRequest',
    +     *  ...
    +     * );
    +     * 
    + * + * @param string $prepayId + * @param bool $json + * + * @return string|array + */ + public function bridgeConfig(string $prepayId, bool $json = true) + { + $params = [ + 'appId' => $this->app['config']->sub_appid ?: $this->app['config']->app_id, + 'timeStamp' => strval(time()), + 'nonceStr' => uniqid(), + 'package' => "prepay_id=$prepayId", + 'signType' => 'MD5', + ]; + + $params['paySign'] = Support\generate_sign($params, $this->app['config']->key, 'md5'); + + return $json ? json_encode($params) : $params; + } + + /** + * [JSSDK] Generate js config for payment. + * + *
    +     * wx.chooseWXPay({...});
    +     * 
    + * + * @param string $prepayId + * + * @return array + */ + public function sdkConfig(string $prepayId): array + { + $config = $this->bridgeConfig($prepayId, false); + + $config['timestamp'] = $config['timeStamp']; + unset($config['timeStamp']); + + return $config; + } + + /** + * Generate app payment parameters. + * + * @param string $prepayId + * + * @return array + */ + public function appConfig(string $prepayId): array + { + $params = [ + 'appid' => $this->app['config']->app_id, + 'partnerid' => $this->app['config']->mch_id, + 'prepayid' => $prepayId, + 'noncestr' => uniqid(), + 'timestamp' => time(), + 'package' => 'Sign=WXPay', + ]; + + $params['sign'] = Support\generate_sign($params, $this->app['config']->key); + + return $params; + } + + /** + * Generate js config for share user address. + * + * @param string|\Overtrue\Socialite\AccessTokenInterface $accessToken + * @param bool $json + * + * @return string|array + */ + public function shareAddressConfig($accessToken, bool $json = true) + { + if ($accessToken instanceof AccessTokenInterface) { + $accessToken = $accessToken->getToken(); + } + + $params = [ + 'appId' => $this->app['config']->app_id, + 'scope' => 'jsapi_address', + 'timeStamp' => strval(time()), + 'nonceStr' => uniqid(), + 'signType' => 'SHA1', + ]; + + $signParams = [ + 'appid' => $params['appId'], + 'url' => $this->getUrl(), + 'timestamp' => $params['timeStamp'], + 'noncestr' => $params['nonceStr'], + 'accesstoken' => strval($accessToken), + ]; + + ksort($signParams); + + $params['addrSign'] = sha1(urldecode(http_build_query($signParams))); + + return $json ? json_encode($params) : $params; + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Jssdk/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Jssdk/ServiceProvider.php new file mode 100644 index 0000000..24f2a76 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Jssdk/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Jssdk; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['jssdk'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Kernel/BaseClient.php b/vendor/overtrue/wechat/src/Payment/Kernel/BaseClient.php new file mode 100644 index 0000000..74f3939 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Kernel/BaseClient.php @@ -0,0 +1,190 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Kernel; + +use EasyWeChat\Kernel\Support; +use EasyWeChat\Kernel\Traits\HasHttpRequests; +use EasyWeChat\Payment\Application; +use GuzzleHttp\MessageFormatter; +use GuzzleHttp\Middleware; +use Psr\Http\Message\ResponseInterface; + +/** + * Class BaseClient. + * + * @author overtrue + */ +class BaseClient +{ + use HasHttpRequests { request as performRequest; } + + /** + * @var \EasyWeChat\Payment\Application + */ + protected $app; + + /** + * Constructor. + * + * @param \EasyWeChat\Payment\Application $app + */ + public function __construct(Application $app) + { + $this->app = $app; + + $this->setHttpClient($this->app['http_client']); + } + + /** + * Extra request params. + * + * @return array + */ + protected function prepends() + { + return []; + } + + /** + * Make a API request. + * + * @param string $endpoint + * @param array $params + * @param string $method + * @param array $options + * @param bool $returnResponse + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function request(string $endpoint, array $params = [], $method = 'post', array $options = [], $returnResponse = false) + { + $base = [ + 'mch_id' => $this->app['config']['mch_id'], + 'nonce_str' => uniqid(), + 'sub_mch_id' => $this->app['config']['sub_mch_id'], + 'sub_appid' => $this->app['config']['sub_appid'], + ]; + + $params = array_filter(array_merge($base, $this->prepends(), $params), 'strlen'); + + $secretKey = $this->app->getKey($endpoint); + + $encryptMethod = Support\get_encrypt_method(Support\Arr::get($params, 'sign_type', 'MD5'), $secretKey); + + $params['sign'] = Support\generate_sign($params, $secretKey, $encryptMethod); + + $options = array_merge([ + 'body' => Support\XML::build($params), + ], $options); + + $this->pushMiddleware($this->logMiddleware(), 'log'); + + $response = $this->performRequest($endpoint, $method, $options); + + return $returnResponse ? $response : $this->castResponseToType($response, $this->app->config->get('response_type')); + } + + /** + * Log the request. + * + * @return \Closure + */ + protected function logMiddleware() + { + $formatter = new MessageFormatter($this->app['config']['http.log_template'] ?? MessageFormatter::DEBUG); + + return Middleware::log($this->app['logger'], $formatter); + } + + /** + * Make a request and return raw response. + * + * @param string $endpoint + * @param array $params + * @param string $method + * @param array $options + * + * @return ResponseInterface + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function requestRaw(string $endpoint, array $params = [], $method = 'post', array $options = []) + { + /** @var ResponseInterface $response */ + $response = $this->request($endpoint, $params, $method, $options, true); + + return $response; + } + + /** + * Make a request and return an array. + * + * @param string $endpoint + * @param array $params + * @param string $method + * @param array $options + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function requestArray(string $endpoint, array $params = [], $method = 'post', array $options = []): array + { + $response = $this->requestRaw($endpoint, $params, $method, $options); + + return $this->castResponseToType($response, 'array'); + } + + /** + * Request with SSL. + * + * @param string $endpoint + * @param array $params + * @param string $method + * @param array $options + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function safeRequest($endpoint, array $params, $method = 'post', array $options = []) + { + $options = array_merge([ + 'cert' => $this->app['config']->get('cert_path'), + 'ssl_key' => $this->app['config']->get('key_path'), + ], $options); + + return $this->request($endpoint, $params, $method, $options); + } + + /** + * Wrapping an API endpoint. + * + * @param string $endpoint + * + * @return string + */ + protected function wrap(string $endpoint): string + { + return $this->app->inSandbox() ? "sandboxnew/{$endpoint}" : $endpoint; + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Kernel/Exceptions/InvalidSignException.php b/vendor/overtrue/wechat/src/Payment/Kernel/Exceptions/InvalidSignException.php new file mode 100644 index 0000000..cdd25ba --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Kernel/Exceptions/InvalidSignException.php @@ -0,0 +1,18 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Kernel\Exceptions; + +use EasyWeChat\Kernel\Exceptions\Exception; + +class InvalidSignException extends Exception +{ +} diff --git a/vendor/overtrue/wechat/src/Payment/Kernel/Exceptions/SandboxException.php b/vendor/overtrue/wechat/src/Payment/Kernel/Exceptions/SandboxException.php new file mode 100644 index 0000000..01f9dd5 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Kernel/Exceptions/SandboxException.php @@ -0,0 +1,18 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Kernel\Exceptions; + +use EasyWeChat\Kernel\Exceptions\Exception; + +class SandboxException extends Exception +{ +} diff --git a/vendor/overtrue/wechat/src/Payment/Merchant/Client.php b/vendor/overtrue/wechat/src/Payment/Merchant/Client.php new file mode 100644 index 0000000..f31a7e2 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Merchant/Client.php @@ -0,0 +1,94 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Merchant; + +use EasyWeChat\Payment\Kernel\BaseClient; + +/** + * Class Client. + * + * @author mingyoung + */ +class Client extends BaseClient +{ + /** + * Add sub-merchant. + * + * @param array $params + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function addSubMerchant(array $params) + { + return $this->manage($params, ['action' => 'add']); + } + + /** + * Query sub-merchant by merchant id. + * + * @param string $id + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function querySubMerchantByMerchantId(string $id) + { + $params = [ + 'micro_mch_id' => $id, + ]; + + return $this->manage($params, ['action' => 'query']); + } + + /** + * Query sub-merchant by wechat id. + * + * @param string $id + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function querySubMerchantByWeChatId(string $id) + { + $params = [ + 'recipient_wechatid' => $id, + ]; + + return $this->manage($params, ['action' => 'query']); + } + + /** + * @param array $params + * @param array $query + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function manage(array $params, array $query) + { + $params = array_merge($params, [ + 'appid' => $this->app['config']->app_id, + 'nonce_str' => '', + 'sub_mch_id' => '', + 'sub_appid' => '', + ]); + + return $this->safeRequest('secapi/mch/submchmanage', $params, 'post', compact('query')); + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Merchant/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Merchant/ServiceProvider.php new file mode 100644 index 0000000..5d05c95 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Merchant/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Merchant; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author mingyoung + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['merchant'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Notify/Handler.php b/vendor/overtrue/wechat/src/Payment/Notify/Handler.php new file mode 100644 index 0000000..c657433 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Notify/Handler.php @@ -0,0 +1,201 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Notify; + +use Closure; +use EasyWeChat\Kernel\Exceptions\Exception; +use EasyWeChat\Kernel\Support; +use EasyWeChat\Kernel\Support\XML; +use EasyWeChat\Payment\Kernel\Exceptions\InvalidSignException; +use Symfony\Component\HttpFoundation\Response; + +abstract class Handler +{ + const SUCCESS = 'SUCCESS'; + const FAIL = 'FAIL'; + + /** + * @var \EasyWeChat\Payment\Application + */ + protected $app; + + /** + * @var array + */ + protected $message; + + /** + * @var string|null + */ + protected $fail; + + /** + * @var array + */ + protected $attributes = []; + + /** + * Check sign. + * If failed, throws an exception. + * + * @var bool + */ + protected $check = true; + + /** + * Respond with sign. + * + * @var bool + */ + protected $sign = false; + + /** + * @param \EasyWeChat\Payment\Application $app + */ + public function __construct($app) + { + $this->app = $app; + } + + /** + * Handle incoming notify. + * + * @param \Closure $closure + * + * @return \Symfony\Component\HttpFoundation\Response + */ + abstract public function handle(Closure $closure); + + /** + * @param string $message + */ + public function fail(string $message) + { + $this->fail = $message; + } + + /** + * @param array $attributes + * @param bool $sign + * + * @return $this + */ + public function respondWith(array $attributes, bool $sign = false) + { + $this->attributes = $attributes; + $this->sign = $sign; + + return $this; + } + + /** + * Build xml and return the response to WeChat. + * + * @return \Symfony\Component\HttpFoundation\Response + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + public function toResponse(): Response + { + $base = [ + 'return_code' => is_null($this->fail) ? static::SUCCESS : static::FAIL, + 'return_msg' => $this->fail, + ]; + + $attributes = array_merge($base, $this->attributes); + + if ($this->sign) { + $attributes['sign'] = Support\generate_sign($attributes, $this->app->getKey()); + } + + return new Response(XML::build($attributes)); + } + + /** + * Return the notify message from request. + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\Exception + */ + public function getMessage(): array + { + if (!empty($this->message)) { + return $this->message; + } + + try { + $message = XML::parse(strval($this->app['request']->getContent())); + } catch (\Throwable $e) { + throw new Exception('Invalid request XML: '.$e->getMessage(), 400); + } + + if (!is_array($message) || empty($message)) { + throw new Exception('Invalid request XML.', 400); + } + + if ($this->check) { + $this->validate($message); + } + + return $this->message = $message; + } + + /** + * Decrypt message. + * + * @param string $key + * + * @return string|null + * + * @throws \EasyWeChat\Kernel\Exceptions\Exception + */ + public function decryptMessage(string $key) + { + $message = $this->getMessage(); + if (empty($message[$key])) { + return null; + } + + return Support\AES::decrypt( + base64_decode($message[$key], true), md5($this->app['config']->key), '', OPENSSL_RAW_DATA, 'AES-256-ECB' + ); + } + + /** + * Validate the request params. + * + * @param array $message + * + * @throws \EasyWeChat\Payment\Kernel\Exceptions\InvalidSignException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + protected function validate(array $message) + { + $sign = $message['sign']; + unset($message['sign']); + + if (Support\generate_sign($message, $this->app->getKey()) !== $sign) { + throw new InvalidSignException(); + } + } + + /** + * @param mixed $result + */ + protected function strict($result) + { + if (true !== $result && is_null($this->fail)) { + $this->fail(strval($result)); + } + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Notify/Paid.php b/vendor/overtrue/wechat/src/Payment/Notify/Paid.php new file mode 100644 index 0000000..c54b9cd --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Notify/Paid.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Notify; + +use Closure; + +class Paid extends Handler +{ + /** + * @param \Closure $closure + * + * @return \Symfony\Component\HttpFoundation\Response + * + * @throws \EasyWeChat\Kernel\Exceptions\Exception + */ + public function handle(Closure $closure) + { + $this->strict( + \call_user_func($closure, $this->getMessage(), [$this, 'fail']) + ); + + return $this->toResponse(); + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Notify/Refunded.php b/vendor/overtrue/wechat/src/Payment/Notify/Refunded.php new file mode 100644 index 0000000..8241892 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Notify/Refunded.php @@ -0,0 +1,48 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Notify; + +use Closure; +use EasyWeChat\Kernel\Support\XML; + +class Refunded extends Handler +{ + protected $check = false; + + /** + * @param \Closure $closure + * + * @return \Symfony\Component\HttpFoundation\Response + * + * @throws \EasyWeChat\Kernel\Exceptions\Exception + */ + public function handle(Closure $closure) + { + $this->strict( + \call_user_func($closure, $this->getMessage(), $this->reqInfo(), [$this, 'fail']) + ); + + return $this->toResponse(); + } + + /** + * Decrypt the `req_info` from request message. + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\Exception + */ + public function reqInfo() + { + return XML::parse($this->decryptMessage('req_info')); + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Notify/Scanned.php b/vendor/overtrue/wechat/src/Payment/Notify/Scanned.php new file mode 100644 index 0000000..023a905 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Notify/Scanned.php @@ -0,0 +1,60 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Notify; + +use Closure; + +class Scanned extends Handler +{ + protected $check = false; + + /** + * @var string|null + */ + protected $alert; + + /** + * @param string $message + */ + public function alert(string $message) + { + $this->alert = $message; + } + + /** + * @param \Closure $closure + * + * @return \Symfony\Component\HttpFoundation\Response + * + * @throws \EasyWeChat\Kernel\Exceptions\Exception + */ + public function handle(Closure $closure) + { + $result = \call_user_func($closure, $this->getMessage(), [$this, 'fail'], [$this, 'alert']); + + $attributes = [ + 'result_code' => is_null($this->alert) && is_null($this->fail) ? static::SUCCESS : static::FAIL, + 'err_code_des' => $this->alert, + ]; + + if (is_null($this->alert) && is_string($result)) { + $attributes += [ + 'appid' => $this->app['config']->app_id, + 'mch_id' => $this->app['config']->mch_id, + 'nonce_str' => uniqid(), + 'prepay_id' => $result, + ]; + } + + return $this->respondWith($attributes, true)->toResponse(); + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Order/Client.php b/vendor/overtrue/wechat/src/Payment/Order/Client.php new file mode 100644 index 0000000..ff2ca2b --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Order/Client.php @@ -0,0 +1,126 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Order; + +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; +use EasyWeChat\Kernel\Exceptions\InvalidConfigException; +use EasyWeChat\Kernel\Support; +use EasyWeChat\Kernel\Support\Collection; +use EasyWeChat\Payment\Kernel\BaseClient; +use Psr\Http\Message\ResponseInterface; + +class Client extends BaseClient +{ + /** + * Unify order. + * + * @param array $params + * @param bool $isContract + * + * @return ResponseInterface|Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function unify(array $params, $isContract = false) + { + if (empty($params['spbill_create_ip'])) { + $params['spbill_create_ip'] = ('NATIVE' === $params['trade_type']) ? Support\get_server_ip() : Support\get_client_ip(); + } + + $params['appid'] = $this->app['config']->app_id; + $params['notify_url'] = $params['notify_url'] ?? $this->app['config']['notify_url']; + + if ($isContract) { + $params['contract_appid'] = $this->app['config']['app_id']; + $params['contract_mchid'] = $this->app['config']['mch_id']; + $params['request_serial'] = $params['request_serial'] ?? time(); + $params['contract_notify_url'] = $params['contract_notify_url'] ?? $this->app['config']['contract_notify_url']; + + return $this->request($this->wrap('pay/contractorder'), $params); + } + + return $this->request($this->wrap('pay/unifiedorder'), $params); + } + + /** + * Query order by out trade number. + * + * @param string $number + * + * @return ResponseInterface|Collection|array|object|string + * + * @throws InvalidArgumentException + * @throws InvalidConfigException + */ + public function queryByOutTradeNumber(string $number) + { + return $this->query([ + 'out_trade_no' => $number, + ]); + } + + /** + * Query order by transaction id. + * + * @param string $transactionId + * + * @return ResponseInterface|Collection|array|object|string + * + * @throws InvalidArgumentException + * @throws InvalidConfigException + */ + public function queryByTransactionId(string $transactionId) + { + return $this->query([ + 'transaction_id' => $transactionId, + ]); + } + + /** + * @param array $params + * + * @return ResponseInterface|Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function query(array $params) + { + $params['appid'] = $this->app['config']->app_id; + + return $this->request($this->wrap('pay/orderquery'), $params); + } + + /** + * Close order by out_trade_no. + * + * @param string $tradeNo + * + * @return ResponseInterface|Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function close(string $tradeNo) + { + $params = [ + 'appid' => $this->app['config']->app_id, + 'out_trade_no' => $tradeNo, + ]; + + return $this->request($this->wrap('pay/closeorder'), $params); + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Order/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Order/ServiceProvider.php new file mode 100644 index 0000000..9e781c0 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Order/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Order; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author mingyoung + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['order'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Payment/ProfitSharing/Client.php b/vendor/overtrue/wechat/src/Payment/ProfitSharing/Client.php new file mode 100644 index 0000000..d16ca2b --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/ProfitSharing/Client.php @@ -0,0 +1,201 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\ProfitSharing; + +use EasyWeChat\Payment\Kernel\BaseClient; + +/** + * Class Client. + * + * @author ClouderSky + */ +class Client extends BaseClient +{ + /** + * {@inheritdoc}. + */ + protected function prepends() + { + return [ + 'sign_type' => 'HMAC-SHA256', + ]; + } + + /** + * Add profit sharing receiver. + * 服务商代子商户发起添加分账接收方请求. + * 后续可通过发起分账请求将结算后的钱分到该分账接收方. + * + * @param array $receiver 分账接收方对象,json格式 + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function addReceiver(array $receiver) + { + $params = [ + 'appid' => $this->app['config']->app_id, + 'receiver' => json_encode( + $receiver, JSON_UNESCAPED_UNICODE + ), + ]; + + return $this->request( + 'pay/profitsharingaddreceiver', $params + ); + } + + /** + * Delete profit sharing receiver. + * 服务商代子商户发起删除分账接收方请求. + * 删除后不支持将结算后的钱分到该分账接收方. + * + * @param array $receiver 分账接收方对象,json格式 + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function deleteReceiver(array $receiver) + { + $params = [ + 'appid' => $this->app['config']->app_id, + 'receiver' => json_encode( + $receiver, JSON_UNESCAPED_UNICODE + ), + ]; + + return $this->request( + 'pay/profitsharingremovereceiver', $params + ); + } + + /** + * Single profit sharing. + * 请求单次分账. + * + * @param string $transactionId 微信支付订单号 + * @param string $outOrderNo 商户系统内部的分账单号 + * @param array $receivers 分账接收方列表 + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function share( + string $transactionId, + string $outOrderNo, + array $receivers + ) { + $params = [ + 'appid' => $this->app['config']->app_id, + 'transaction_id' => $transactionId, + 'out_order_no' => $outOrderNo, + 'receivers' => json_encode( + $receivers, JSON_UNESCAPED_UNICODE + ), + ]; + + return $this->safeRequest( + 'secapi/pay/profitsharing', $params + ); + } + + /** + * Multi profit sharing. + * 请求多次分账. + * + * @param string $transactionId 微信支付订单号 + * @param string $outOrderNo 商户系统内部的分账单号 + * @param array $receivers 分账接收方列表 + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function multiShare( + string $transactionId, + string $outOrderNo, + array $receivers + ) { + $params = [ + 'appid' => $this->app['config']->app_id, + 'transaction_id' => $transactionId, + 'out_order_no' => $outOrderNo, + 'receivers' => json_encode( + $receivers, JSON_UNESCAPED_UNICODE + ), + ]; + + return $this->safeRequest( + 'secapi/pay/multiprofitsharing', $params + ); + } + + /** + * Finish profit sharing. + * 完结分账. + * + * @param array $params + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function markOrderAsFinished(array $params) + { + $params['appid'] = $this->app['config']->app_id; + $params['sub_appid'] = null; + + return $this->safeRequest( + 'secapi/pay/profitsharingfinish', $params + ); + } + + /** + * Query profit sharing result. + * 查询分账结果. + * + * @param string $transactionId 微信支付订单号 + * @param string $outOrderNo 商户系统内部的分账单号 + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function query( + string $transactionId, string $outOrderNo + ) { + $params = [ + 'sub_appid' => null, + 'transaction_id' => $transactionId, + 'out_order_no' => $outOrderNo, + ]; + + return $this->request( + 'pay/profitsharingquery', $params + ); + } +} diff --git a/vendor/overtrue/wechat/src/Payment/ProfitSharing/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/ProfitSharing/ServiceProvider.php new file mode 100644 index 0000000..d247d53 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/ProfitSharing/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\ProfitSharing; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author ClouderSky + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['profit_sharing'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Redpack/Client.php b/vendor/overtrue/wechat/src/Payment/Redpack/Client.php new file mode 100644 index 0000000..a764c39 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Redpack/Client.php @@ -0,0 +1,88 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Redpack; + +use EasyWeChat\Kernel\Support; +use EasyWeChat\Payment\Kernel\BaseClient; + +/** + * Class Client. + * + * @author tianyong90 <412039588@qq.com> + */ +class Client extends BaseClient +{ + /** + * Query redpack. + * + * @param mixed $mchBillno + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function info($mchBillno) + { + $params = is_array($mchBillno) ? $mchBillno : ['mch_billno' => $mchBillno]; + $base = [ + 'appid' => $this->app['config']->app_id, + 'bill_type' => 'MCHT', + ]; + + return $this->safeRequest('mmpaymkttransfers/gethbinfo', array_merge($base, $params)); + } + + /** + * Send normal redpack. + * + * @param array $params + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function sendNormal(array $params) + { + $base = [ + 'total_num' => 1, + 'client_ip' => $params['client_ip'] ?? Support\get_server_ip(), + 'wxappid' => $this->app['config']->app_id, + ]; + + return $this->safeRequest('mmpaymkttransfers/sendredpack', array_merge($base, $params)); + } + + /** + * Send group redpack. + * + * @param array $params + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function sendGroup(array $params) + { + $base = [ + 'amt_type' => 'ALL_RAND', + 'wxappid' => $this->app['config']->app_id, + ]; + + return $this->safeRequest('mmpaymkttransfers/sendgroupredpack', array_merge($base, $params)); + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Redpack/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Redpack/ServiceProvider.php new file mode 100644 index 0000000..af36f35 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Redpack/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Redpack; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['redpack'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Refund/Client.php b/vendor/overtrue/wechat/src/Payment/Refund/Client.php new file mode 100644 index 0000000..128c6e7 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Refund/Client.php @@ -0,0 +1,159 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Refund; + +use EasyWeChat\Payment\Kernel\BaseClient; + +class Client extends BaseClient +{ + /** + * Refund by out trade number. + * + * @param string $number + * @param string $refundNumber + * @param int $totalFee + * @param int $refundFee + * @param array $optional + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function byOutTradeNumber(string $number, string $refundNumber, int $totalFee, int $refundFee, array $optional = []) + { + return $this->refund($refundNumber, $totalFee, $refundFee, array_merge($optional, ['out_trade_no' => $number])); + } + + /** + * Refund by transaction id. + * + * @param string $transactionId + * @param string $refundNumber + * @param int $totalFee + * @param int $refundFee + * @param array $optional + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function byTransactionId(string $transactionId, string $refundNumber, int $totalFee, int $refundFee, array $optional = []) + { + return $this->refund($refundNumber, $totalFee, $refundFee, array_merge($optional, ['transaction_id' => $transactionId])); + } + + /** + * Query refund by transaction id. + * + * @param string $transactionId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function queryByTransactionId(string $transactionId) + { + return $this->query($transactionId, 'transaction_id'); + } + + /** + * Query refund by out trade number. + * + * @param string $outTradeNumber + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function queryByOutTradeNumber(string $outTradeNumber) + { + return $this->query($outTradeNumber, 'out_trade_no'); + } + + /** + * Query refund by out refund number. + * + * @param string $outRefundNumber + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function queryByOutRefundNumber(string $outRefundNumber) + { + return $this->query($outRefundNumber, 'out_refund_no'); + } + + /** + * Query refund by refund id. + * + * @param string $refundId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function queryByRefundId(string $refundId) + { + return $this->query($refundId, 'refund_id'); + } + + /** + * Refund. + * + * @param string $refundNumber + * @param int $totalFee + * @param int $refundFee + * @param array $optional + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function refund(string $refundNumber, int $totalFee, int $refundFee, $optional = []) + { + $params = array_merge([ + 'out_refund_no' => $refundNumber, + 'total_fee' => $totalFee, + 'refund_fee' => $refundFee, + 'appid' => $this->app['config']->app_id, + ], $optional); + + return $this->safeRequest($this->wrap( + $this->app->inSandbox() ? 'pay/refund' : 'secapi/pay/refund' + ), $params); + } + + /** + * Query refund. + * + * @param string $number + * @param string $type + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function query(string $number, string $type) + { + $params = [ + 'appid' => $this->app['config']->app_id, + $type => $number, + ]; + + return $this->request($this->wrap('pay/refundquery'), $params); + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Refund/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Refund/ServiceProvider.php new file mode 100644 index 0000000..faa4e89 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Refund/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Refund; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author mingyoung + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['refund'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Reverse/Client.php b/vendor/overtrue/wechat/src/Payment/Reverse/Client.php new file mode 100644 index 0000000..990e6e6 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Reverse/Client.php @@ -0,0 +1,67 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Reverse; + +use EasyWeChat\Payment\Kernel\BaseClient; + +class Client extends BaseClient +{ + /** + * Reverse order by out trade number. + * + * @param string $outTradeNumber + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function byOutTradeNumber(string $outTradeNumber) + { + return $this->reverse($outTradeNumber, 'out_trade_no'); + } + + /** + * Reverse order by transaction_id. + * + * @param string $transactionId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function byTransactionId(string $transactionId) + { + return $this->reverse($transactionId, 'transaction_id'); + } + + /** + * Reverse order. + * + * @param string $number + * @param string $type + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function reverse(string $number, string $type) + { + $params = [ + 'appid' => $this->app['config']->app_id, + $type => $number, + ]; + + return $this->safeRequest($this->wrap('secapi/pay/reverse'), $params); + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Reverse/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Reverse/ServiceProvider.php new file mode 100644 index 0000000..2417874 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Reverse/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Reverse; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['reverse'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Sandbox/Client.php b/vendor/overtrue/wechat/src/Payment/Sandbox/Client.php new file mode 100644 index 0000000..9f1ec7c --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Sandbox/Client.php @@ -0,0 +1,60 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Sandbox; + +use EasyWeChat\Kernel\Traits\InteractsWithCache; +use EasyWeChat\Payment\Kernel\BaseClient; +use EasyWeChat\Payment\Kernel\Exceptions\SandboxException; + +/** + * Class Client. + * + * @author mingyoung + */ +class Client extends BaseClient +{ + use InteractsWithCache; + + /** + * @return string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Payment\Kernel\Exceptions\SandboxException + * @throws \Psr\SimpleCache\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getKey(): string + { + if ($cache = $this->getCache()->get($this->getCacheKey())) { + return $cache; + } + + $response = $this->requestArray('sandboxnew/pay/getsignkey'); + + if ('SUCCESS' === $response['return_code']) { + $this->getCache()->set($this->getCacheKey(), $key = $response['sandbox_signkey'], 24 * 3600); + + return $key; + } + + throw new SandboxException($response['retmsg'] ?? $response['return_msg']); + } + + /** + * @return string + */ + protected function getCacheKey(): string + { + return 'easywechat.payment.sandbox.'.md5($this->app['config']->app_id.$this->app['config']['mch_id']); + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Sandbox/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Sandbox/ServiceProvider.php new file mode 100644 index 0000000..d0b5d6e --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Sandbox/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Sandbox; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author mingyoung + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * @param \Pimple\Container $app + */ + public function register(Container $app) + { + $app['sandbox'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Security/Client.php b/vendor/overtrue/wechat/src/Payment/Security/Client.php new file mode 100644 index 0000000..01aa752 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Security/Client.php @@ -0,0 +1,38 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Security; + +use EasyWeChat\Payment\Kernel\BaseClient; + +/** + * Class Client. + * + * @author overtrue + */ +class Client extends BaseClient +{ + /** + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getPublicKey() + { + $params = [ + 'sign_type' => 'MD5', + ]; + + return $this->safeRequest('https://fraud.mch.weixin.qq.com/risk/getpublickey', $params); + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Security/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Security/ServiceProvider.php new file mode 100644 index 0000000..e0e31f8 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Security/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Security; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['security'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Transfer/Client.php b/vendor/overtrue/wechat/src/Payment/Transfer/Client.php new file mode 100644 index 0000000..b8a7c44 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Transfer/Client.php @@ -0,0 +1,122 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Transfer; + +use EasyWeChat\Kernel\Exceptions\RuntimeException; +use function EasyWeChat\Kernel\Support\get_server_ip; +use function EasyWeChat\Kernel\Support\rsa_public_encrypt; +use EasyWeChat\Payment\Kernel\BaseClient; + +/** + * Class Client. + * + * @author AC + */ +class Client extends BaseClient +{ + /** + * Query MerchantPay to balance. + * + * @param string $partnerTradeNo + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function queryBalanceOrder(string $partnerTradeNo) + { + $params = [ + 'appid' => $this->app['config']->app_id, + 'mch_id' => $this->app['config']->mch_id, + 'partner_trade_no' => $partnerTradeNo, + ]; + + return $this->safeRequest('mmpaymkttransfers/gettransferinfo', $params); + } + + /** + * Send MerchantPay to balance. + * + * @param array $params + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function toBalance(array $params) + { + $base = [ + 'mch_id' => null, + 'mchid' => $this->app['config']->mch_id, + 'mch_appid' => $this->app['config']->app_id, + ]; + + if (empty($params['spbill_create_ip'])) { + $params['spbill_create_ip'] = get_server_ip(); + } + + return $this->safeRequest('mmpaymkttransfers/promotion/transfers', array_merge($base, $params)); + } + + /** + * Query MerchantPay order to BankCard. + * + * @param string $partnerTradeNo + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function queryBankCardOrder(string $partnerTradeNo) + { + $params = [ + 'mch_id' => $this->app['config']->mch_id, + 'partner_trade_no' => $partnerTradeNo, + ]; + + return $this->safeRequest('mmpaysptrans/query_bank', $params); + } + + /** + * Send MerchantPay to BankCard. + * + * @param array $params + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function toBankCard(array $params) + { + foreach (['bank_code', 'partner_trade_no', 'enc_bank_no', 'enc_true_name', 'amount'] as $key) { + if (empty($params[$key])) { + throw new RuntimeException(\sprintf('"%s" is required.', $key)); + } + } + + $publicKey = file_get_contents($this->app['config']->get('rsa_public_key_path')); + + $params['enc_bank_no'] = rsa_public_encrypt($params['enc_bank_no'], $publicKey); + $params['enc_true_name'] = rsa_public_encrypt($params['enc_true_name'], $publicKey); + + return $this->safeRequest('mmpaysptrans/pay_bank', $params); + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Transfer/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Transfer/ServiceProvider.php new file mode 100644 index 0000000..0e27aad --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Transfer/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Transfer; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['transfer'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Work/Agent/Client.php b/vendor/overtrue/wechat/src/Work/Agent/Client.php new file mode 100644 index 0000000..9e11aac --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Agent/Client.php @@ -0,0 +1,68 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Agent; + +use EasyWeChat\Kernel\BaseClient; + +/** + * This is WeWork Agent Client. + * + * @author mingyoung + */ +class Client extends BaseClient +{ + /** + * Get agent. + * + * @param int $agentId + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function get(int $agentId) + { + $params = [ + 'agentid' => $agentId, + ]; + + return $this->httpGet('cgi-bin/agent/get', $params); + } + + /** + * Set agent. + * + * @param int $agentId + * @param array $attributes + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function set(int $agentId, array $attributes) + { + return $this->httpPostJson('cgi-bin/agent/set', array_merge(['agentid' => $agentId], $attributes)); + } + + /** + * Get agent list. + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function list() + { + return $this->httpGet('cgi-bin/agent/list'); + } +} diff --git a/vendor/overtrue/wechat/src/Work/Agent/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/Agent/ServiceProvider.php new file mode 100644 index 0000000..b85a6f5 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Agent/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Agent; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author mingyoung + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['agent'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Work/Application.php b/vendor/overtrue/wechat/src/Work/Application.php new file mode 100644 index 0000000..ccd7c48 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Application.php @@ -0,0 +1,100 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work; + +use EasyWeChat\Kernel\ServiceContainer; +use EasyWeChat\Work\MiniProgram\Application as MiniProgram; + +/** + * Application. + * + * @author mingyoung + * + * @property \EasyWeChat\Work\OA\Client $oa + * @property \EasyWeChat\Work\Auth\AccessToken $access_token + * @property \EasyWeChat\Work\Agent\Client $agent + * @property \EasyWeChat\Work\Department\Client $department + * @property \EasyWeChat\Work\Media\Client $media + * @property \EasyWeChat\Work\Menu\Client $menu + * @property \EasyWeChat\Work\Message\Client $message + * @property \EasyWeChat\Work\Message\Messenger $messenger + * @property \EasyWeChat\Work\User\Client $user + * @property \EasyWeChat\Work\User\TagClient $tag + * @property \EasyWeChat\Work\Server\ServiceProvider $server + * @property \EasyWeChat\Work\Jssdk\Client $jssdk + * @property \Overtrue\Socialite\Providers\WeWorkProvider $oauth + * @property \EasyWeChat\Work\Invoice\Client $invoice + * @property \EasyWeChat\Work\Chat\Client $chat + * @property \EasyWeChat\Work\ExternalContact\Client $external_contact + * @property \EasyWeChat\Work\ExternalContact\ContactWayClient $contact_way + * @property \EasyWeChat\Work\ExternalContact\StatisticsClient $external_contact_statistics + * @property \EasyWeChat\Work\ExternalContact\MessageClient $external_contact_message + * @property \EasyWeChat\Work\GroupRobot\Client $group_robot + * @property \EasyWeChat\Work\GroupRobot\Messenger $group_robot_messenger + * + * @method mixed getCallbackIp() + */ +class Application extends ServiceContainer +{ + /** + * @var array + */ + protected $providers = [ + OA\ServiceProvider::class, + Auth\ServiceProvider::class, + Base\ServiceProvider::class, + Menu\ServiceProvider::class, + OAuth\ServiceProvider::class, + User\ServiceProvider::class, + Agent\ServiceProvider::class, + Media\ServiceProvider::class, + Message\ServiceProvider::class, + Department\ServiceProvider::class, + Server\ServiceProvider::class, + Jssdk\ServiceProvider::class, + Invoice\ServiceProvider::class, + Chat\ServiceProvider::class, + ExternalContact\ServiceProvider::class, + GroupRobot\ServiceProvider::class, + ]; + + /** + * @var array + */ + protected $defaultConfig = [ + // http://docs.guzzlephp.org/en/stable/request-options.html + 'http' => [ + 'base_uri' => 'https://qyapi.weixin.qq.com/', + ], + ]; + + /** + * Creates the miniProgram application. + * + * @return \EasyWeChat\Work\MiniProgram\Application + */ + public function miniProgram(): MiniProgram + { + return new MiniProgram($this->getConfig()); + } + + /** + * @param string $method + * @param array $arguments + * + * @return mixed + */ + public function __call($method, $arguments) + { + return $this['base']->$method(...$arguments); + } +} diff --git a/vendor/overtrue/wechat/src/Work/Auth/AccessToken.php b/vendor/overtrue/wechat/src/Work/Auth/AccessToken.php new file mode 100644 index 0000000..56d5218 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Auth/AccessToken.php @@ -0,0 +1,45 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Auth; + +use EasyWeChat\Kernel\AccessToken as BaseAccessToken; + +/** + * Class AccessToken. + * + * @author mingyoung + */ +class AccessToken extends BaseAccessToken +{ + /** + * @var string + */ + protected $endpointToGetToken = 'cgi-bin/gettoken'; + + /** + * @var int + */ + protected $safeSeconds = 0; + + /** + * Credential for get token. + * + * @return array + */ + protected function getCredentials(): array + { + return [ + 'corpid' => $this->app['config']['corp_id'], + 'corpsecret' => $this->app['config']['secret'], + ]; + } +} diff --git a/vendor/overtrue/wechat/src/Work/Auth/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/Auth/ServiceProvider.php new file mode 100644 index 0000000..5f0dba9 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Auth/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Auth; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + isset($app['access_token']) || $app['access_token'] = function ($app) { + return new AccessToken($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Work/Base/Client.php b/vendor/overtrue/wechat/src/Work/Base/Client.php new file mode 100644 index 0000000..97b5445 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Base/Client.php @@ -0,0 +1,34 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Base; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author mingyoung + */ +class Client extends BaseClient +{ + /** + * Get callback ip. + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function getCallbackIp() + { + return $this->httpGet('cgi-bin/getcallbackip'); + } +} diff --git a/vendor/overtrue/wechat/src/Work/Base/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/Base/ServiceProvider.php new file mode 100644 index 0000000..5e28fd1 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Base/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Base; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author mingyoung + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * @param Container $app + */ + public function register(Container $app) + { + $app['base'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Work/Chat/Client.php b/vendor/overtrue/wechat/src/Work/Chat/Client.php new file mode 100644 index 0000000..0d4fdec --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Chat/Client.php @@ -0,0 +1,82 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Chat; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author XiaolonY + */ +class Client extends BaseClient +{ + /** + * Get chat. + * + * @param string $chatId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function get(string $chatId) + { + return $this->httpGet('cgi-bin/appchat/get', ['chatid' => $chatId]); + } + + /** + * Create chat. + * + * @param array $data + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function create(array $data) + { + return $this->httpPostJson('cgi-bin/appchat/create', $data); + } + + /** + * Update chat. + * + * @param string $chatId + * @param array $data + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function update(string $chatId, array $data) + { + return $this->httpPostJson('cgi-bin/appchat/update', array_merge(['chatid' => $chatId], $data)); + } + + /** + * Send a message. + * + * @param array $message + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function send(array $message) + { + return $this->httpPostJson('cgi-bin/appchat/send', $message); + } +} diff --git a/vendor/overtrue/wechat/src/Work/Chat/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/Chat/ServiceProvider.php new file mode 100644 index 0000000..5bf9b2c --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Chat/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Chat; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author XiaolonY + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['chat'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Work/Department/Client.php b/vendor/overtrue/wechat/src/Work/Department/Client.php new file mode 100644 index 0000000..c34cc86 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Department/Client.php @@ -0,0 +1,81 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Department; + +use EasyWeChat\Kernel\BaseClient; + +/** + * This is WeWork Department Client. + * + * @author mingyoung + */ +class Client extends BaseClient +{ + /** + * Create a department. + * + * @param array $data + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function create(array $data) + { + return $this->httpPostJson('cgi-bin/department/create', $data); + } + + /** + * Update a department. + * + * @param int $id + * @param array $data + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function update(int $id, array $data) + { + return $this->httpPostJson('cgi-bin/department/update', array_merge(compact('id'), $data)); + } + + /** + * Delete a department. + * + * @param int $id + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function delete($id) + { + return $this->httpGet('cgi-bin/department/delete', compact('id')); + } + + /** + * Get department lists. + * + * @param int|null $id + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function list($id = null) + { + return $this->httpGet('cgi-bin/department/list', compact('id')); + } +} diff --git a/vendor/overtrue/wechat/src/Work/Department/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/Department/ServiceProvider.php new file mode 100644 index 0000000..2fe5bc6 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Department/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Department; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author mingyoung + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['department'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Work/ExternalContact/Client.php b/vendor/overtrue/wechat/src/Work/ExternalContact/Client.php new file mode 100644 index 0000000..477686f --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/ExternalContact/Client.php @@ -0,0 +1,118 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\ExternalContact; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author mingyoung + */ +class Client extends BaseClient +{ + /** + * 获取配置了客户联系功能的成员列表. + * + * @see https://work.weixin.qq.com/api/doc#90000/90135/91554 + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function getFollowUsers() + { + return $this->httpGet('cgi-bin/externalcontact/get_follow_user_list'); + } + + /** + * 获取外部联系人列表. + * + * @see https://work.weixin.qq.com/api/doc#90000/90135/91555 + * + * @param string $userId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function list(string $userId) + { + return $this->httpGet('cgi-bin/externalcontact/list', [ + 'userid' => $userId, + ]); + } + + /** + * 获取外部联系人详情. + * + * @see https://work.weixin.qq.com/api/doc#90000/90135/91556 + * + * @param string $externalUserId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function get(string $externalUserId) + { + return $this->httpGet('cgi-bin/externalcontact/get', [ + 'external_userid' => $externalUserId, + ]); + } + + /** + * 获取离职成员的客户列表. + * + * @see https://work.weixin.qq.com/api/doc#90000/90135/91563 + * + * @param int $pageId + * @param int $pageSize + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getUnassigned(int $pageId = 0, int $pageSize = 1000) + { + return $this->httpPostJson('cgi-bin/externalcontact/get_unassigned_list', [ + 'page_id' => $pageId, + 'page_size' => $pageSize, + ]); + } + + /** + * 离职成员的外部联系人再分配. + * + * @see https://work.weixin.qq.com/api/doc#90000/90135/91564 + * + * @param string $externalUserId + * @param string $handoverUserId + * @param string $takeoverUserId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function transfer(string $externalUserId, string $handoverUserId, string $takeoverUserId) + { + $params = [ + 'external_userid' => $externalUserId, + 'handover_userid' => $handoverUserId, + 'takeover_userid' => $takeoverUserId, + ]; + + return $this->httpPostJson('cgi-bin/externalcontact/transfer', $params); + } +} diff --git a/vendor/overtrue/wechat/src/Work/ExternalContact/ContactWayClient.php b/vendor/overtrue/wechat/src/Work/ExternalContact/ContactWayClient.php new file mode 100644 index 0000000..7cfc2bb --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/ExternalContact/ContactWayClient.php @@ -0,0 +1,98 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\ExternalContact; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class ContactWayClient. + * + * @author milkmeowo + */ +class ContactWayClient extends BaseClient +{ + /** + * 配置客户联系「联系我」方式. + * + * @param int $type + * @param int $scene + * @param array $config + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function create(int $type, int $scene, array $config = []) + { + $params = array_merge([ + 'type' => $type, + 'scene' => $scene, + ], $config); + + return $this->httpPostJson('cgi-bin/externalcontact/add_contact_way', $params); + } + + /** + * 获取企业已配置的「联系我」方式. + * + * @param string $configId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function get(string $configId) + { + return $this->httpPostJson('cgi-bin/externalcontact/get_contact_way', [ + 'config_id' => $configId, + ]); + } + + /** + * 更新企业已配置的「联系我」方式. + * + * @param string $configId + * @param array $config + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function update(string $configId, array $config = []) + { + $params = array_merge([ + 'config_id' => $configId, + ], $config); + + return $this->httpPostJson('cgi-bin/externalcontact/update_contact_way', $params); + } + + /** + * 删除企业已配置的「联系我」方式. + * + * @param string $configId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function delete(string $configId) + { + return $this->httpPostJson('cgi-bin/externalcontact/del_contact_way', [ + 'config_id' => $configId, + ]); + } +} diff --git a/vendor/overtrue/wechat/src/Work/ExternalContact/MessageClient.php b/vendor/overtrue/wechat/src/Work/ExternalContact/MessageClient.php new file mode 100644 index 0000000..ce00f80 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/ExternalContact/MessageClient.php @@ -0,0 +1,168 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\ExternalContact; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; + +/** + * Class MessageClient. + * + * @author milkmeowo + */ +class MessageClient extends BaseClient +{ + /** + * Required attributes. + * + * @var array + */ + protected $required = ['content', 'media_id', 'title', 'url', 'pic_media_id', 'appid', 'page']; + + protected $textMessage = [ + 'content' => '', + ]; + + protected $imageMessage = [ + 'media_id' => '', + ]; + + protected $linkMessage = [ + 'title' => '', + 'picurl' => '', + 'desc' => '', + 'url' => '', + ]; + + protected $miniprogramMessage = [ + 'title' => '', + 'pic_media_id' => '', + 'appid' => '', + 'page' => '', + ]; + + /** + * 添加企业群发消息模板 + * + * @see https://work.weixin.qq.com/api/doc#90000/90135/91560 + * + * @param array $msg + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function submit(array $msg) + { + $params = $this->formatMessage($msg); + + return $this->httpPostJson('cgi-bin/externalcontact/add_msg_template', $params); + } + + /** + * 获取企业群发消息发送结果. + * + * @see https://work.weixin.qq.com/api/doc#90000/90135/91561 + * + * @param string $msgId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function get(string $msgId) + { + return $this->httpPostJson('cgi-bin/externalcontact/get_group_msg_result', [ + 'msgid' => $msgId, + ]); + } + + /** + * 发送新客户欢迎语. + * + * @see https://work.weixin.qq.com/api/doc#90000/90135/91688 + * + * @param string $welcomeCode + * @param array $msg + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function sendWelcome(string $welcomeCode, array $msg) + { + $formattedMsg = $this->formatMessage($msg); + + $params = array_merge($formattedMsg, [ + 'welcome_code' => $welcomeCode, + ]); + + return $this->httpPostJson('cgi-bin/externalcontact/send_welcome_msg', $params); + } + + /** + * @param array $data + * + * @return array + * + * @throws InvalidArgumentException + */ + protected function formatMessage(array $data = []) + { + $params = $data; + + if (!empty($params['text'])) { + $params['text'] = $this->formatFields($params['text'], $this->textMessage); + } + + if (!empty($params['image'])) { + $params['image'] = $this->formatFields($params['image'], $this->imageMessage); + } + + if (!empty($params['link'])) { + $params['link'] = $this->formatFields($params['link'], $this->linkMessage); + } + + if (!empty($params['miniprogram'])) { + $params['miniprogram'] = $this->formatFields($params['miniprogram'], $this->miniprogramMessage); + } + + return $params; + } + + /** + * @param array $data + * @param array $default + * + * @return array + * + * @throws InvalidArgumentException + */ + protected function formatFields(array $data = [], array $default = []) + { + $params = array_merge($default, $data); + foreach ($params as $key => $value) { + if (in_array($key, $this->required, true) && empty($value) && empty($default[$key])) { + throw new InvalidArgumentException(sprintf('Attribute "%s" can not be empty!', $key)); + } + + $params[$key] = empty($value) ? $default[$key] : $value; + } + + return $params; + } +} diff --git a/vendor/overtrue/wechat/src/Work/ExternalContact/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/ExternalContact/ServiceProvider.php new file mode 100644 index 0000000..b76630a --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/ExternalContact/ServiceProvider.php @@ -0,0 +1,45 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\ExternalContact; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author mingyoung + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['external_contact'] = function ($app) { + return new Client($app); + }; + + $app['contact_way'] = function ($app) { + return new ContactWayClient($app); + }; + + $app['external_contact_statistics'] = function ($app) { + return new StatisticsClient($app); + }; + + $app['external_contact_message'] = function ($app) { + return new MessageClient($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Work/ExternalContact/StatisticsClient.php b/vendor/overtrue/wechat/src/Work/ExternalContact/StatisticsClient.php new file mode 100644 index 0000000..c6922df --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/ExternalContact/StatisticsClient.php @@ -0,0 +1,47 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\ExternalContact; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class StatisticsClient. + * + * @author milkmeowo + */ +class StatisticsClient extends BaseClient +{ + /** + * 获取员工行为数据. + * + * @see https://work.weixin.qq.com/api/doc#90000/90135/91580 + * + * @param array $userIds + * @param string $from + * @param string $to + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function userBehavior(array $userIds, string $from, string $to) + { + $params = [ + 'userid' => $userIds, + 'start_time' => $from, + 'end_time' => $to, + ]; + + return $this->httpPostJson('cgi-bin/externalcontact/get_user_behavior_data', $params); + } +} diff --git a/vendor/overtrue/wechat/src/Work/GroupRobot/Client.php b/vendor/overtrue/wechat/src/Work/GroupRobot/Client.php new file mode 100644 index 0000000..dcc1a8f --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/GroupRobot/Client.php @@ -0,0 +1,49 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\GroupRobot; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Work\GroupRobot\Messages\Message; + +/** + * Class Client. + * + * @author her-cat + */ +class Client extends BaseClient +{ + /** + * @param string|Message $message + * + * @return Messenger + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + public function message($message) + { + return (new Messenger($this))->message($message); + } + + /** + * @param string $key + * @param array $message + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function send(string $key, array $message) + { + return $this->httpPostJson('cgi-bin/webhook/send', $message, ['key' => $key]); + } +} diff --git a/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/Image.php b/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/Image.php new file mode 100644 index 0000000..14beb81 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/Image.php @@ -0,0 +1,41 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\GroupRobot\Messages; + +/** + * Class Image. + * + * @author her-cat + */ +class Image extends Message +{ + /** + * @var string + */ + protected $type = 'image'; + + /** + * @var array + */ + protected $properties = ['base64', 'md5']; + + /** + * Image constructor. + * + * @param string $base64 + * @param string $md5 + */ + public function __construct(string $base64, string $md5) + { + parent::__construct(compact('base64', 'md5')); + } +} diff --git a/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/Markdown.php b/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/Markdown.php new file mode 100644 index 0000000..dfe6980 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/Markdown.php @@ -0,0 +1,40 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\GroupRobot\Messages; + +/** + * Class Markdown. + * + * @author her-cat + */ +class Markdown extends Message +{ + /** + * @var string + */ + protected $type = 'markdown'; + + /** + * @var array + */ + protected $properties = ['content']; + + /** + * Markdown constructor. + * + * @param string $content + */ + public function __construct(string $content) + { + parent::__construct(compact('content')); + } +} diff --git a/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/Message.php b/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/Message.php new file mode 100644 index 0000000..50201b9 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/Message.php @@ -0,0 +1,23 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\GroupRobot\Messages; + +use EasyWeChat\Kernel\Messages\Message as BaseMessage; + +/** + * Class Message. + * + * @author her-cat + */ +class Message extends BaseMessage +{ +} diff --git a/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/News.php b/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/News.php new file mode 100644 index 0000000..835023e --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/News.php @@ -0,0 +1,55 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\GroupRobot\Messages; + +/** + * Class News. + * + * @author her-cat + */ +class News extends Message +{ + /** + * @var string + */ + protected $type = 'news'; + + /** + * @var array + */ + protected $properties = ['items']; + + /** + * News constructor. + * + * @param array $items + */ + public function __construct(array $items = []) + { + parent::__construct(compact('items')); + } + + /** + * @param array $data + * @param array $aliases + * + * @return array + */ + public function propertiesToArray(array $data, array $aliases = []): array + { + return ['articles' => array_map(function ($item) { + if ($item instanceof NewsItem) { + return $item->toJsonArray(); + } + }, $this->get('items'))]; + } +} diff --git a/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/NewsItem.php b/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/NewsItem.php new file mode 100644 index 0000000..2d206de --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/NewsItem.php @@ -0,0 +1,45 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\GroupRobot\Messages; + +/** + * Class NewsItem. + * + * @author her-cat + */ +class NewsItem extends Message +{ + /** + * @var string + */ + protected $type = 'news'; + + /** + * @var array + */ + protected $properties = [ + 'title', + 'description', + 'url', + 'image', + ]; + + public function toJsonArray() + { + return [ + 'title' => $this->get('title'), + 'description' => $this->get('description'), + 'url' => $this->get('url'), + 'picurl' => $this->get('image'), + ]; + } +} diff --git a/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/Text.php b/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/Text.php new file mode 100644 index 0000000..a1e6dda --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/Text.php @@ -0,0 +1,70 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\GroupRobot\Messages; + +/** + * Class Text. + * + * @author her-cat + */ +class Text extends Message +{ + /** + * @var string + */ + protected $type = 'text'; + + /** + * @var array + */ + protected $properties = ['content', 'mentioned_list', 'mentioned_mobile_list']; + + /** + * Text constructor. + * + * @param string $content + * @param string|array $userIds + * @param string|array $mobiles + */ + public function __construct(string $content, $userIds = [], $mobiles = []) + { + parent::__construct([ + 'content' => $content, + 'mentioned_list' => (array) $userIds, + 'mentioned_mobile_list' => (array) $mobiles, + ]); + } + + /** + * @param array $userIds + * + * @return Text + */ + public function mention($userIds) + { + $this->set('mentioned_list', (array) $userIds); + + return $this; + } + + /** + * @param array $mobiles + * + * @return Text + */ + public function mentionByMobile($mobiles) + { + $this->set('mentioned_mobile_list', (array) $mobiles); + + return $this; + } +} diff --git a/vendor/overtrue/wechat/src/Work/GroupRobot/Messenger.php b/vendor/overtrue/wechat/src/Work/GroupRobot/Messenger.php new file mode 100644 index 0000000..573296a --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/GroupRobot/Messenger.php @@ -0,0 +1,129 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\GroupRobot; + +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; +use EasyWeChat\Kernel\Exceptions\InvalidConfigException; +use EasyWeChat\Kernel\Exceptions\RuntimeException; +use EasyWeChat\Work\GroupRobot\Messages\Message; +use EasyWeChat\Work\GroupRobot\Messages\Text; + +/** + * Class Messenger. + * + * @author her-cat + */ +class Messenger +{ + /** + * @var Client + */ + protected $client; + + /** + * @var Message|null + */ + protected $message; + + /** + * @var string|null + */ + protected $groupKey; + + /** + * Messenger constructor. + * + * @param Client $client + */ + public function __construct(Client $client) + { + $this->client = $client; + } + + /** + * @param string|Message $message + * + * @return Messenger + * + * @throws InvalidArgumentException + */ + public function message($message) + { + if (is_string($message) || is_numeric($message)) { + $message = new Text($message); + } + + if (!($message instanceof Message)) { + throw new InvalidArgumentException('Invalid message.'); + } + + $this->message = $message; + + return $this; + } + + /** + * @param string $groupKey + * + * @return Messenger + */ + public function toGroup(string $groupKey) + { + $this->groupKey = $groupKey; + + return $this; + } + + /** + * @param string|Message|null $message + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws RuntimeException + * @throws InvalidArgumentException + * @throws InvalidConfigException + */ + public function send($message = null) + { + if ($message) { + $this->message($message); + } + + if (empty($this->message)) { + throw new RuntimeException('No message to send.'); + } + + if (is_null($this->groupKey)) { + throw new RuntimeException('No group key specified.'); + } + + $message = $this->message->transformForJsonRequest(); + + return $this->client->send($this->groupKey, $message); + } + + /** + * @param string $property + * + * @return mixed + * + * @throws InvalidArgumentException + */ + public function __get($property) + { + if (property_exists($this, $property)) { + return $this->$property; + } + + throw new InvalidArgumentException(sprintf('No property named "%s"', $property)); + } +} diff --git a/vendor/overtrue/wechat/src/Work/GroupRobot/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/GroupRobot/ServiceProvider.php new file mode 100644 index 0000000..d72d630 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/GroupRobot/ServiceProvider.php @@ -0,0 +1,37 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\GroupRobot; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author her-cat + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc} + */ + public function register(Container $app) + { + $app['group_robot'] = function ($app) { + return new Client($app); + }; + + $app['group_robot_messenger'] = function ($app) { + return new Messenger($app['group_robot']); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Work/Invoice/Client.php b/vendor/overtrue/wechat/src/Work/Invoice/Client.php new file mode 100644 index 0000000..edbd34b --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Invoice/Client.php @@ -0,0 +1,100 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Invoice; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author mingyoung + */ +class Client extends BaseClient +{ + /** + * @param string $cardId + * @param string $encryptCode + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function get(string $cardId, string $encryptCode) + { + $params = [ + 'card_id' => $cardId, + 'encrypt_code' => $encryptCode, + ]; + + return $this->httpPostJson('cgi-bin/card/invoice/reimburse/getinvoiceinfo', $params); + } + + /** + * @param array $invoices + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function select(array $invoices) + { + $params = [ + 'item_list' => $invoices, + ]; + + return $this->httpPostJson('cgi-bin/card/invoice/reimburse/getinvoiceinfobatch', $params); + } + + /** + * @param string $cardId + * @param string $encryptCode + * @param string $status + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function update(string $cardId, string $encryptCode, string $status) + { + $params = [ + 'card_id' => $cardId, + 'encrypt_code' => $encryptCode, + 'reimburse_status' => $status, + ]; + + return $this->httpPostJson('cgi-bin/card/invoice/reimburse/updateinvoicestatus', $params); + } + + /** + * @param array $invoices + * @param string $openid + * @param string $status + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function batchUpdate(array $invoices, string $openid, string $status) + { + $params = [ + 'openid' => $openid, + 'reimburse_status' => $status, + 'invoice_list' => $invoices, + ]; + + return $this->httpPostJson('cgi-bin/card/invoice/reimburse/updatestatusbatch', $params); + } +} diff --git a/vendor/overtrue/wechat/src/Work/Invoice/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/Invoice/ServiceProvider.php new file mode 100644 index 0000000..3259d80 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Invoice/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Invoice; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author mingyoung + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['invoice'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Work/Jssdk/Client.php b/vendor/overtrue/wechat/src/Work/Jssdk/Client.php new file mode 100644 index 0000000..40fc368 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Jssdk/Client.php @@ -0,0 +1,68 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Jssdk; + +use EasyWeChat\BasicService\Jssdk\Client as BaseClient; +use EasyWeChat\Kernel\Exceptions\RuntimeException; + +/** + * Class Client. + * + * @author mingyoung + */ +class Client extends BaseClient +{ + protected $ticketEndpoint = 'https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket'; + + /** + * @return string + */ + protected function getAppId() + { + return $this->app['config']->get('corp_id'); + } + + /** + * @param bool $refresh + * @param string $type + * + * @return array|\EasyWeChat\Kernel\Support\Collection|mixed|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws RuntimeException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + * @throws \Psr\SimpleCache\InvalidArgumentException + */ + public function getAgentTicket(bool $refresh = false, string $type = 'agent_config') + { + $cacheKey = sprintf('easywechat.work.jssdk.ticket.%s.%s', $type, $this->getAppId()); + + if (!$refresh && $this->getCache()->has($cacheKey)) { + return $this->getCache()->get($cacheKey); + } + + /** @var array $result */ + $result = $this->castResponseToType( + $this->requestRaw('cgi-bin/ticket/get', 'GET', ['query' => ['type' => $type]]), + 'array' + ); + + $this->getCache()->set($cacheKey, $result, $result['expires_in'] - 500); + + if (!$this->getCache()->has($cacheKey)) { + throw new RuntimeException('Failed to cache jssdk ticket.'); + } + + return $result; + } +} diff --git a/vendor/overtrue/wechat/src/Work/Jssdk/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/Jssdk/ServiceProvider.php new file mode 100644 index 0000000..efb509c --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Jssdk/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Jssdk; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author mingyoung + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['jssdk'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Work/Media/Client.php b/vendor/overtrue/wechat/src/Work/Media/Client.php new file mode 100644 index 0000000..16e8a94 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Media/Client.php @@ -0,0 +1,116 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Media; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\Http\StreamResponse; + +/** + * Class Client. + * + * @author mingyoung + */ +class Client extends BaseClient +{ + /** + * Get media. + * + * @param string $mediaId + * + * @return array|\EasyWeChat\Kernel\Http\Response|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function get(string $mediaId) + { + $response = $this->requestRaw('cgi-bin/media/get', 'GET', [ + 'query' => [ + 'media_id' => $mediaId, + ], + ]); + + if (false !== stripos($response->getHeaderLine('Content-Type'), 'text/plain')) { + return $this->castResponseToType($response, $this->app['config']->get('response_type')); + } + + return StreamResponse::buildFromPsrResponse($response); + } + + /** + * Upload Image. + * + * @param string $path + * + * @return mixed + */ + public function uploadImage(string $path) + { + return $this->upload('image', $path); + } + + /** + * Upload Voice. + * + * @param string $path + * + * @return mixed + */ + public function uploadVoice(string $path) + { + return $this->upload('voice', $path); + } + + /** + * Upload Video. + * + * @param string $path + * + * @return mixed + */ + public function uploadVideo(string $path) + { + return $this->upload('video', $path); + } + + /** + * Upload File. + * + * @param string $path + * + * @return mixed + */ + public function uploadFile(string $path) + { + return $this->upload('file', $path); + } + + /** + * Upload media. + * + * @param string $type + * @param string $path + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function upload(string $type, string $path) + { + $files = [ + 'media' => $path, + ]; + + return $this->httpUpload('cgi-bin/media/upload', $files, [], compact('type')); + } +} diff --git a/vendor/overtrue/wechat/src/Work/Media/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/Media/ServiceProvider.php new file mode 100644 index 0000000..450d156 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Media/ServiceProvider.php @@ -0,0 +1,28 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Media; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['media'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Work/Menu/Client.php b/vendor/overtrue/wechat/src/Work/Menu/Client.php new file mode 100644 index 0000000..e57a3a1 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Menu/Client.php @@ -0,0 +1,61 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Menu; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author mingyoung + */ +class Client extends BaseClient +{ + /** + * Get menu. + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function get() + { + return $this->httpGet('cgi-bin/menu/get', ['agentid' => $this->app['config']['agent_id']]); + } + + /** + * Create menu for the given agent. + * + * @param array $data + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function create(array $data) + { + return $this->httpPostJson('cgi-bin/menu/create', $data, ['agentid' => $this->app['config']['agent_id']]); + } + + /** + * Delete menu. + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function delete() + { + return $this->httpGet('cgi-bin/menu/delete', ['agentid' => $this->app['config']['agent_id']]); + } +} diff --git a/vendor/overtrue/wechat/src/Work/Menu/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/Menu/ServiceProvider.php new file mode 100644 index 0000000..02c4290 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Menu/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Menu; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author mingyoung + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['menu'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Work/Message/Client.php b/vendor/overtrue/wechat/src/Work/Message/Client.php new file mode 100644 index 0000000..1497586 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Message/Client.php @@ -0,0 +1,48 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Message; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\Messages\Message; + +/** + * Class Client. + * + * @author mingyoung + */ +class Client extends BaseClient +{ + /** + * @param string|\EasyWeChat\Kernel\Messages\Message $message + * + * @return \EasyWeChat\Work\Message\Messenger + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + public function message($message) + { + return (new Messenger($this))->message($message); + } + + /** + * @param array $message + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function send(array $message) + { + return $this->httpPostJson('cgi-bin/message/send', $message); + } +} diff --git a/vendor/overtrue/wechat/src/Work/Message/Messenger.php b/vendor/overtrue/wechat/src/Work/Message/Messenger.php new file mode 100644 index 0000000..62bc62d --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Message/Messenger.php @@ -0,0 +1,205 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Message; + +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; +use EasyWeChat\Kernel\Exceptions\RuntimeException; +use EasyWeChat\Kernel\Messages\Message; +use EasyWeChat\Kernel\Messages\Text; + +/** + * Class MessageBuilder. + * + * @author overtrue + */ +class Messenger +{ + /** + * @var \EasyWeChat\Kernel\Messages\Message; + */ + protected $message; + + /** + * @var array + */ + protected $to = ['touser' => '@all']; + + /** + * @var int + */ + protected $agentId; + + /** + * @var bool + */ + protected $secretive = false; + + /** + * @var \EasyWeChat\Work\Message\Client + */ + protected $client; + + /** + * MessageBuilder constructor. + * + * @param \EasyWeChat\Work\Message\Client $client + */ + public function __construct(Client $client) + { + $this->client = $client; + } + + /** + * Set message to send. + * + * @param string|Message $message + * + * @return \EasyWeChat\Work\Message\Messenger + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + public function message($message) + { + if (is_string($message) || is_numeric($message)) { + $message = new Text($message); + } + + if (!($message instanceof Message)) { + throw new InvalidArgumentException('Invalid message.'); + } + + $this->message = $message; + + return $this; + } + + /** + * @param int $agentId + * + * @return \EasyWeChat\Work\Message\Messenger + */ + public function ofAgent(int $agentId) + { + $this->agentId = $agentId; + + return $this; + } + + /** + * @param array|string $userIds + * + * @return \EasyWeChat\Work\Message\Messenger + */ + public function toUser($userIds) + { + return $this->setRecipients($userIds, 'touser'); + } + + /** + * @param array|string $partyIds + * + * @return \EasyWeChat\Work\Message\Messenger + */ + public function toParty($partyIds) + { + return $this->setRecipients($partyIds, 'toparty'); + } + + /** + * @param array|string $tagIds + * + * @return \EasyWeChat\Work\Message\Messenger + */ + public function toTag($tagIds) + { + return $this->setRecipients($tagIds, 'totag'); + } + + /** + * Keep secret. + * + * @return \EasyWeChat\Work\Message\Messenger + */ + public function secretive() + { + $this->secretive = true; + + return $this; + } + + /** + * @param array|string $ids + * @param string $key + * + * @return \EasyWeChat\Work\Message\Messenger + */ + protected function setRecipients($ids, string $key): self + { + if (is_array($ids)) { + $ids = implode('|', $ids); + } + + $this->to = [$key => $ids]; + + return $this; + } + + /** + * @param \EasyWeChat\Kernel\Messages\Message|string|null $message + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + public function send($message = null) + { + if ($message) { + $this->message($message); + } + + if (empty($this->message)) { + throw new RuntimeException('No message to send.'); + } + + if (is_null($this->agentId)) { + throw new RuntimeException('No agentid specified.'); + } + + $message = $this->message->transformForJsonRequest(array_merge([ + 'agentid' => $this->agentId, + 'safe' => intval($this->secretive), + ], $this->to)); + + $this->secretive = false; + + return $this->client->send($message); + } + + /** + * Return property. + * + * @param string $property + * + * @return mixed + * + * @throws InvalidArgumentException + */ + public function __get($property) + { + if (property_exists($this, $property)) { + return $this->$property; + } + + throw new InvalidArgumentException(sprintf('No property named "%s"', $property)); + } +} diff --git a/vendor/overtrue/wechat/src/Work/Message/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/Message/ServiceProvider.php new file mode 100644 index 0000000..18193bd --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Message/ServiceProvider.php @@ -0,0 +1,43 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Message; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author mingyoung + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['message'] = function ($app) { + return new Client($app); + }; + + $app['messenger'] = function ($app) { + $messenger = new Messenger($app['message']); + + if (is_int($app['config']['agent_id'])) { + $messenger->ofAgent($app['config']['agent_id']); + } + + return $messenger; + }; + } +} diff --git a/vendor/overtrue/wechat/src/Work/MiniProgram/Application.php b/vendor/overtrue/wechat/src/Work/MiniProgram/Application.php new file mode 100644 index 0000000..aa290cd --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/MiniProgram/Application.php @@ -0,0 +1,44 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\MiniProgram; + +use EasyWeChat\MiniProgram\Application as MiniProgram; +use EasyWeChat\Work\Auth\AccessToken; +use EasyWeChat\Work\MiniProgram\Auth\Client; + +/** + * Class Application. + * + * @author Caikeal + * + * @property \EasyWeChat\Work\MiniProgram\Auth\Client $auth + */ +class Application extends MiniProgram +{ + /** + * Application constructor. + * + * @param array $config + * @param array $prepends + */ + public function __construct(array $config = [], array $prepends = []) + { + parent::__construct($config, $prepends + [ + 'access_token' => function ($app) { + return new AccessToken($app); + }, + 'auth' => function ($app) { + return new Client($app); + }, + ]); + } +} diff --git a/vendor/overtrue/wechat/src/Work/MiniProgram/Auth/Client.php b/vendor/overtrue/wechat/src/Work/MiniProgram/Auth/Client.php new file mode 100644 index 0000000..ac9baff --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/MiniProgram/Auth/Client.php @@ -0,0 +1,39 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\MiniProgram\Auth; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + */ +class Client extends BaseClient +{ + /** + * Get session info by code. + * + * @param string $code + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function session(string $code) + { + $params = [ + 'js_code' => $code, + 'grant_type' => 'authorization_code', + ]; + + return $this->httpGet('cgi-bin/miniprogram/jscode2session', $params); + } +} diff --git a/vendor/overtrue/wechat/src/Work/OA/Client.php b/vendor/overtrue/wechat/src/Work/OA/Client.php new file mode 100644 index 0000000..483d892 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/OA/Client.php @@ -0,0 +1,171 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\OA; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author mingyoung + */ +class Client extends BaseClient +{ + /** + * Get the checkin data. + * + * @param int $startTime + * @param int $endTime + * @param array $userList + * @param int $type + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function checkinRecords(int $startTime, int $endTime, array $userList, int $type = 3) + { + $params = [ + 'opencheckindatatype' => $type, + 'starttime' => $startTime, + 'endtime' => $endTime, + 'useridlist' => $userList, + ]; + + return $this->httpPostJson('cgi-bin/checkin/getcheckindata', $params); + } + + /** + * Get the checkin rules. + * + * @param int $datetime + * @param array $userList + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function checkinRules(int $datetime, array $userList) + { + $params = [ + 'datetime' => $datetime, + 'useridlist' => $userList, + ]; + + return $this->httpPostJson('cgi-bin/checkin/getcheckinoption', $params); + } + + /** + * Get approval template details. + * + * @param string $templateId + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function approvalTemplate(string $templateId) + { + $params = [ + 'template_id' => $templateId, + ]; + + return $this->httpPostJson('cgi-bin/oa/gettemplatedetail', $params); + } + + /** + * Submit an application for approval. + * + * @param array $data + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function createApproval(array $data) + { + return $this->httpPostJson('cgi-bin/oa/applyevent', $data); + } + + /** + * Get Approval number. + * + * @param int $startTime + * @param int $endTime + * @param int $nextCursor + * @param int $size + * @param array $filters + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function approvalNumbers(int $startTime, int $endTime, int $nextCursor = 0, int $size = 100, array $filters = []) + { + $params = [ + 'starttime' => $startTime, + 'endtime' => $endTime, + 'cursor' => $nextCursor, + 'size' => $size > 100 ? 100 : $size, + 'filters' => $filters, + ]; + + return $this->httpPostJson('cgi-bin/oa/getapprovalinfo', $params); + } + + /** + * Get approval detail. + * + * @param int $number + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function approvalDetail(int $number) + { + $params = [ + 'sp_no' => $number, + ]; + + return $this->httpPostJson('cgi-bin/oa/getapprovaldetail', $params); + } + + /** + * Get Approval Data. + * + * @param int $startTime + * @param int $endTime + * @param int $nextNumber + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function approvalRecords(int $startTime, int $endTime, int $nextNumber = null) + { + $params = [ + 'starttime' => $startTime, + 'endtime' => $endTime, + 'next_spnum' => $nextNumber, + ]; + + return $this->httpPostJson('cgi-bin/corp/getapprovaldata', $params); + } +} diff --git a/vendor/overtrue/wechat/src/Work/OA/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/OA/ServiceProvider.php new file mode 100644 index 0000000..5d389c2 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/OA/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\OA; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author mingyoung + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['oa'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Work/OAuth/AccessTokenDelegate.php b/vendor/overtrue/wechat/src/Work/OAuth/AccessTokenDelegate.php new file mode 100644 index 0000000..9acfbbb --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/OAuth/AccessTokenDelegate.php @@ -0,0 +1,46 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\OAuth; + +use EasyWeChat\Work\Application; +use Overtrue\Socialite\AccessTokenInterface; + +/** + * Class AccessTokenDelegate. + * + * @author mingyoung + */ +class AccessTokenDelegate implements AccessTokenInterface +{ + /** + * @var \EasyWeChat\Work\Application + */ + protected $app; + + /** + * @param \EasyWeChat\Work\Application $app + */ + public function __construct(Application $app) + { + $this->app = $app; + } + + /** + * Return the access token string. + * + * @return string + */ + public function getToken() + { + return $this->app['access_token']->getToken()['access_token']; + } +} diff --git a/vendor/overtrue/wechat/src/Work/OAuth/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/OAuth/ServiceProvider.php new file mode 100644 index 0000000..b1fdfce --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/OAuth/ServiceProvider.php @@ -0,0 +1,62 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\OAuth; + +use Overtrue\Socialite\SocialiteManager; +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +class ServiceProvider implements ServiceProviderInterface +{ + public function register(Container $app) + { + $app['oauth'] = function ($app) { + $socialite = (new SocialiteManager([ + 'wework' => [ + 'client_id' => $app['config']['corp_id'], + 'client_secret' => null, + 'redirect' => $this->prepareCallbackUrl($app), + ], + ], $app['request']))->driver('wework'); + + $scopes = (array) $app['config']->get('oauth.scopes', ['snsapi_base']); + + if (!empty($scopes)) { + $socialite->scopes($scopes); + } else { + $socialite->setAgentId($app['config']['agent_id']); + } + + return $socialite->setAccessToken(new AccessTokenDelegate($app)); + }; + } + + /** + * Prepare the OAuth callback url for wechat. + * + * @param Container $app + * + * @return string + */ + private function prepareCallbackUrl($app) + { + $callback = $app['config']->get('oauth.callback'); + + if (0 === stripos($callback, 'http')) { + return $callback; + } + + $baseUrl = $app['request']->getSchemeAndHttpHost(); + + return $baseUrl.'/'.ltrim($callback, '/'); + } +} diff --git a/vendor/overtrue/wechat/src/Work/Server/Guard.php b/vendor/overtrue/wechat/src/Work/Server/Guard.php new file mode 100644 index 0000000..b77c6c1 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Server/Guard.php @@ -0,0 +1,48 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Server; + +use EasyWeChat\Kernel\ServerGuard; + +/** + * Class Guard. + * + * @author overtrue + */ +class Guard extends ServerGuard +{ + /** + * @return $this + */ + public function validate() + { + return $this; + } + + /** + * Check the request message safe mode. + * + * @return bool + */ + protected function isSafeMode(): bool + { + return true; + } + + /** + * @return bool + */ + protected function shouldReturnRawResponse(): bool + { + return !is_null($this->app['request']->get('echostr')); + } +} diff --git a/vendor/overtrue/wechat/src/Work/Server/Handlers/EchoStrHandler.php b/vendor/overtrue/wechat/src/Work/Server/Handlers/EchoStrHandler.php new file mode 100644 index 0000000..64555ef --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Server/Handlers/EchoStrHandler.php @@ -0,0 +1,58 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Server\Handlers; + +use EasyWeChat\Kernel\Contracts\EventHandlerInterface; +use EasyWeChat\Kernel\Decorators\FinallyResult; +use EasyWeChat\Kernel\ServiceContainer; + +/** + * Class EchoStrHandler. + * + * @author overtrue + */ +class EchoStrHandler implements EventHandlerInterface +{ + /** + * @var ServiceContainer + */ + protected $app; + + /** + * EchoStrHandler constructor. + * + * @param ServiceContainer $app + */ + public function __construct(ServiceContainer $app) + { + $this->app = $app; + } + + /** + * @param mixed $payload + * + * @return FinallyResult|null + */ + public function handle($payload = null) + { + if ($decrypted = $this->app['request']->get('echostr')) { + $str = $this->app['encryptor']->decrypt( + $decrypted, + $this->app['request']->get('msg_signature'), + $this->app['request']->get('nonce'), + $this->app['request']->get('timestamp') + ); + + return new FinallyResult($str); + } + } +} diff --git a/vendor/overtrue/wechat/src/Work/Server/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/Server/ServiceProvider.php new file mode 100644 index 0000000..8a2fc3e --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Server/ServiceProvider.php @@ -0,0 +1,46 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Server; + +use EasyWeChat\Kernel\Encryptor; +use EasyWeChat\Work\Server\Handlers\EchoStrHandler; +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + !isset($app['encryptor']) && $app['encryptor'] = function ($app) { + return new Encryptor( + $app['config']['corp_id'], + $app['config']['token'], + $app['config']['aes_key'] + ); + }; + + !isset($app['server']) && $app['server'] = function ($app) { + $guard = new Guard($app); + $guard->push(new EchoStrHandler($app)); + + return $guard; + }; + } +} diff --git a/vendor/overtrue/wechat/src/Work/User/Client.php b/vendor/overtrue/wechat/src/Work/User/Client.php new file mode 100644 index 0000000..8c07ec6 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/User/Client.php @@ -0,0 +1,251 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\User; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; + +/** + * Class Client. + * + * @author mingyoung + */ +class Client extends BaseClient +{ + /** + * Create a user. + * + * @param array $data + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function create(array $data) + { + return $this->httpPostJson('cgi-bin/user/create', $data); + } + + /** + * Update an exist user. + * + * @param string $id + * @param array $data + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function update(string $id, array $data) + { + return $this->httpPostJson('cgi-bin/user/update', array_merge(['userid' => $id], $data)); + } + + /** + * Delete a user. + * + * @param string|array $userId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function delete($userId) + { + if (is_array($userId)) { + return $this->batchDelete($userId); + } + + return $this->httpGet('cgi-bin/user/delete', ['userid' => $userId]); + } + + /** + * Batch delete users. + * + * @param array $userIds + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function batchDelete(array $userIds) + { + return $this->httpPostJson('cgi-bin/user/batchdelete', ['useridlist' => $userIds]); + } + + /** + * Get user. + * + * @param string $userId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function get(string $userId) + { + return $this->httpGet('cgi-bin/user/get', ['userid' => $userId]); + } + + /** + * Get simple user list. + * + * @param int $departmentId + * @param bool $fetchChild + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function getDepartmentUsers(int $departmentId, bool $fetchChild = false) + { + $params = [ + 'department_id' => $departmentId, + 'fetch_child' => (int) $fetchChild, + ]; + + return $this->httpGet('cgi-bin/user/simplelist', $params); + } + + /** + * Get user list. + * + * @param int $departmentId + * @param bool $fetchChild + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function getDetailedDepartmentUsers(int $departmentId, bool $fetchChild = false) + { + $params = [ + 'department_id' => $departmentId, + 'fetch_child' => (int) $fetchChild, + ]; + + return $this->httpGet('cgi-bin/user/list', $params); + } + + /** + * Convert userId to openid. + * + * @param string $userId + * @param int|null $agentId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function userIdToOpenid(string $userId, int $agentId = null) + { + $params = [ + 'userid' => $userId, + 'agentid' => $agentId, + ]; + + return $this->httpPostJson('cgi-bin/user/convert_to_openid', $params); + } + + /** + * Convert openid to userId. + * + * @param string $openid + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function openidToUserId(string $openid) + { + $params = [ + 'openid' => $openid, + ]; + + return $this->httpPostJson('cgi-bin/user/convert_to_userid', $params); + } + + /** + * Convert mobile to userId. + * + * @param string $mobile + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function mobileToUserId(string $mobile) + { + $params = [ + 'mobile' => $mobile, + ]; + + return $this->httpPostJson('cgi-bin/user/getuserid', $params); + } + + /** + * @param string $userId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function accept(string $userId) + { + $params = [ + 'userid' => $userId, + ]; + + return $this->httpGet('cgi-bin/user/authsucc', $params); + } + + /** + * Batch invite users. + * + * @param array $params + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function invite(array $params) + { + return $this->httpPostJson('cgi-bin/batch/invite', $params); + } + + /** + * Get invitation QR code. + * + * @param int $sizeType + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function getInvitationQrCode(int $sizeType = 1) + { + if (!\in_array($sizeType, [1, 2, 3, 4], true)) { + throw new InvalidArgumentException('The sizeType must be 1, 2, 3, 4.'); + } + + return $this->httpGet('cgi-bin/corp/get_join_qrcode', ['size_type' => $sizeType]); + } +} diff --git a/vendor/overtrue/wechat/src/Work/User/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/User/ServiceProvider.php new file mode 100644 index 0000000..fcb5f10 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/User/ServiceProvider.php @@ -0,0 +1,37 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\User; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author mingyoung + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['user'] = function ($app) { + return new Client($app); + }; + + $app['tag'] = function ($app) { + return new TagClient($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Work/User/TagClient.php b/vendor/overtrue/wechat/src/Work/User/TagClient.php new file mode 100644 index 0000000..aae7cf1 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/User/TagClient.php @@ -0,0 +1,178 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\User; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class TagClient. + * + * @author mingyoung + */ +class TagClient extends BaseClient +{ + /** + * Create tag. + * + * @param string $tagName + * @param int|null $tagId + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function create(string $tagName, int $tagId = null) + { + $params = [ + 'tagname' => $tagName, + 'tagid' => $tagId, + ]; + + return $this->httpPostJson('cgi-bin/tag/create', $params); + } + + /** + * Update tag. + * + * @param int $tagId + * @param string $tagName + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function update(int $tagId, string $tagName) + { + $params = [ + 'tagid' => $tagId, + 'tagname' => $tagName, + ]; + + return $this->httpPostJson('cgi-bin/tag/update', $params); + } + + /** + * Delete tag. + * + * @param int $tagId + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function delete(int $tagId) + { + return $this->httpGet('cgi-bin/tag/delete', ['tagid' => $tagId]); + } + + /** + * @param int $tagId + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function get(int $tagId) + { + return $this->httpGet('cgi-bin/tag/get', ['tagid' => $tagId]); + } + + /** + * @param int $tagId + * @param array $userList + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function tagUsers(int $tagId, array $userList = []) + { + return $this->tagOrUntagUsers('cgi-bin/tag/addtagusers', $tagId, $userList); + } + + /** + * @param int $tagId + * @param array $partyList + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function tagDepartments(int $tagId, array $partyList = []) + { + return $this->tagOrUntagUsers('cgi-bin/tag/addtagusers', $tagId, [], $partyList); + } + + /** + * @param int $tagId + * @param array $userList + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function untagUsers(int $tagId, array $userList = []) + { + return $this->tagOrUntagUsers('cgi-bin/tag/deltagusers', $tagId, $userList); + } + + /** + * @param int $tagId + * @param array $partyList + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function untagDepartments(int $tagId, array $partyList = []) + { + return $this->tagOrUntagUsers('cgi-bin/tag/deltagusers', $tagId, [], $partyList); + } + + /** + * @param string $endpoint + * @param int $tagId + * @param array $userList + * @param array $partyList + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function tagOrUntagUsers(string $endpoint, int $tagId, array $userList = [], array $partyList = []) + { + $data = [ + 'tagid' => $tagId, + 'userlist' => $userList, + 'partylist' => $partyList, + ]; + + return $this->httpPostJson($endpoint, $data); + } + + /** + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function list() + { + return $this->httpGet('cgi-bin/tag/list'); + } +} diff --git a/vendor/paragonie/random_compat/LICENSE b/vendor/paragonie/random_compat/LICENSE new file mode 100644 index 0000000..45c7017 --- /dev/null +++ b/vendor/paragonie/random_compat/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Paragon Initiative Enterprises + +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/paragonie/random_compat/build-phar.sh b/vendor/paragonie/random_compat/build-phar.sh new file mode 100644 index 0000000..b4a5ba3 --- /dev/null +++ b/vendor/paragonie/random_compat/build-phar.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +basedir=$( dirname $( readlink -f ${BASH_SOURCE[0]} ) ) + +php -dphar.readonly=0 "$basedir/other/build_phar.php" $* \ No newline at end of file diff --git a/vendor/paragonie/random_compat/composer.json b/vendor/paragonie/random_compat/composer.json new file mode 100644 index 0000000..f2b9c4e --- /dev/null +++ b/vendor/paragonie/random_compat/composer.json @@ -0,0 +1,34 @@ +{ + "name": "paragonie/random_compat", + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "random", + "polyfill", + "pseudorandom" + ], + "license": "MIT", + "type": "library", + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "support": { + "issues": "https://github.com/paragonie/random_compat/issues", + "email": "info@paragonie.com", + "source": "https://github.com/paragonie/random_compat" + }, + "require": { + "php": ">= 7" + }, + "require-dev": { + "vimeo/psalm": "^1", + "phpunit/phpunit": "4.*|5.*" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + } +} diff --git a/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey b/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey new file mode 100644 index 0000000..eb50ebf --- /dev/null +++ b/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey @@ -0,0 +1,5 @@ +-----BEGIN PUBLIC KEY----- +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEEd+wCqJDrx5B4OldM0dQE0ZMX+lx1ZWm +pui0SUqD4G29L3NGsz9UhJ/0HjBdbnkhIK5xviT0X5vtjacF6ajgcCArbTB+ds+p ++h7Q084NuSuIpNb6YPfoUFgC/CL9kAoc +-----END PUBLIC KEY----- diff --git a/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc b/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc new file mode 100644 index 0000000..6a1d7f3 --- /dev/null +++ b/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc @@ -0,0 +1,11 @@ +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v2.0.22 (MingW32) + +iQEcBAABAgAGBQJWtW1hAAoJEGuXocKCZATaJf0H+wbZGgskK1dcRTsuVJl9IWip +QwGw/qIKI280SD6/ckoUMxKDCJiFuPR14zmqnS36k7N5UNPnpdTJTS8T11jttSpg +1LCmgpbEIpgaTah+cELDqFCav99fS+bEiAL5lWDAHBTE/XPjGVCqeehyPYref4IW +NDBIEsvnHPHPLsn6X5jq4+Yj5oUixgxaMPiR+bcO4Sh+RzOVB6i2D0upWfRXBFXA +NNnsg9/zjvoC7ZW73y9uSH+dPJTt/Vgfeiv52/v41XliyzbUyLalf02GNPY+9goV +JHG1ulEEBJOCiUD9cE1PUIJwHA/HqyhHIvV350YoEFiHl8iSwm7SiZu5kPjaq74= +=B6+8 +-----END PGP SIGNATURE----- diff --git a/vendor/paragonie/random_compat/lib/random.php b/vendor/paragonie/random_compat/lib/random.php new file mode 100644 index 0000000..c7731a5 --- /dev/null +++ b/vendor/paragonie/random_compat/lib/random.php @@ -0,0 +1,32 @@ +buildFromDirectory(dirname(__DIR__).'/lib'); +rename( + dirname(__DIR__).'/lib/index.php', + dirname(__DIR__).'/lib/random.php' +); + +/** + * If we pass an (optional) path to a private key as a second argument, we will + * sign the Phar with OpenSSL. + * + * If you leave this out, it will produce an unsigned .phar! + */ +if ($argc > 1) { + if (!@is_readable($argv[1])) { + echo 'Could not read the private key file:', $argv[1], "\n"; + exit(255); + } + $pkeyFile = file_get_contents($argv[1]); + + $private = openssl_get_privatekey($pkeyFile); + if ($private !== false) { + $pkey = ''; + openssl_pkey_export($private, $pkey); + $phar->setSignatureAlgorithm(Phar::OPENSSL, $pkey); + + /** + * Save the corresponding public key to the file + */ + if (!@is_readable($dist.'/random_compat.phar.pubkey')) { + $details = openssl_pkey_get_details($private); + file_put_contents( + $dist.'/random_compat.phar.pubkey', + $details['key'] + ); + } + } else { + echo 'An error occurred reading the private key from OpenSSL.', "\n"; + exit(255); + } +} diff --git a/vendor/paragonie/random_compat/psalm-autoload.php b/vendor/paragonie/random_compat/psalm-autoload.php new file mode 100644 index 0000000..d71d1b8 --- /dev/null +++ b/vendor/paragonie/random_compat/psalm-autoload.php @@ -0,0 +1,9 @@ + + + + + + + + + + + + + + + diff --git a/vendor/phpoffice/phpspreadsheet/.gitattributes b/vendor/phpoffice/phpspreadsheet/.gitattributes new file mode 100644 index 0000000..0375f55 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/.gitattributes @@ -0,0 +1,4 @@ +/tests export-ignore +README.md export-ignore +*.min.js binary +/.github export-ignore diff --git a/vendor/phpoffice/phpspreadsheet/.gitignore b/vendor/phpoffice/phpspreadsheet/.gitignore new file mode 100644 index 0000000..0723541 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/.gitignore @@ -0,0 +1,10 @@ +/tests/codeCoverage +/analysis +/vendor/ +/phpunit.xml + +## IDE support +*.buildpath +*.project +/.settings +/.idea diff --git a/vendor/phpoffice/phpspreadsheet/.php_cs.dist b/vendor/phpoffice/phpspreadsheet/.php_cs.dist new file mode 100644 index 0000000..2321692 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/.php_cs.dist @@ -0,0 +1,183 @@ +exclude(['vendor', 'tests/data/Calculation']) + ->in('samples') + ->in('src') + ->in('tests/PhpSpreadsheetTests') + ; + +return PhpCsFixer\Config::create() + ->setRiskyAllowed(true) + ->setFinder($finder) + ->setCacheFile(sys_get_temp_dir() . '/php-cs-fixer' . preg_replace('~\W~', '-', __DIR__)) + ->setRules([ + 'align_multiline_comment' => true, + 'array_syntax' => ['syntax' => 'short'], + 'backtick_to_shell_exec' => true, + 'binary_operator_spaces' => true, + 'blank_line_after_namespace' => true, + 'blank_line_after_opening_tag' => true, + 'blank_line_before_statement' => true, + 'braces' => true, + 'cast_spaces' => true, + 'class_attributes_separation' => ['elements' => ['method', 'property']], // const are often grouped with other related const + 'class_definition' => true, + 'class_keyword_remove' => false, // ::class keyword gives us beter support in IDE + 'combine_consecutive_issets' => true, + 'combine_consecutive_unsets' => true, + 'compact_nullable_typehint' => true, + 'concat_space' => ['spacing' => 'one'], + 'declare_equal_normalize' => true, + 'declare_strict_types' => false, // Too early to adopt strict types + 'dir_constant' => true, + 'doctrine_annotation_array_assignment' => true, + 'doctrine_annotation_braces' => true, + 'doctrine_annotation_indentation' => true, + 'doctrine_annotation_spaces' => true, + 'elseif' => true, + 'encoding' => true, + 'ereg_to_preg' => true, + 'escape_implicit_backslashes' => true, + 'explicit_indirect_variable' => false, // I feel it makes the code actually harder to read + 'explicit_string_variable' => false, // I feel it makes the code actually harder to read + 'final_internal_class' => true, + 'full_opening_tag' => true, + 'function_declaration' => true, + 'function_to_constant' => true, + 'function_typehint_space' => true, + 'general_phpdoc_annotation_remove' => false, // No use for that + 'hash_to_slash_comment' => true, + 'header_comment' => false, // We don't use common header in all our files + 'heredoc_to_nowdoc' => false, // Not sure about this one + 'include' => true, + 'increment_style' => true, + 'indentation_type' => true, + 'is_null' => ['use_yoda_style' => false], + 'linebreak_after_opening_tag' => true, + 'line_ending' => true, + 'list_syntax' => ['syntax' => 'short'], + 'lowercase_cast' => true, + 'lowercase_constants' => true, + 'lowercase_keywords' => true, + 'magic_constant_casing' => true, + 'mb_str_functions' => false, // No, too dangerous to change that + 'method_argument_space' => true, + 'method_chaining_indentation' => true, + 'method_separation' => true, + 'modernize_types_casting' => true, + 'multiline_comment_opening_closing' => true, + 'native_function_casing' => true, + 'native_function_invocation' => false, // This is risky and seems to be micro-optimization that make code uglier so not worth it, at least for now + 'new_with_braces' => true, + 'no_alias_functions' => true, + 'no_blank_lines_after_class_opening' => true, + 'no_blank_lines_after_phpdoc' => true, + 'no_blank_lines_before_namespace' => false, // we want 1 blank line before namespace + 'no_break_comment' => true, + 'no_closing_tag' => true, + 'no_empty_comment' => true, + 'no_empty_phpdoc' => true, + 'no_empty_statement' => true, + 'no_extra_blank_lines' => true, + 'no_homoglyph_names' => true, + 'no_leading_import_slash' => true, + 'no_leading_namespace_whitespace' => true, + 'no_mixed_echo_print' => true, + 'no_multiline_whitespace_around_double_arrow' => true, + 'no_multiline_whitespace_before_semicolons' => true, + 'non_printable_character' => true, + 'no_null_property_initialization' => true, + 'no_php4_constructor' => true, + 'normalize_index_brace' => true, + 'no_short_bool_cast' => true, + 'no_short_echo_tag' => true, + 'no_singleline_whitespace_before_semicolons' => true, + 'no_spaces_after_function_name' => true, + 'no_spaces_around_offset' => true, + 'no_spaces_inside_parenthesis' => true, + 'no_superfluous_elseif' => false, // Might be risky on a huge code base + 'not_operator_with_space' => false, // No we prefer to keep '!' without spaces + 'not_operator_with_successor_space' => false, // idem + 'no_trailing_comma_in_list_call' => true, + 'no_trailing_comma_in_singleline_array' => true, + 'no_trailing_whitespace_in_comment' => true, + 'no_trailing_whitespace' => true, + 'no_unneeded_control_parentheses' => true, + 'no_unneeded_curly_braces' => true, + 'no_unneeded_final_method' => true, + 'no_unreachable_default_argument_value' => true, + 'no_unused_imports' => true, + 'no_useless_else' => true, + 'no_useless_return' => true, + 'no_whitespace_before_comma_in_array' => true, + 'no_whitespace_in_blank_line' => true, + 'object_operator_without_whitespace' => true, + 'ordered_class_elements' => false, // We prefer to keep some freedom + 'ordered_imports' => true, + 'phpdoc_add_missing_param_annotation' => true, + 'phpdoc_align' => false, // Waste of time + 'phpdoc_annotation_without_dot' => true, + 'phpdoc_indent' => true, + 'phpdoc_inline_tag' => true, + 'phpdoc_no_access' => true, + 'phpdoc_no_alias_tag' => true, + 'phpdoc_no_empty_return' => true, + 'phpdoc_no_package' => true, + 'phpdoc_no_useless_inheritdoc' => true, + 'phpdoc_order' => true, + 'phpdoc_return_self_reference' => true, + 'phpdoc_scalar' => true, + 'phpdoc_separation' => true, + 'phpdoc_single_line_var_spacing' => true, + 'phpdoc_summary' => true, + 'phpdoc_to_comment' => true, + 'phpdoc_trim' => true, + 'phpdoc_types_order' => true, + 'phpdoc_types' => true, + 'phpdoc_var_without_name' => true, + 'php_unit_construct' => true, + 'php_unit_dedicate_assert' => true, + 'php_unit_expectation' => true, + 'php_unit_fqcn_annotation' => true, + 'php_unit_mock' => true, + 'php_unit_namespaced' => true, + 'php_unit_no_expectation_annotation' => true, + 'php_unit_strict' => false, // We sometime actually need assertEquals + 'php_unit_test_annotation' => true, + 'php_unit_test_class_requires_covers' => false, // We don't care as much as we should about coverage + 'pow_to_exponentiation' => false, + 'protected_to_private' => true, + 'psr0' => true, + 'psr4' => true, + 'random_api_migration' => false, // This breaks our unit tests + 'return_type_declaration' => true, + 'self_accessor' => true, + 'semicolon_after_instruction' => false, // Buggy in `samples/index.php` + 'short_scalar_cast' => true, + 'silenced_deprecation_error' => true, + 'simplified_null_return' => false, // While technically correct we prefer to be explicit when returning null + 'single_blank_line_at_eof' => true, + 'single_blank_line_before_namespace' => true, + 'single_class_element_per_statement' => true, + 'single_import_per_statement' => true, + 'single_line_after_imports' => true, + 'single_line_comment_style' => true, + 'single_quote' => true, + 'space_after_semicolon' => true, + 'standardize_not_equals' => true, + 'static_lambda' => false, // Risky if we can't guarantee nobody use `bindTo()` + 'strict_comparison' => false, // No, too dangerous to change that + 'strict_param' => false, // No, too dangerous to change that + 'switch_case_semicolon_to_colon' => true, + 'switch_case_space' => true, + 'ternary_operator_spaces' => true, + 'ternary_to_null_coalescing' => true, + 'trailing_comma_in_multiline_array' => true, + 'trim_array_spaces' => true, + 'unary_operator_spaces' => true, + 'visibility_required' => true, + 'void_return' => false, // Cannot use that with PHP 5.6 + 'whitespace_after_comma_in_array' => true, + 'yoda_style' => false, + ]); diff --git a/vendor/phpoffice/phpspreadsheet/.scrutinizer.yml b/vendor/phpoffice/phpspreadsheet/.scrutinizer.yml new file mode 100644 index 0000000..748f3ac --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/.scrutinizer.yml @@ -0,0 +1,27 @@ +checks: + php: true + +coding_style: + php: + spaces: + before_parentheses: + closure_definition: true + around_operators: + concatenation: true + +build: + nodes: + analysis: + tests: + override: + - php-scrutinizer-run + +tools: + external_code_coverage: + timeout: 3600 + +build_failure_conditions: + - 'elements.rating(<= C).new.exists' # No new classes/methods with a rating of C or worse allowed + - 'issues.severity(>= MAJOR).new.exists' # New issues of major or higher severity + - 'project.metric_change("scrutinizer.test_coverage", < 0)' # Code Coverage decreased from previous inspection + - 'patches.label("Unused Use Statements").new.exists' # No new unused imports patches allowed diff --git a/vendor/phpoffice/phpspreadsheet/.travis.yml b/vendor/phpoffice/phpspreadsheet/.travis.yml new file mode 100644 index 0000000..82e25ce --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/.travis.yml @@ -0,0 +1,57 @@ +language: php +dist: bionic + +php: + - 7.1 + - 7.2 + - 7.3 + - 7.4 + +cache: + directories: + - vendor + - $HOME/.composer/cache + +before_script: + # Deactivate xdebug + - phpenv config-rm xdebug.ini + - composer install --ignore-platform-reqs + +script: + - ./vendor/bin/phpunit + +jobs: + include: + + - stage: Code style + php: 7.2 + script: + - ./vendor/bin/php-cs-fixer fix --diff --verbose --dry-run + - ./vendor/bin/phpcs --report-width=200 samples/ src/ tests/ --ignore=samples/Header.php --standard=PSR2 -n + + - stage: Coverage + php: 7.2 + script: + - pecl install pcov + - composer require pcov/clobber --dev + - ./vendor/bin/pcov clobber + - ./vendor/bin/phpunit --coverage-clover coverage-clover.xml + after_script: + - wget https://scrutinizer-ci.com/ocular.phar + - php ocular.phar code-coverage:upload --format=php-clover tests/coverage-clover.xml + + - stage: API documentations + if: tag is present + php: 7.4 + before_script: + - curl -O https://github.com/phpDocumentor/phpDocumentor/releases/download/v3.0.0-rc/phpDocumentor.phar + script: + - php phpDocumentor.phar --directory src/ --target docs/api + deploy: + provider: pages + skip-cleanup: true + local-dir: docs/api + github-token: $GITHUB_TOKEN + on: + all_branches: true + condition: $TRAVIS_BRANCH =~ ^master$ diff --git a/vendor/phpoffice/phpspreadsheet/CHANGELOG.PHPExcel.md b/vendor/phpoffice/phpspreadsheet/CHANGELOG.PHPExcel.md new file mode 100644 index 0000000..3c29902 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/CHANGELOG.PHPExcel.md @@ -0,0 +1,1593 @@ +# Changelog for PHPExcel + +This is the historic changelog of the project when it was still called PHPExcel. +It exists only for historical purposes and versions mentioned here should not be +confused with PhpSpreadsheet versions. + +## [1.8.1] - 2015-04-30 + +### Bugfixes + +- Fix for Writing an Open Document cell with non-numeric formula - @goncons [#397](https://github.com/PHPOffice/PHPExcel/issues/397) +- Avoid potential divide by zero in basedrawing - @sarciszewski [#329](https://github.com/PHPOffice/PHPExcel/issues/329) +- XML External Entity (XXE) Processing, different behaviour between simplexml_load_string() and simplexml_load_file(). - @ymaerschalck [#405](https://github.com/PHPOffice/PHPExcel/issues/405) +- Fix to ensure that current cell is maintained when executing formula calculations - @MarkBaker +- Keep/set the value on Reader _loadSheetsOnly as NULL, courtesy of Restless-ET - @MarkBaker [#350](https://github.com/PHPOffice/PHPExcel/issues/350) +- Loading an Excel 2007 spreadsheet throws an "Autofilter must be set on a range of cells" exception - @MarkBaker [CodePlex #18105](https://phpexcel.codeplex.com/workitem/18105) +- Fix to autoloader registration for backward compatibility with PHP 5.2.0 not accepting the prepend flag - @MarkBaker [#388](https://github.com/PHPOffice/PHPExcel/issues/388) +- DOM loadHTMLFile() failing with options flags when using PHP < 5.4.0 - @MarkBaker [#384](https://github.com/PHPOffice/PHPExcel/issues/384) +- Fix for percentage operator in formulae for BIFF Writer - @MarkBaker +- Fix to getStyle() call for cell object - @MarkBaker +- Discard Autofilters in Excel2007 Reader when filter range isn't a valid range - @MarkBaker +- Fix invalid NA return in VLOOKUP - @frozenstupidity [#423](https://github.com/PHPOffice/PHPExcel/issues/423) +- "No Impact" conditional formatting fix for NumberFormat - @wiseloren [CodePlex #21454](https://phpexcel.codeplex.com/workitem/21454) +- Bug in Excel2003XML reader, parsing merged cells - @bobwitlox [#467](https://github.com/PHPOffice/PHPExcel/issues/467) +- Fix for CEIL() and FLOOR() when number argument is zero - @MarkBaker [#302](https://github.com/PHPOffice/PHPExcel/issues/302) + +### General + +- Remove cells cleanly when calling RemoveRow() or RemoveColumn() - @MarkBaker +- Small performance improvement for autosize columns - @MarkBaker +- Change the getter/setter for zeroHeight to camel case - @frost-nzcr4 [#379](https://github.com/PHPOffice/PHPExcel/issues/379) +- DefaultValueBinder is too much aggressive when converting string to numeric - @MarkBaker [#394](https://github.com/PHPOffice/PHPExcel/issues/394) +- Default precalculate formulas to false for writers - @MarkBaker +- Set default Cyclic Reference behaviour to 1 to eliminate exception when using a single cyclic iteration in formulae - @MarkBaker + +### Features + +- Some Excel writer libraries erroneously use Codepage 21010 for UTF-16LE - @MarkBaker [#396](https://github.com/PHPOffice/PHPExcel/issues/396) +- Methods to manage most of the existing options for Chart Axis, Major Grid-lines and Minor Grid-lines - @WiktrzGE [#404](https://github.com/PHPOffice/PHPExcel/issues/404) +- ODS read/write comments in the cell - @frost-nzcr4 [#403](https://github.com/PHPOffice/PHPExcel/issues/403) +- Additional Mac CJK codepage definitions - @CQD [#389](https://github.com/PHPOffice/PHPExcel/issues/389) +- Update Worksheet.php getStyleByColumnAndRow() to allow a range of cells rather than just a single cell - @bolovincev [#269](https://github.com/PHPOffice/PHPExcel/issues/269) +- New methods added for testing cell status within merge groups - @MarkBaker +- Handling merge cells in HTML Reader - @cifren/MBaker [#205](https://github.com/PHPOffice/PHPExcel/issues/205) +- Helper to convert basic HTML markup to a Rich Text object - @MarkBaker +- Improved Iterators - @MarkBaker + - New Column Iterator + - Support for row and column ranges + - Improved handling for next/prev + +### Security + +- XML filescan in XML-based Readers to prevent XML Entity Expansion (XEE) - @MarkBaker + - (see http://projects.webappsec.org/w/page/13247002/XML%20Entity%20Expansion for an explanation of XEE injection) attacks + - Reference CVE-2015-3542 - Identification of problem courtesy of Dawid Golunski (Pentest Ltd.) + +## [1.8.0] - 2014-03-02 + +### Bugfixes + +- Undefined variable: fileHandle in CSV Reader - @MarkBaker [CodePlex #19830](https://phpexcel.codeplex.com/workitem/19830) +- Out of memory in style/supervisor.php - @MarkBaker [CodePlex #19968](https://phpexcel.codeplex.com/workitem/19968) +- Style error with merged cells in PDF Writer - @MarkBaker +- Problem with cloning worksheets - @MarkBaker +- Bug fix reading Open Office files - @tavoarcila [#259](https://github.com/PHPOffice/PHPExcel/issues/259) +- Serious bug in absolute cell reference used in shared formula - @MarkBaker [CodePlex #20397](https://phpexcel.codeplex.com/workitem/20397) + - Would also have affected insert/delete column/row- CHOOSE() returns "#VALUE!" if the 1st entry is chosen - @RomanSyroeshko [#267](https://github.com/PHPOffice/PHPExcel/issues/267) +- When duplicating styles, styles shifted by one column to the right - @Gemorroj [#268](https://github.com/PHPOffice/PHPExcel/issues/268) + - Fix also applied to duplicating conditional styles- Fix for formulae that reference a sheet whose name begins with a digit: - @IndrekHaav [#212](https://github.com/PHPOffice/PHPExcel/issues/212) + - these were erroneously identified as numeric values, causing the parser to throw an undefined variable error.- Fixed undefined variable error due to $styleArray being used before it's initialised - @IndrekHaav [CodePlex #16208](https://phpexcel.codeplex.com/workitem/16208) +- ISTEXT() return wrong result if referencing an empty but formatted cell - @PowerKiKi [#273](https://github.com/PHPOffice/PHPExcel/issues/273) +- Binary comparison of strings are case insensitive - @PowerKiKi [#270](https://github.com/PHPOffice/PHPExcel/issues/270), [#31](https://github.com/PHPOffice/PHPExcel/issues/31) +- Insert New Row/Column Before is not correctly updating formula references - @MarkBaker [#275](https://github.com/PHPOffice/PHPExcel/issues/275) +- Passing an array of cells to _generateRow() in the HTML/PDF Writer causes caching problems with last cell in the range - @MarkBaker [#257](https://github.com/PHPOffice/PHPExcel/issues/257) +- Fix to empty worksheet garbage collection when using cell caching - @MarkBaker [#193](https://github.com/PHPOffice/PHPExcel/issues/193) +- Excel2007 does not correctly mark rows as hidden - @Jazzo [#248](https://github.com/PHPOffice/PHPExcel/issues/248) +- Fixed typo in Chart/Layout set/getYMode() - @Roy Shahbazian [#299](https://github.com/PHPOffice/PHPExcel/issues/299) +- Fatal error: Call to a member function cellExists() line: 3327 in calculation.php if referenced worksheet doesn't exist - @EliuFlorez [#279](https://github.com/PHPOffice/PHPExcel/issues/279) +- AdvancedValueBinder "Division by zero"-error - @MarkBaker [#290](https://github.com/PHPOffice/PHPExcel/issues/290) +- Adding Sheet to Workbook Bug - @MarkBaker [CodePlex #20604](https://phpexcel.codeplex.com/workitem/20604) +- Calculation engine incorrectly evaluates empty cells as #VALUE - @MarkBaker [CodePlex #20703](https://phpexcel.codeplex.com/workitem/20703) +- Formula references to cell on another sheet in ODS files - @MarkBaker [CodePlex #20760](https://phpexcel.codeplex.com/workitem/20760) + +### Features + +- LibreOffice created XLSX files results in an empty file. - @MarkBaker [#321](https://github.com/PHPOffice/PHPExcel/issues/321), [#158](https://github.com/PHPOffice/PHPExcel/issues/158), [CodePlex #17824](https://phpexcel.codeplex.com/workitem/17824) +- Implementation of the Excel HLOOKUP() function - @amerov +- Added "Quote Prefix" to style settings (Excel2007 Reader and Writer only) - @MarkBaker +- Added Horizontal FILL alignment for Excel5 and Excel2007 Readers/Writers, and Horizontal DISTRIBUTED alignment for Excel2007 Reader/Writer - @MarkBaker +- Add support for reading protected (RC4 encrypted) .xls files - @trvrnrth [#261](https://github.com/PHPOffice/PHPExcel/issues/261) + +### General + +- Adding support for macros, Ribbon in Excel 2007 - @LWol [#252](https://github.com/PHPOffice/PHPExcel/issues/252) +- Remove array_shift in ReferenceHelper::insertNewBefore improves column or row delete speed - @cdhutch [CodePlex #20055](https://phpexcel.codeplex.com/workitem/20055) +- Improve stock chart handling and rendering, with help from Swashata Ghosh - @MarkBaker +- Fix to calculation properties for Excel2007 so that the opening application will only recalculate on load if it's actually required - @MarkBaker +- Modified Excel2007 Writer to default preCalculateFormulas to false - @MarkBaker + - Note that autosize columns will still recalculate affected formulae internally- Functionality to getHighestRow() for a specified column, and getHighestColumn() for a specified row - @dresenhista [#242](https://github.com/PHPOffice/PHPExcel/issues/242) +- Modify PHPExcel_Reader_Excel2007 to use zipClass from PHPExcel_Settings::getZipClass() - @adamriyadi [#247](https://github.com/PHPOffice/PHPExcel/issues/247) + - This allows the use of PCLZip when reading for people that don't have access to ZipArchive +### Security + +- Convert properties to string in OOCalc reader - @infojunkie [#276](https://github.com/PHPOffice/PHPExcel/issues/276) +- Disable libxml external entity loading by default. - @maartenba [#322](https://github.com/PHPOffice/PHPExcel/issues/322) + - This is to prevent XML External Entity Processing (XXE) injection attacks (see https://websec.io/2012/08/27/Preventing-XEE-in-PHP.html for an explanation of XXE injection). + - Reference CVE-2014-2054 + +## [1.7.9] - 2013-06-02 + +### Features + +- Include charts option for HTML Writer - @MarkBaker +- Added composer file - @MarkBaker +- cache_in_memory_gzip "eats" last worksheet line, cache_in_memory doesn't - @MarkBaker [CodePlex #18844](https://phpexcel.codeplex.com/workitem/18844) +- echo statements in HTML.php - @MarkBaker [#104](https://github.com/PHPOffice/PHPExcel/issues/104) + +### Bugfixes + +- Added getStyle() method to Cell object - @MarkBaker +- Error in PHPEXCEL/Calculation.php script on line 2976 (stack pop check) - @Asker [CodePlex #18777](https://phpexcel.codeplex.com/workitem/18777) +- CSV files without a file extension being identified as HTML - @MarkBaker [CodePlex #18794](https://phpexcel.codeplex.com/workitem/18794) +- Wrong check for maximum number of rows in Excel5 Writer - @AndreKR [#66](https://github.com/PHPOffice/PHPExcel/issues/66) +- Cache directory for DiscISAM cache storage cannot be set - @MarkBaker [#67](https://github.com/PHPOffice/PHPExcel/issues/67) +- Fix to Excel2007 Reader for hyperlinks with an anchor fragment (following a #), otherwise they were treated as sheet references - @MarkBaker [CodePlex #17976](https://phpexcel.codeplex.com/workitem/17976) +- getSheetNames() fails on numeric (floating point style) names with trailing zeroes - @MarkBaker [CodePlex #18963](https://phpexcel.codeplex.com/workitem/18963) +- Modify cell's getCalculatedValue() method to return the content of RichText objects rather than the RichText object itself - @MarkBaker +- Fixed formula/formatting bug when removing rows - @techhead [#70](https://github.com/PHPOffice/PHPExcel/issues/70) +- Fix to cellExists for non-existent namedRanges - @alexgann [#63](https://github.com/PHPOffice/PHPExcel/issues/63) +- Sheet View in Excel5 Writer - @Progi1984 [#22](https://github.com/PHPOffice/PHPExcel/issues/22) +- PHPExcel_Worksheet::getCellCollection() may not return last cached cell - @amironov [#82](https://github.com/PHPOffice/PHPExcel/issues/82) +- Rich Text containing UTF-8 characters creating unreadable content with Excel5 Writer - @teso [CodePlex #18551](https://phpexcel.codeplex.com/workitem/18551) +- Work item GH-8/CP11704 : Conditional formatting in Excel 5 Writer - @Progi1984 +- canRead() Error for GoogleDocs ODS files: in ODS files from Google Docs there is no mimetype file - @MarkBaker [#113](https://github.com/PHPOffice/PHPExcel/issues/113) +- "Sheet index is out of bounds." Exception - @MarkBaker [#80](https://github.com/PHPOffice/PHPExcel/issues/80) +- Fixed number format fatal error - @ccorliss [#105](https://github.com/PHPOffice/PHPExcel/issues/105) +- Add DROP TABLE in destructor for SQLite and SQLite3 cache controllers - @MarkBaker +- Fix merged-cell borders on HTML/PDF output - @alexgann [#154](https://github.com/PHPOffice/PHPExcel/issues/154) +- Fix: Hyperlinks break when removing rows - @Shanto [#161](https://github.com/PHPOffice/PHPExcel/issues/161) +- Fix Extra Table Row From Images and Charts - @neclimdul [#166](https://github.com/PHPOffice/PHPExcel/issues/166) + +### General + +- Single cell print area - @MarkBaker [#130](https://github.com/PHPOffice/PHPExcel/issues/130) +- Improved AdvancedValueBinder for currency - @kea [#69](https://github.com/PHPOffice/PHPExcel/issues/69) +- Fix for environments where there is no access to /tmp but to upload_tmp_dir - @MarkBaker + - Provided an option to set the sys_get_temp_dir() call to use the upload_tmp_dir; though by default the standard temp directory will still be used- Search style by identity in PHPExcel_Worksheet::duplicateStyle() - @amironov [#84](https://github.com/PHPOffice/PHPExcel/issues/84) +- Fill SheetView IO in Excel5 - @karak [#85](https://github.com/PHPOffice/PHPExcel/issues/85) +- Memory and Speed improvements in PHPExcel_Reader_Excel5 - @cfhay [CodePlex #18958](https://phpexcel.codeplex.com/workitem/18958) +- Modify listWorksheetNames() and listWorksheetInfo to use XMLReader with streamed XML rather than SimpleXML - @MarkBaker [#78](https://github.com/PHPOffice/PHPExcel/issues/78) +- Restructuring of PHPExcel Exceptions - @dbonsch +- Refactor Calculation Engine from singleton to a Multiton - @MarkBaker + - Ensures that calculation cache is maintained independently for different workbooks + +## [1.7.8] - 2012-10-12 + +### Features + +- Phar builder script to add phar file as a distribution option - @kkamkou +- Refactor PDF Writer to allow use with a choice of PDF Rendering library - @MarkBaker + - rather than restricting to tcPDF + - Current options are tcPDF, mPDF, DomPDF + - tcPDF Library has now been removed from the deployment bundle- Initial version of HTML Reader - @MarkBaker +- Implement support for AutoFilter in PHPExcel_Writer_Excel5 - @Progi1984 +- Modified ERF and ERFC Engineering functions to accept Excel 2010's modified acceptance of negative arguments - @MarkBaker +- Support SheetView `view` attribute (Excel2007) - @k1LoW +- Excel compatibility option added for writing CSV files - @MarkBaker + - While Excel 2010 can read CSV files with a simple UTF-8 BOM, Excel2007 and earlier require UTF-16LE encoded tab-separated files. + - The new setExcelCompatibility(TRUE) option for the CSV Writer will generate files with this formatting for easy import into Excel2007 and below.- Language implementations for Turkish (tr) - @MarkBaker +- Added fraction tests to advanced value binder - @MarkBaker + +### General + +- Allow call to font setUnderline() for underline format to specify a simple boolean for UNDERLINE_NONE or UNDERLINE_SINGLE - @MarkBaker +- Add Currency detection to the Advanced Value Binder - @alexgann +- setCellValueExplicitByColumnAndRow() do not return PHPExcel_Worksheet - @MarkBaker [CodePlex #18404](https://phpexcel.codeplex.com/workitem/18404) +- Reader factory doesn't read anymore XLTX and XLT files - @MarkBaker [CodePlex #18324](https://phpexcel.codeplex.com/workitem/18324) +- Magic __toString() method added to Cell object to return raw data value as a string - @MarkBaker +- Add cell indent to html rendering - @alexgann + +### Bugfixes + +- ZeroHeight for rows in sheet format - @Raghav1981 +- OOCalc cells containing inside the tag - @cyberconte +- Fix to listWorksheetInfo() method for OOCalc Reader - @schir1964 +- Support for "e" (epoch) date format mask - @MarkBaker + - Rendered as a 4-digit CE year in non-Excel outputs- Background color cell is always black when editing cell - @MarkBaker +- Allow "no impact" to formats on Conditional Formatting - @MarkBaker +- OOCalc Reader fix for NULL cells - @wackonline +- Fix to excel2007 Chart Writer when a $plotSeriesValues is empty - @seltzlab +- Various fixes to Chart handling - @MarkBaker +- Error loading xlsx file with column breaks - @MarkBaker [CodePlex #18370](https://phpexcel.codeplex.com/workitem/18370) +- OOCalc Reader now handles percentage and currency data types - @MarkBaker +- mb_stripos empty delimiter - @MarkBaker +- getNestingLevel() Error on Excel5 Read - @takaakik +- Fix to Excel5 Reader when cell annotations are defined before their referenced text objects - @MarkBaker +- OOCalc Reader modified to process number-rows-repeated - @MarkBaker +- Chart Title compatibility on Excel 2007 - @MarkBaker [CodePlex #18377](https://phpexcel.codeplex.com/workitem/18377) +- Chart Refresh returning cell reference rather than values - @MarkBaker [CodePlex #18146](https://phpexcel.codeplex.com/workitem/18146) +- Autoshape being identified in twoCellAnchor when includeCharts is TRUE triggering load error - @MarkBaker [CodePlex #18145](https://phpexcel.codeplex.com/workitem/18145) +- v-type texts for series labels now recognised and parsed correctly - @MarkBaker [CodePlex #18325](https://phpexcel.codeplex.com/workitem/18325) +- load file failed if the file has no extensionType - @wolf5x [CodePlex #18492](https://phpexcel.codeplex.com/workitem/18492) +- Pattern fill colours in Excel2007 Style Writer - @dverspui +- Excel2007 Writer order of font style elements to conform with Excel2003 using compatibility pack - @MarkBaker +- Problems with $_activeSheetIndex when decreased below 0. - @MarkBaker [CodePlex #18425](https://phpexcel.codeplex.com/workitem/18425) +- PHPExcel_CachedObjectStorage_SQLite3::cacheMethodIsAvailable() uses class_exists - autoloader throws error - @MarkBaker [CodePlex #18597](https://phpexcel.codeplex.com/workitem/18597) +- Cannot access private property PHPExcel_CachedObjectStorageFactory::$_cacheStorageMethod - @MarkBaker [CodePlex #18598](https://phpexcel.codeplex.com/workitem/18598) +- Data titles for charts - @MarkBaker [CodePlex #18397](https://phpexcel.codeplex.com/workitem/18397) + - PHPExcel_Chart_Layout now has methods for getting/setting switches for displaying/hiding chart data labels- Discard single cell merge ranges when reading (stupid that Excel allows them in the first place) - @MarkBaker +- Discard hidden autoFilter named ranges - @MarkBaker + +## [1.7.7] - 2012-05-19 + +### Bugfixes + +- Support for Rich-Text in PHPExcel_Writer_Excel5 - @Progi1984 [CodePlex #8916](https://phpexcel.codeplex.com/workitem/8916) +- Change iterators to implement Iterator rather than extend CachingIterator, as a fix for PHP 5.4. changes in SPL - @MarkBaker +- Invalid cell coordinate in Autofilter for Excel2007 Writer - @MarkBaker [CodePlex #15459](https://phpexcel.codeplex.com/workitem/15459) +- PCLZip library issue - @MarkBaker [CodePlex #15518](https://phpexcel.codeplex.com/workitem/15518) +- Excel2007 Reader canRead function bug - @MarkBaker [CodePlex #15537](https://phpexcel.codeplex.com/workitem/15537) +- Support for Excel functions whose return can be used as either a value or as a cell reference depending on its context within a formula - @MarkBaker +- ini_set() call in Calculation class destructor - @gilles06 [CodePlex #15707](https://phpexcel.codeplex.com/workitem/15707) +- RangeToArray strange array keys - @MarkBaker [CodePlex #15786](https://phpexcel.codeplex.com/workitem/15786) +- INDIRECT() function doesn't work with named ranges - @MarkBaker [CodePlex #15762](https://phpexcel.codeplex.com/workitem/15762) +- Locale-specific fix to text functions when passing a boolean argument instead of a string - @MarkBaker +- reader/CSV fails on this file - @MarkBaker [CodePlex #16246](https://phpexcel.codeplex.com/workitem/16246) + - auto_detect_line_endings now set in CSV reader- $arguments improperly used in CachedObjectStorage/PHPTemp.php - @MarkBaker [CodePlex #16212](https://phpexcel.codeplex.com/workitem/16212) +- Bug In Cache System (cell reference when throwing caching errors) - @MarkBaker [CodePlex #16643](https://phpexcel.codeplex.com/workitem/16643) +- PHP Invalid index notice on writing excel file when active sheet has been deleted - @MarkBaker [CodePlex #16895](https://phpexcel.codeplex.com/workitem/16895) +- External links in Excel2010 files cause Fatal error - @MarkBaker [CodePlex #16956](https://phpexcel.codeplex.com/workitem/16956) +- Previous calculation engine error conditions trigger cyclic reference errors - @MarkBaker [CodePlex #16960](https://phpexcel.codeplex.com/workitem/16960) +- PHPExcel_Style::applyFromArray() returns null rather than style object in advanced mode - @mkopinsky [CodePlex #16266](https://phpexcel.codeplex.com/workitem/16266) +- Cell::getFormattedValue returns RichText object instead of string - @fauvel [CodePlex #16958](https://phpexcel.codeplex.com/workitem/16958) +- Indexed colors do not refer to Excel's indexed colors? - @MarkBaker [CodePlex #17166](https://phpexcel.codeplex.com/workitem/17166) +- Indexed colors should be consistent with Excel and start from 1 (current index starts at 0) - @MarkBaker [CodePlex #17199](https://phpexcel.codeplex.com/workitem/17199) +- Named Range definition in .xls when sheet reeference is quote wrapped - @MarkBaker [CodePlex #17262](https://phpexcel.codeplex.com/workitem/17262) +- duplicateStyle() method doesn't duplicate conditional formats - @MarkBaker [CodePlex #17403](https://phpexcel.codeplex.com/workitem/17403) + - Added an equivalent duplicateConditionalStyle() method for duplicating conditional styles- =sumproduct(A,B) <> =sumproduct(B,A) in xlsx - @bnr [CodePlex #17501](https://phpexcel.codeplex.com/workitem/17501) + +### Features + +- OOCalc cells contain same data bug? - @cyberconte [CodePlex #17471](https://phpexcel.codeplex.com/workitem/17471) +- listWorksheetInfo() method added to Readers... courtesy of Christopher Mullins - @schir1964 +- Options for cell caching using Igbinary and SQLite/SQlite3. - @MarkBaker +- Additional row iterator options: allow a start row to be defined in the constructor; seek(), and prev() methods added. - @MarkBaker +- Implement document properties in Excel5 writer - @Progi1984 [CodePlex #9759](https://phpexcel.codeplex.com/workitem/9759) + +### General + +- Implement chart functionality (EXPERIMENTAL) - @MarkBaker [CodePlex #16](https://phpexcel.codeplex.com/workitem/16) + - Initial definition of chart objects. + - Reading Chart definitions through the Excel2007 Reader + - Facility to render charts to images using the 3rd-party jpgraph library + - Writing Charts using the Excel2007 Writer- Fix to build to ensure that Examples are included with the documentation - @MarkBaker +- Reduce cell caching overhead using dirty flag to ensure that cells are only rewritten to the cache if they have actually been changed - @MarkBaker +- Improved memory usage in CSV Writer - @MarkBaker +- Improved speed and memory usage in Excel5 Writer - @MarkBaker +- Experimental - @MarkBaker + - Added getHighestDataColumn(), getHighestDataRow(), getHighestRowAndColumn() and calculateWorksheetDataDimension() methods for the worksheet that return the highest row and column that have cell records- Support for Rich-Text in PHPExcel_Writer_Excel5 - @Progi1984 [CodePlex #8916](https://phpexcel.codeplex.com/workitem/8916) +- Two easy to fix Issues concerning PHPExcel_Token_Stack (l10n/UC) - @MarkBaker [CodePlex #15405](https://phpexcel.codeplex.com/workitem/15405) +- Locale file paths not fit for windows - @MarkBaker [CodePlex #15461](https://phpexcel.codeplex.com/workitem/15461) +- Add file directory as a cache option for cache_to_discISAM - @MarkBaker [CodePlex #16643](https://phpexcel.codeplex.com/workitem/16643) +- Datatype.php & constant TYPE_NULL - @MarkBaker [CodePlex #16923](https://phpexcel.codeplex.com/workitem/16923) +- Ensure use of system temp directory for all temporary work files, unless explicitly specified - @MarkBaker +- [Patch] faster stringFromColumnIndex() - @char101 [CodePlex #16359](https://phpexcel.codeplex.com/workitem/16359) +- Fix for projects that still use old autoloaders - @whit1206 [CodePlex #16028](https://phpexcel.codeplex.com/workitem/16028) +- Unknown codepage: 10007 - @atz [CodePlex #17024](https://phpexcel.codeplex.com/workitem/17024) + - Additional Mac codepages + +## [1.7.6] - 2011-02-27 + +### Features + +- Provide option to use PCLZip as an alternative to ZipArchive. - @MarkBaker + - This allows the writing of Excel2007 files, even without ZipArchive enabled (it does require zlib), or when php_zip is one of the buggy PHP 5.2.6 or 5.2.8 versions + - It can be enabled using PHPExcel_Settings::setZipClass(PHPExcel_Settings::PCLZIP); + - Note that it is not yet implemented as an alternative to ZipArchive for those Readers that are extracting from zips- Added listWorksheetNames() method to Readers that support multiple worksheets in a workbook, allowing a user to extract a list of all the worksheet names from a file without parsing/loading the whole file. - @MarkBaker [CodePlex #14979](https://phpexcel.codeplex.com/workitem/14979) +- Speed boost and memory reduction in the Worksheet toArray() method. - @MarkBaker +- Added new rangeToArray() and namedRangeToArray() methods to the PHPExcel_Worksheet object. - @MarkBaker + - Functionally, these are identical to the toArray() method, except that they take an additional first parameter of a Range (e.g. 'B2:C3') or a Named Range name. + - Modified the toArray() method so that it actually uses rangeToArray().- Added support for cell comments in the OOCalc, Gnumeric and Excel2003XML Readers, and in the Excel5 Reader - @MarkBaker +- Improved toFormattedString() handling for Currency and Accounting formats to render currency symbols - @MarkBaker + +### Bugfixes + +- Implement more Excel calculation functions - @MarkBaker + - Implemented the DAVERAGE(), DCOUNT(), DCOUNTA(), DGET(), DMAX(), DMIN(), DPRODUCT(), DSTDEV(), DSTDEVP(), DSUM(), DVAR() and DVARP() Database functions- Simple =IF() formula disappears - @MarkBaker [CodePlex #14888](https://phpexcel.codeplex.com/workitem/14888) +- PHP Warning: preg_match(): Compilation failed: PCRE does not support \\L, \\l, \\N, \\P, \\p, \\U, \\u, or \\X - @MarkBaker [CodePlex #14898](https://phpexcel.codeplex.com/workitem/14898) +- VLOOKUP choking on parameters in PHPExcel.1.7.5/PHPExcel_Writer_Excel2007 - @MarkBaker [CodePlex #14901](https://phpexcel.codeplex.com/workitem/14901) +- PHPExcel_Cell::isInRange() incorrect results - offset by one column - @MarkBaker [CodePlex #14973](https://phpexcel.codeplex.com/workitem/14973) +- Treat CodePage of 0 as CP1251 (for .xls files written by applications that don't set the CodePage correctly, such as Apple Numbers) - @MarkBaker +- Need method for removing autoFilter - @MarkBaker [CodePlex #11583](https://phpexcel.codeplex.com/workitem/11583) +- coordinateFromString throws exception for rows greater than 99,999 - @MarkBaker [CodePlex #15029](https://phpexcel.codeplex.com/workitem/15029) +- PHPExcel Excel2007 Reader colour problems with solidfill - @MarkBaker [CodePlex #14999](https://phpexcel.codeplex.com/workitem/14999) +- Formatting get lost and edit a template XLSX file - @MarkBaker [CodePlex #13215](https://phpexcel.codeplex.com/workitem/13215) +- Excel 2007 Reader /writer lost fontcolor - @MarkBaker [CodePlex #14029](https://phpexcel.codeplex.com/workitem/14029) +- file that makes cells go black - @MarkBaker [CodePlex #13374](https://phpexcel.codeplex.com/workitem/13374) +- Minor patchfix for Excel2003XML Reader when XML is defined with a charset attribute - @MarkBaker +- PHPExcel_Worksheet->toArray() index problem - @MarkBaker [CodePlex #15089](https://phpexcel.codeplex.com/workitem/15089) +- Merge cells 'un-merge' when using an existing spreadsheet - @MarkBaker [CodePlex #15094](https://phpexcel.codeplex.com/workitem/15094) +- Worksheet fromArray() only working with 2-D arrays - @MarkBaker [CodePlex #15129](https://phpexcel.codeplex.com/workitem/15129) +- rangeToarray function modified for non-existent cells - @xkeshav [CodePlex #15172](https://phpexcel.codeplex.com/workitem/15172) +- Images not getting copyied with the ->clone function - @MarkBaker [CodePlex #14980](https://phpexcel.codeplex.com/workitem/14980) +- AdvancedValueBinder.php: String sometimes becomes a date when it shouldn't - @MarkBaker [CodePlex #11576](https://phpexcel.codeplex.com/workitem/11576) +- Fix Excel5 Writer so that it only writes column dimensions for columns that are actually used rather than the full range (A to IV) - @MarkBaker +- FreezePane causing damaged or modified error - @MarkBaker [CodePlex #15198](https://phpexcel.codeplex.com/workitem/15198) + - The freezePaneByColumnAndRow() method row argument should default to 1 rather than 0. + - Default row argument for all __ByColumnAndRow() methods should be 1- Column reference rather than cell reference in Print Area definition - @MarkBaker [CodePlex #15121](https://phpexcel.codeplex.com/workitem/15121) + - Fix Excel2007 Writer to handle print areas that are defined as row or column ranges rather than just as cell ranges- Reduced false positives from isDateTimeFormatCode() method by suppressing testing within quoted strings - @MarkBaker +- Caching and tmp partition exhaustion - @MarkBaker [CodePlex #15312](https://phpexcel.codeplex.com/workitem/15312) +- Writing to Variable No Longer Works. $_tmp_dir Missing in PHPExcel\PHPExcel\Shared\OLE\PPS\Root.php - @MarkBaker [CodePlex #15308](https://phpexcel.codeplex.com/workitem/15308) +- Named ranges with dot don't get parsed properly - @MarkBaker [CodePlex #15379](https://phpexcel.codeplex.com/workitem/15379) +- insertNewRowBefore fails to consistently update references - @MarkBaker [CodePlex #15096](https://phpexcel.codeplex.com/workitem/15096) +- "i" is not a valid character for Excel date format masks (in isDateTimeFormatCode() method) - @MarkBaker +- PHPExcel_ReferenceHelper::insertNewBefore() is missing an 'Update worksheet: comments' section - @MKunert [CodePlex #15421](https://phpexcel.codeplex.com/workitem/15421) + +### General + +- Full column/row references in named ranges not supported by updateCellReference() - @MarkBaker [CodePlex #15409](https://phpexcel.codeplex.com/workitem/15409) +- Improved performance (speed), for building the Shared Strings table in the Excel2007 Writer. - @MarkBaker +- Improved performance (speed), for PHP to Excel date conversions - @MarkBaker +- Enhanced SheetViews element structures in the Excel2007 Writer for frozen panes. - @MarkBaker +- Removed Serialized Reader/Writer as these no longer work. - @MarkBaker + +## [1.7.5] - 2010-12-10 + +### Features + +- Implement Gnumeric File Format - @MarkBaker [CodePlex #8769](https://phpexcel.codeplex.com/workitem/8769) + - Initial work on Gnumeric Reader (Worksheet Data, Document Properties and basic Formatting)- Support for Extended Workbook Properties in Excel2007, Excel5 and OOCalc Readers; support for User-defined Workbook Properties in Excel2007 and OOCalc Readers - @MarkBaker +- Support for Extended and User-defined Workbook Properties in Excel2007 Writer - @MarkBaker +- Provided a setGenerateSheetNavigationBlock(false); option to suppress generation of the sheet navigation block when writing multiple worksheets to HTML - @MarkBaker +- Advanced Value Binder now recognises TRUE/FALSE strings (locale-specific) and converts to boolean - @MarkBaker +- PHPExcel_Worksheet->toArray() is returning truncated values - @MarkBaker [CodePlex #14301](https://phpexcel.codeplex.com/workitem/14301) +- Configure PDF Writer margins based on Excel Worksheet Margin Settings value - @MarkBaker +- Added Contiguous flag for the CSV Reader, when working with Read Filters - @MarkBaker +- Added getFormattedValue() method for cell object - @MarkBaker +- Added strictNullComparison argument to the worksheet fromArray() method - @MarkBaker + +### Bugfixes + +- Fix to toFormattedString() method in PHPExcel_Style_NumberFormat to handle fractions with a # code for the integer part - @MarkBaker +- NA() doesn't propagate in matrix calc - quick fix in JAMA/Matrix.php - @MarkBaker [CodePlex #14143](https://phpexcel.codeplex.com/workitem/14143) +- Excel5 : Formula : String constant containing double quotation mark - @Progi1984 [CodePlex #7895](https://phpexcel.codeplex.com/workitem/7895) +- Excel5 : Formula : Percent - @Progi1984 [CodePlex #7895](https://phpexcel.codeplex.com/workitem/7895) +- Excel5 : Formula : Error constant - @Progi1984 [CodePlex #7895](https://phpexcel.codeplex.com/workitem/7895) +- Excel5 : Formula : Concatenation operator - @Progi1984 [CodePlex #7895](https://phpexcel.codeplex.com/workitem/7895) +- Worksheet clone broken for CachedObjectStorage_Memory - @MarkBaker [CodePlex #14146](https://phpexcel.codeplex.com/workitem/14146) +- PHPExcel_Reader_Excel2007 fails when gradient fill without type is present in a file - @MarkBaker [CodePlex #12998](https://phpexcel.codeplex.com/workitem/12998) +- @ format for numeric strings in XLSX to CSV conversion - @MarkBaker [CodePlex #14176](https://phpexcel.codeplex.com/workitem/14176) +- Advanced Value Binder Not Working? - @MarkBaker [CodePlex #14223](https://phpexcel.codeplex.com/workitem/14223) +- unassigned object variable in PHPExcel->removeCellXfByIndex - @MarkBaker [CodePlex #14226](https://phpexcel.codeplex.com/workitem/14226) +- problem with getting cell values from another worksheet... (if cell doesn't exist) - @MarkBaker [CodePlex #14236](https://phpexcel.codeplex.com/workitem/14236) +- Setting cell values to one char strings & Trouble reading one character string (thanks gorfou) - @MarkBaker +- Worksheet title exception when duplicate worksheet is being renamed but exceeds the 31 character limit - @MarkBaker [CodePlex #14256](https://phpexcel.codeplex.com/workitem/14256) +- Named range with sheet name that contains the $ throws exception when getting the cell - @MarkBaker [CodePlex #14086](https://phpexcel.codeplex.com/workitem/14086) +- Added autoloader to DefaultValueBinder and AdvancedValueBinder - @MarkBaker +- Modified PHPExcel_Shared_Date::isDateTimeFormatCode() to return false if format code begins with "_" or with "0 " to prevent false positives - @MarkBaker + - These leading characters are most commonly associated with number, currency or accounting (or occasionally fraction) formats- BUG : Excel5 and setReadFilter ? - @MarkBaker [CodePlex #14374](https://phpexcel.codeplex.com/workitem/14374) +- Wrong exception message while deleting column - @MarkBaker [CodePlex #14425](https://phpexcel.codeplex.com/workitem/14425) +- Formula evaluation fails with Japanese sheet refs - @MarkBaker [CodePlex #14679](https://phpexcel.codeplex.com/workitem/14679) +- PHPExcel_Writer_PDF does not handle cell borders correctly - @MarkBaker [CodePlex #13559](https://phpexcel.codeplex.com/workitem/13559) +- Style : applyFromArray() for 'allborders' not working - @MarkBaker [CodePlex #14831](https://phpexcel.codeplex.com/workitem/14831) + +### General + +- Using $this when not in object context in Excel5 Reader - @MarkBaker [CodePlex #14837](https://phpexcel.codeplex.com/workitem/14837) +- Removes a unnecessary loop through each cell when applying conditional formatting to a range. - @MarkBaker +- Removed spurious PHP end tags (?>) - @MarkBaker +- Improved performance (speed) and reduced memory overheads, particularly for the Writers, but across the whole library. - @MarkBaker + +## [1.7.4] - 2010-08-26 + +### Bugfixes + +- Excel5 : Formula : Power - @Progi1984 [CodePlex #7895](https://phpexcel.codeplex.com/workitem/7895) +- Excel5 : Formula : Unary plus - @Progi1984 [CodePlex #7895](https://phpexcel.codeplex.com/workitem/7895) +- Excel5 : Just write the Escher stream if necessary in Worksheet - @Progi1984 +- Syntax errors in memcache.php 1.7.3c - @MarkBaker [CodePlex #13433](https://phpexcel.codeplex.com/workitem/13433) +- Support for row or column ranges in the calculation engine, e.g. =SUM(C:C) or =SUM(1:2) - @MarkBaker + - Also support in the calculation engine for absolute row or column ranges e.g. =SUM($C:$E) or =SUM($3:5)- Picture problem with Excel 2003 - @Erik Tilt [CodePlex #13455](https://phpexcel.codeplex.com/workitem/13455) +- Wrong variable used in addExternalSheet in PHPExcel.php - @MarkBaker [CodePlex #13484](https://phpexcel.codeplex.com/workitem/13484) +- "Invalid cell coordinate" error when formula access data from an other sheet - @MarkBaker [CodePlex #13515](https://phpexcel.codeplex.com/workitem/13515) +- (related to Work item 13515) Calculation engine confusing cell range worksheet when referencing cells in a different worksheet to the formula - @MarkBaker +- Wrong var naming in Worksheet->garbageCollect() - @MarkBaker [CodePlex #13752](https://phpexcel.codeplex.com/workitem/13752) +- PHPExcel_Style_*::__clone() methods cause cloning loops? - @MarkBaker [CodePlex #13764](https://phpexcel.codeplex.com/workitem/13764) +- Recent builds causing problems loading xlsx files? (ZipArchive issue?) - @MarkBaker [CodePlex #11488](https://phpexcel.codeplex.com/workitem/11488) +- cache_to_apc causes fatal error when processing large data sets - @MarkBaker [CodePlex #13856](https://phpexcel.codeplex.com/workitem/13856) +- OOCalc reader misses first line if it's a 'table-header-row' - @MarkBaker [CodePlex #13880](https://phpexcel.codeplex.com/workitem/13880) +- using cache with copy or clone bug? - @MarkBaker [CodePlex #14011](https://phpexcel.codeplex.com/workitem/14011) + - Fixed $worksheet->copy() or clone $worksheet when using cache_in_memory, cache_in_memory_gzip, cache_in_memory_serialized, cache_to_discISAM, cache_to_phpTemp, cache_to_apc and cache_to_memcache; + - Fixed but untested when using cache_to_wincache. +### Features + +- Standard Deviation functions returning DIV/0 Error when Standard Deviation is zero - @MarkBaker [CodePlex #13450](https://phpexcel.codeplex.com/workitem/13450) +- Support for print area with several ranges in the Excel2007 reader, and improved features for editing print area with several ranges - @MarkBaker +- Improved Cell Exception Reporting - @MarkBaker [CodePlex #13769](https://phpexcel.codeplex.com/workitem/13769) + +### General + +- Fixed problems with reading Excel2007 Properties - @MarkBaker +- PHP Strict Standards: Non-static method PHPExcel_Shared_String::utf16_decode() should not be called statically - @MarkBaker +- Array functions were ignored when loading an existing file containing them, and as a result, they would lose their 'cse' status. - @MarkBaker +- Minor memory tweaks to Excel2007 Writer - @MarkBaker +- Modified ReferenceHelper updateFormulaReferences() method to handle updates to row and column cell ranges (including absolute references e.g. =SUM(A:$E) or =SUM($5:5), and range/cell references that reference a worksheet by name), and to provide both performance and memory improvements. - @MarkBaker +- Modified Excel2007 Reader so that ReferenceHelper class is instantiated only once rather than for every shared formula in a workbook. - @MarkBaker +- Correct handling for additional (synonym) formula tokens in Excel5 Reader - @MarkBaker +- Additional reading of some Excel2007 Extended Properties (Company, Manager) - @MarkBaker + +## [1.7.3c] - 2010-06-01 + +### Bugfixes + +- Fatal error: Class 'ZipArchive' not found... ...Reader/Excel2007.php on line 217 - @MarkBaker [CodePlex #13012](https://phpexcel.codeplex.com/workitem/13012) +- PHPExcel_Writer_Excel2007 error after 1.7.3b - @MarkBaker [CodePlex #13398](https://phpexcel.codeplex.com/workitem/13398) + +## [1.7.3b] - 2010-05-31 + +### Bugfixes + +- Infinite loop when reading - @MarkBaker [CodePlex #12903](https://phpexcel.codeplex.com/workitem/12903) +- Wrong method chaining on PHPExcel_Worksheet class - @MarkBaker [CodePlex #13381](https://phpexcel.codeplex.com/workitem/13381) + +## [1.7.3] - 2010-05-17 + +### General + +- Applied patch 4990 (modified) - @Erik Tilt +- Applied patch 5568 (modified) - @MarkBaker +- Applied patch 5943 - @MarkBaker +- Upgrade build script to use Phing - @MarkBaker [CodePlex #13042](https://phpexcel.codeplex.com/workitem/13042) +- Replacing var with public/private - @Erik Tilt [CodePlex #11586](https://phpexcel.codeplex.com/workitem/11586) +- Applied Anthony's Sterling's Class Autoloader to reduce memory overhead by "Lazy Loading" of classes - @MarkBaker +- Modification to functions that accept a date parameter to support string values containing ordinals as per Excel (English language only) - @MarkBaker +- Modify PHPExcel_Style_NumberFormat::toFormattedString() to handle dates that fall outside of PHP's 32-bit date range - @MarkBaker +- Applied patch 5207 - @MarkBaker + +### Features + +- PHPExcel developer documentation: Set page margins - @Erik Tilt [CodePlex #11970](https://phpexcel.codeplex.com/workitem/11970) +- Special characters and accents in SYLK reader - @Erik Tilt [CodePlex #11038](https://phpexcel.codeplex.com/workitem/11038) +- Implement more Excel calculation functions - @MarkBaker + - Implemented the COUPDAYS(), COUPDAYBS(), COUPDAYSNC(), COUPNCD(), COUPPCD() and PRICE() Financial functions + - Implemented the N() and TYPE() Information functions + - Implemented the HYPERLINK() Lookup and Reference function- Horizontal page break support in PHPExcel_Writer_PDF - @Erik Tilt [CodePlex #11526](https://phpexcel.codeplex.com/workitem/11526) +- Introduce method setActiveSheetIndexByName() - @Erik Tilt [CodePlex #11529](https://phpexcel.codeplex.com/workitem/11529) +- AdvancedValueBinder.php: Automatically wrap text when there is new line in string (ALT+"Enter") - @Erik Tilt [CodePlex #11550](https://phpexcel.codeplex.com/workitem/11550) +- Data validation support in PHPExcel_Reader_Excel5 and PHPExcel_Writer_Excel5 - @Erik Tilt [CodePlex #10300](https://phpexcel.codeplex.com/workitem/10300) +- Improve autosize calculation - @MarkBaker [CodePlex #11616](https://phpexcel.codeplex.com/workitem/11616) +- Methods to translate locale-specific function names in formulae - @MarkBaker + - Language implementations for Czech (cs), Danish (da), German (de), English (uk), Spanish (es), Finnish (fi), French (fr), Hungarian (hu), Italian (it), Dutch (nl), Norwegian (no), Polish (pl), Portuguese (pt), Brazilian Portuguese (pt_br), Russian (ru) and Swedish (sv)- Implement document properties in Excel5 reader/writer - @Erik Tilt [CodePlex #9759](https://phpexcel.codeplex.com/workitem/9759) + - Fixed so far for PHPExcel_Reader_Excel5- Show/hide row and column headers in worksheet - @Erik Tilt [CodePlex #11849](https://phpexcel.codeplex.com/workitem/11849) +- Can't set font on writing PDF (by key) - @Erik Tilt [CodePlex #11919](https://phpexcel.codeplex.com/workitem/11919) +- Thousands scale (1000^n) support in PHPExcel_Style_NumberFormat::toFormattedString - @Erik Tilt [CodePlex #12096](https://phpexcel.codeplex.com/workitem/12096) +- Implement repeating rows in PDF and HTML writer - @Erik Tilt +- Sheet tabs in PHPExcel_Writer_HTML - @Erik Tilt [CodePlex #12289](https://phpexcel.codeplex.com/workitem/12289) +- Add Wincache CachedObjectProvider - @MarkBaker [CodePlex #13041](https://phpexcel.codeplex.com/workitem/13041) +- Configure PDF Writer paper size based on Excel Page Settings value, and provided methods to override paper size and page orientation with the writer - @MarkBaker + - Note PHPExcel defaults to Letter size, while the previous PDF writer enforced A4 size, so PDF writer will now default to Letter- Initial implementation of cell caching: allowing larger workbooks to be managed, but at a cost in speed - @MarkBaker + +### Bugfixes + +- Added an identify() method to the IO Factory that identifies the reader which will be used to load a particular file without actually loading it. - @MarkBaker +- Warning messages with INDEX function having 2 arguments - @MarkBaker [CodePlex #10979](https://phpexcel.codeplex.com/workitem/10979) +- setValue('=') should result in string instead of formula - @Erik Tilt [CodePlex #11473](https://phpexcel.codeplex.com/workitem/11473) +- method _raiseFormulaError should no be private - @MarkBaker [CodePlex #11471](https://phpexcel.codeplex.com/workitem/11471) +- Fatal error: Call to undefined function mb_substr() in ...Classes\PHPExcel\Reader\Excel5.php on line 2903 - @Erik Tilt [CodePlex #11485](https://phpexcel.codeplex.com/workitem/11485) +- getBold(), getItallic(), getStrikeThrough() not always working with PHPExcel_Reader_Excel2007 - @Erik Tilt [CodePlex #11487](https://phpexcel.codeplex.com/workitem/11487) +- AdvancedValueBinder.php not working correctly for $cell->setValue('hh:mm:ss') - @Erik Tilt [CodePlex #11492](https://phpexcel.codeplex.com/workitem/11492) +- Fixed leap year handling for the YEARFRAC() Date/Time function when basis ia 1 (Actual/actual) - @MarkBaker +- Warning messages - @MarkBaker [CodePlex #11490](https://phpexcel.codeplex.com/workitem/11490) + - Calculation Engine code modified to enforce strict standards for pass by reference- PHPExcel_Cell_AdvancedValueBinder doesnt work for dates in far future - @Erik Tilt [CodePlex #11483](https://phpexcel.codeplex.com/workitem/11483) +- MSODRAWING bug with long CONTINUE record in PHPExcel_Reader_Excel5 - @Erik Tilt [CodePlex #11528](https://phpexcel.codeplex.com/workitem/11528) +- PHPExcel_Reader_Excel2007 reads print titles as named range when there is more than one sheet - @Erik Tilt [CodePlex #11571](https://phpexcel.codeplex.com/workitem/11571) +- missing @return in phpdocblock in reader classes - @Erik Tilt [CodePlex #11561](https://phpexcel.codeplex.com/workitem/11561) +- AdvancedValueBinder.php: String sometimes becomes a date when it shouldn't - @Erik Tilt [CodePlex #11576](https://phpexcel.codeplex.com/workitem/11576) +- Small numbers escape treatment in PHPExcel_Style_NumberFormat::toFormattedString() - @Erik Tilt [CodePlex #11588](https://phpexcel.codeplex.com/workitem/11588) +- Blank styled cells are not blank in output by HTML writer due to   - @Erik Tilt [CodePlex #11590](https://phpexcel.codeplex.com/workitem/11590) +- Calculation engine bug: Existing, blank cell + number gives #NUM - @MarkBaker [CodePlex #11587](https://phpexcel.codeplex.com/workitem/11587) +- AutoSize only measures length of first line in cell with multiple lines (ALT+Enter) - @Erik Tilt [CodePlex #11608](https://phpexcel.codeplex.com/workitem/11608) +- Fatal error running Tests/12serializedfileformat.php (PHPExcel 1.7.2) - @Erik Tilt [CodePlex #11608](https://phpexcel.codeplex.com/workitem/11608) +- Fixed various errors in the WORKDAY() and NETWORKDAYS() Date/Time functions (particularly related to holidays) - @MarkBaker +- Uncaught exception 'Exception' with message 'Valid scale is between 10 and 400.' in Classes/PHPExcel/Worksheet/SheetView.php:115 - @Erik Tilt [CodePlex #11660](https://phpexcel.codeplex.com/workitem/11660) +- "Unrecognized token 39 in formula" with PHPExcel_Reader_Excel5 (occuring with add-in functions) - @Erik Tilt [CodePlex #11551](https://phpexcel.codeplex.com/workitem/11551) +- Excel2007 reader not reading PHPExcel_Style_Conditional::CONDITION_EXPRESSION - @Erik Tilt [CodePlex #11668](https://phpexcel.codeplex.com/workitem/11668) +- Fix to the BESSELI(), BESSELJ(), BESSELK(), BESSELY() and COMPLEX() Engineering functions to use correct default values for parameters - @MarkBaker +- DATEVALUE function not working for pure time values + allow DATEVALUE() function to handle partial dates (e.g. "1-Jun" or "12/2010") - @MarkBaker [CodePlex #11525](https://phpexcel.codeplex.com/workitem/11525) +- Fix for empty quoted strings in formulae - @MarkBaker +- Trap for division by zero in Bessel functions - @MarkBaker +- Fix to OOCalc Reader to convert semi-colon (;) argument separator in formulae to a comma (,) - @MarkBaker +- PHPExcel_Writer_Excel5_Parser cannot parse formula like =SUM(C$5:C5) - @Erik Tilt [CodePlex #11693](https://phpexcel.codeplex.com/workitem/11693) +- Fix to OOCalc Reader to handle dates that fall outside 32-bit PHP's date range - @MarkBaker +- File->sys_get_temp_dir() can fail in safe mode - @Erik Tilt [CodePlex #11692](https://phpexcel.codeplex.com/workitem/11692) +- Sheet references in Excel5 writer do not work when referenced sheet title contains non-Latin symbols - @Erik Tilt [CodePlex #11727](https://phpexcel.codeplex.com/workitem/11727) +- Bug in HTML writer can result in missing rows in output - @Erik Tilt [CodePlex #11743](https://phpexcel.codeplex.com/workitem/11743) +- setShowGridLines(true) not working with PHPExcel_Writer_PDF - @Erik Tilt [CodePlex #11674](https://phpexcel.codeplex.com/workitem/11674) +- PHPExcel_Worksheet_RowIterator initial position incorrect - @Erik Tilt [CodePlex #11836](https://phpexcel.codeplex.com/workitem/11836) +- PHPExcel_Worksheet_HeaderFooterDrawing Strict Exception thrown (by jshaw86) - @Erik Tilt [CodePlex #11835](https://phpexcel.codeplex.com/workitem/11835) +- Parts of worksheet lost when there are embedded charts (Excel5 reader) - @Erik Tilt [CodePlex #11850](https://phpexcel.codeplex.com/workitem/11850) +- VLOOKUP() function error when lookup value is passed as a cell reference rather than an absolute value - @MarkBaker +- First segment of Rich-Text not read correctly by PHPExcel_Reader_Excel2007 - @Erik Tilt [CodePlex #12041](https://phpexcel.codeplex.com/workitem/12041) +- Fatal Error with getCell('name') when name matches the pattern for a cell reference - @MarkBaker [CodePlex #12048](https://phpexcel.codeplex.com/workitem/12048) +- excel5 writer appears to be swapping image locations - @Erik Tilt [CodePlex #12039](https://phpexcel.codeplex.com/workitem/12039) +- Undefined index: host in ZipStreamWrapper.php, line 94 and line 101 - @Erik Tilt [CodePlex #11954](https://phpexcel.codeplex.com/workitem/11954) +- BIFF8 File Format problem (too short COLINFO record) - @Erik Tilt [CodePlex #11672](https://phpexcel.codeplex.com/workitem/11672) +- Column width sometimes changed after read/write with Excel2007 reader/writer - @Erik Tilt [CodePlex #12121](https://phpexcel.codeplex.com/workitem/12121) +- Worksheet.php throws a fatal error when styling is turned off via setReadDataOnly on the reader - @Erik Tilt [CodePlex #11964](https://phpexcel.codeplex.com/workitem/11964) +- Checking for Circular References in Formulae - @MarkBaker [CodePlex #11851](https://phpexcel.codeplex.com/workitem/11851) + - Calculation Engine code now traps for cyclic references, raising an error or throwing an exception, or allows 1 or more iterations through cyclic references, based on a configuration setting- PNG transparency using Excel2007 writer - @Erik Tilt [CodePlex #12244](https://phpexcel.codeplex.com/workitem/12244) +- Custom readfilter error when cell formulas reference excluded cells (Excel5 reader) - @Erik Tilt [CodePlex #12221](https://phpexcel.codeplex.com/workitem/12221) +- Protection problem in XLS - @Erik Tilt [CodePlex #12288](https://phpexcel.codeplex.com/workitem/12288) +- getColumnDimension()->setAutoSize() incorrect on cells with Number Formatting - @Erik Tilt [CodePlex #12300](https://phpexcel.codeplex.com/workitem/12300) +- Notices reading Excel file with Add-in funcitons (PHPExcel_Reader_Excel5) - @Erik Tilt [CodePlex #12378](https://phpexcel.codeplex.com/workitem/12378) +- Excel5 reader not reading formulas with deleted sheet references - @Erik Tilt [CodePlex #12380](https://phpexcel.codeplex.com/workitem/12380) +- Named range (defined name) scope problems for in PHPExcel - @Erik Tilt [CodePlex #12404](https://phpexcel.codeplex.com/workitem/12404) +- PHP Parse error: syntax error, unexpected T_PUBLIC in PHPExcel/Calculation.php on line 3482 - @Erik Tilt [CodePlex #12423](https://phpexcel.codeplex.com/workitem/12423) +- Named ranges don't appear in name box using Excel5 writer - @Erik Tilt [CodePlex #12505](https://phpexcel.codeplex.com/workitem/12505) +- Many merged cells + autoSize column -> slows down the writer - @Erik Tilt [CodePlex #12509](https://phpexcel.codeplex.com/workitem/12509) +- Incorrect fallback order comment in Shared/Strings.php ConvertEncoding() - @Erik Tilt [CodePlex #12539](https://phpexcel.codeplex.com/workitem/12539) +- IBM AIX iconv() will not work, should revert to mbstring etc. instead - @Erik Tilt [CodePlex #12538](https://phpexcel.codeplex.com/workitem/12538) +- Excel5 writer and mbstring functions overload - @Erik Tilt [CodePlex #12568](https://phpexcel.codeplex.com/workitem/12568) +- OFFSET needs to flattenSingleValue the $rows and $columns args - @MarkBaker [CodePlex #12672](https://phpexcel.codeplex.com/workitem/12672) +- Formula with DMAX(): Notice: Undefined offset: 2 in ...\PHPExcel\Calculation.php on line 2365 - @MarkBaker [CodePlex #12546](https://phpexcel.codeplex.com/workitem/12546) + - Note that the Database functions have not yet been implemented- Call to a member function getParent() on a non-object in Classes\\PHPExcel\\Calculation.php Title is required - @MarkBaker [CodePlex #12839](https://phpexcel.codeplex.com/workitem/12839) +- Cyclic Reference in Formula - @MarkBaker [CodePlex #12935](https://phpexcel.codeplex.com/workitem/12935) +- Memory error...data validation? - @MarkBaker [CodePlex #13025](https://phpexcel.codeplex.com/workitem/13025) + +## [1.7.2] - 2010-01-11 + +### General + +- Applied patch 4362 - @Erik Tilt +- Applied patch 4363 (modified) - @Erik Tilt +- 1.7.1 Extremely Slow - Refactored PHPExcel_Calculation_Functions::flattenArray() method and set calculation cache timer default to 2.5 seconds - @MarkBaker [CodePlex #10874](https://phpexcel.codeplex.com/workitem/10874) +- Allow formulae to contain line breaks - @MarkBaker +- split() function deprecated in PHP 5.3.0 - @Erik Tilt [CodePlex #10910](https://phpexcel.codeplex.com/workitem/10910) +- sys_get_temp_dir() requires PHP 5.2.1, not PHP 5.2 [provide fallback function for PHP 5.2.0] - @Erik Tilt +- Implementation of the ISPMT() Financial function by Matt Groves - @MarkBaker +- Put the example of formula with more arguments in documentation - @MarkBaker [CodePlex #11052](https://phpexcel.codeplex.com/workitem/11052) + +### Features + +- Improved accuracy for the GAMMAINV() Statistical Function - @MarkBaker +- XFEXT record support to fix colors change from Excel5 reader, and copy/paste color change with Excel5 writer - @Erik Tilt [CodePlex #10409](https://phpexcel.codeplex.com/workitem/10409) + - Excel5 reader reads RGB color information in XFEXT records for borders, font color and fill color- Implement more Excel calculation functions - @MarkBaker + - Implemented the FVSCHEDULE(), XNPV(), IRR(), MIRR(), XIRR() and RATE() Financial functions + - Implemented the SUMPRODUCT() Mathematical function + - Implemented the ZTEST() Statistical Function- Multiple print areas in one sheet - @Erik Tilt [CodePlex #10919](https://phpexcel.codeplex.com/workitem/10919) +- Store calculated values in output by PHPExcel_Writer_Excel5 - @Erik Tilt [CodePlex #10930](https://phpexcel.codeplex.com/workitem/10930) +- Sheet protection options in Excel5 reader/writer - @Erik Tilt [CodePlex #10939](https://phpexcel.codeplex.com/workitem/10939) +- Modification of the COUNT(), AVERAGE(), AVERAGEA(), DEVSQ, AVEDEV(), STDEV(), STDEVA(), STDEVP(), STDEVPA(), VARA() and VARPA() SKEW() and KURT() functions to correctly handle boolean values depending on whether they're passed in as values, values within a matrix or values within a range of cells. - @MarkBaker +- Cell range selection - @Erik Tilt +- Root-relative path handling - @MarkBaker [CodePlex #10266](https://phpexcel.codeplex.com/workitem/10266) + +### Bugfixes + +- Named Ranges not working with PHPExcel_Writer_Excel5 - @Erik Tilt [CodePlex #11315](https://phpexcel.codeplex.com/workitem/11315) +- Excel2007 Reader fails to load Apache POI generated Excel - @MarkBaker [CodePlex #11206](https://phpexcel.codeplex.com/workitem/11206) +- Number format is broken when system's thousands separator is empty - @MarkBaker [CodePlex #11154](https://phpexcel.codeplex.com/workitem/11154) +- ReferenceHelper::updateNamedFormulas throws errors if oldName is empty - @MarkBaker [CodePlex #11401](https://phpexcel.codeplex.com/workitem/11401) +- parse_url() fails to parse path to an image in xlsx - @MarkBaker [CodePlex #11296](https://phpexcel.codeplex.com/workitem/11296) +- Workaround for iconv_substr() bug in PHP 5.2.0 - @Erik Tilt [CodePlex #10876](https://phpexcel.codeplex.com/workitem/10876) +- 1 pixel error for image width and height with PHPExcel_Writer_Excel5 - @Erik Tilt [CodePlex #10877](https://phpexcel.codeplex.com/workitem/10877) +- Fix to GEOMEAN() Statistical function - @MarkBaker +- setValue('-') and setValue('.') sets numeric 0 instead of 1-character string - @Erik Tilt [CodePlex #10884](https://phpexcel.codeplex.com/workitem/10884) +- Row height sometimes much too low after read with PHPExcel_Reader_Excel5 - @Erik Tilt [CodePlex #10885](https://phpexcel.codeplex.com/workitem/10885) +- Diagonal border. Miscellaneous missing support. - @Erik Tilt [CodePlex #10888](https://phpexcel.codeplex.com/workitem/10888) + - Constant PHPExcel_Style_Borders::DIAGONAL_BOTH added to support double-diagonal (cross) + - PHPExcel_Reader_Excel2007 not always reading diagonal borders (only recognizes 'true' and not '1') + - PHPExcel_Reader_Excel5 support for diagonal borders + - PHPExcel_Writer_Excel5 support for diagonal borders- Session bug: Fatal error: Call to a member function bindValue() on a non-object in ...\Classes\PHPExcel\Cell.php on line 217 - @Erik Tilt [CodePlex #10894](https://phpexcel.codeplex.com/workitem/10894) +- Colors messed up saving twice with same instance of PHPExcel_Writer_Excel5 (regression since 1.7.0) - @Erik Tilt [CodePlex #10896](https://phpexcel.codeplex.com/workitem/10896) +- Method PHPExcel_Worksheet::setDefaultStyle is not working - @Erik Tilt [CodePlex #10917](https://phpexcel.codeplex.com/workitem/10917) +- PHPExcel_Reader_CSV::canRead() sometimes says false when it shouldn't - @Erik Tilt [CodePlex #10897](https://phpexcel.codeplex.com/workitem/10897) +- Changes in workbook not picked up between two saves with PHPExcel_Writer_Excel2007 - @Erik Tilt [CodePlex #10922](https://phpexcel.codeplex.com/workitem/10922) +- Decimal and thousands separators missing in HTML and PDF output - @Erik Tilt [CodePlex #10913](https://phpexcel.codeplex.com/workitem/10913) +- Notices with PHPExcel_Reader_Excel5 and named array constants - @Erik Tilt [CodePlex #10936](https://phpexcel.codeplex.com/workitem/10936) +- Calculation engine limitation on 32-bit platform with integers > 2147483647 - @MarkBaker [CodePlex #10938](https://phpexcel.codeplex.com/workitem/10938) +- Shared(?) formulae containing absolute cell references not read correctly using Excel5 Reader - @Erik Tilt [CodePlex #10959](https://phpexcel.codeplex.com/workitem/10959) +- Warning messages with intersection operator involving single cell - @MarkBaker [CodePlex #10962](https://phpexcel.codeplex.com/workitem/10962) +- Infinite loop in Excel5 reader caused by zero-length string in SST - @Erik Tilt [CodePlex #10980](https://phpexcel.codeplex.com/workitem/10980) +- Remove unnecessary cell sorting to improve speed by approx. 18% in HTML and PDF writers - @Erik Tilt [CodePlex #10983](https://phpexcel.codeplex.com/workitem/10983) +- Cannot read A1 cell content - OO_Reader - @MarkBaker [CodePlex #10977](https://phpexcel.codeplex.com/workitem/10977) +- Transliteration failed, invalid encoding - @Erik Tilt [CodePlex #11000](https://phpexcel.codeplex.com/workitem/11000) + +## [1.7.1] - 2009-11-02 + +### General + +- ereg() function deprecated in PHP 5.3.0 - @Erik Tilt [CodePlex #10687](https://phpexcel.codeplex.com/workitem/10687) +- Writer Interface Inconsequence - setTempDir and setUseDiskCaching - @MarkBaker [CodePlex #10739](https://phpexcel.codeplex.com/workitem/10739) + +### Features + +- Upgrade to TCPDF 4.8.009 - @Erik Tilt +- Support for row and column styles (feature request) - @Erik Tilt + - Basic implementation for Excel2007/Excel5 reader/writer- Hyperlink to local file in Excel5 reader/writer - @Erik Tilt [CodePlex #10459](https://phpexcel.codeplex.com/workitem/10459) +- Color Tab (Color Sheet's name) - @MarkBaker [CodePlex #10472](https://phpexcel.codeplex.com/workitem/10472) +- Border style "double" support in PHPExcel_Writer_HTML - @Erik Tilt [CodePlex #10488](https://phpexcel.codeplex.com/workitem/10488) +- Multi-section number format support in HTML/PDF/CSV writers - @Erik Tilt [CodePlex #10492](https://phpexcel.codeplex.com/workitem/10492) +- Some additional performance tweaks in the calculation engine - @MarkBaker +- Fix result of DB() and DDB() Financial functions to 2dp when in Gnumeric Compatibility mode - @MarkBaker +- Added AMORDEGRC(), AMORLINC() and COUPNUM() Financial function (no validation of parameters yet) - @MarkBaker +- Improved accuracy of TBILLEQ(), TBILLPRICE() and TBILLYIELD() Financial functions when in Excel or Gnumeric mode - @MarkBaker +- Added INDIRECT() Lookup/Reference function (only supports full addresses at the moment) - @MarkBaker +- PHPExcel_Reader_CSV::canRead() improvements - @MarkBaker [CodePlex #10498](https://phpexcel.codeplex.com/workitem/10498) +- Input encoding option for PHPExcel_Reader_CSV - @Erik Tilt [CodePlex #10500](https://phpexcel.codeplex.com/workitem/10500) +- Colored number format support, e.g. [Red], in HTML/PDF output - @Erik Tilt [CodePlex #10493](https://phpexcel.codeplex.com/workitem/10493) +- Color Tab (Color Sheet's name) [Excel5 reader/writer support] - @Erik Tilt [CodePlex #10559](https://phpexcel.codeplex.com/workitem/10559) +- Initial version of SYLK (slk) and Excel 2003 XML Readers (Cell data and basic cell formatting) - @MarkBaker +- Initial version of Open Office Calc (ods) Reader (Cell data only) - @MarkBaker +- Initial use of "pass by reference" in the calculation engine for ROW() and COLUMN() Lookup/Reference functions - @MarkBaker +- COLUMNS() and ROWS() Lookup/Reference functions, and SUBSTITUTE() Text function - @MarkBaker [CodePlex #2346](https://phpexcel.codeplex.com/workitem/2346) +- AdvancedValueBinder(): Re-enable zero-padded string-to-number conversion, e.g '0004' -> 4 - @Erik Tilt [CodePlex #10502](https://phpexcel.codeplex.com/workitem/10502) +- Make PHP type match Excel datatype - @Erik Tilt [CodePlex #10600](https://phpexcel.codeplex.com/workitem/10600) +- Change first page number on header - @MarkBaker [CodePlex #10630](https://phpexcel.codeplex.com/workitem/10630) +- Applied patch 3941 - @MarkBaker +- Hidden sheets - @MB,ET [CodePlex #10745](https://phpexcel.codeplex.com/workitem/10745) +- mbstring fallback when iconv is broken - @Erik Tilt [CodePlex #10761](https://phpexcel.codeplex.com/workitem/10761) +- Note, can't yet handle comparison of two matrices - @MarkBaker +- Improved handling for validation and error trapping in a number of functions - @MarkBaker +- Improved support for fraction number formatting - @MarkBaker +- Support Reading CSV with Byte Order Mark (BOM) - @Erik Tilt [CodePlex #10455](https://phpexcel.codeplex.com/workitem/10455) + +### Bugfixes + +- addExternalSheet() at specified index - @Erik Tilt [CodePlex #10860](https://phpexcel.codeplex.com/workitem/10860) +- Named range can no longer be passed to worksheet->getCell() - @MarkBaker [CodePlex #10684](https://phpexcel.codeplex.com/workitem/10684) +- RichText HTML entities no longer working in PHPExcel 1.7.0 - @Erik Tilt [CodePlex #10455](https://phpexcel.codeplex.com/workitem/10455) +- Fit-to-width value of 1 is lost after read/write of Excel2007 spreadsheet [+ support for simultaneous scale/fitToPage] - @Erik Tilt +- Performance issue identified by profiling - @MarkBaker [CodePlex #10469](https://phpexcel.codeplex.com/workitem/10469) +- setSelectedCell is wrong - @Erik Tilt [CodePlex #10473](https://phpexcel.codeplex.com/workitem/10473) +- Images get squeezed/stretched with (Mac) Verdana 10 Excel files using Excel5 reader/writer - @Erik Tilt [CodePlex #10481](https://phpexcel.codeplex.com/workitem/10481) +- Error in argument count for DATEDIF() function - @MarkBaker [CodePlex #10482](https://phpexcel.codeplex.com/workitem/10482) +- updateFormulaReferences is buggy - @MarkBaker [CodePlex #10452](https://phpexcel.codeplex.com/workitem/10452) +- CellIterator returns null Cell if onlyExistingCells is set and key() is in use - @MarkBaker [CodePlex #10485](https://phpexcel.codeplex.com/workitem/10485) +- Wrong RegEx for parsing cell references in formulas - @MarkBaker [CodePlex #10453](https://phpexcel.codeplex.com/workitem/10453) +- Optimisation subverted to devastating effect if IterateOnlyExistingCells is clear - @MarkBaker [CodePlex #10486](https://phpexcel.codeplex.com/workitem/10486) +- Fatal error: Uncaught exception 'Exception' with message 'Unrecognized token 6C in formula'... with PHPExcel_Reader_Excel5 - @Erik Tilt [CodePlex #10494](https://phpexcel.codeplex.com/workitem/10494) +- Fractions stored as text are not treated as numbers by PHPExcel's calculation engine - @MarkBaker [CodePlex #10490](https://phpexcel.codeplex.com/workitem/10490) +- AutoFit (autosize) row height not working in PHPExcel_Writer_Excel5 - @Erik Tilt [CodePlex #10503](https://phpexcel.codeplex.com/workitem/10503) +- Fixed problem with null values breaking the calculation stack - @MarkBaker +- Date number formats sometimes fail with PHPExcel_Style_NumberFormat::toFormattedString, e.g. [$-40047]mmmm d yyyy - @Erik Tilt [CodePlex #10524](https://phpexcel.codeplex.com/workitem/10524) +- Fixed minor problem with DATEDIFF YM calculation - @MarkBaker +- Applied patch 3695 - @MarkBaker +- setAutosize() and Date cells not working properly - @Erik Tilt [CodePlex #10536](https://phpexcel.codeplex.com/workitem/10536) +- Time value hour offset in output by HTML/PDF/CSV writers (system timezone problem) - @Erik Tilt [CodePlex #10556](https://phpexcel.codeplex.com/workitem/10556) +- Control characters 0x14-0x1F are not treated by PHPExcel - @Erik Tilt [CodePlex #10558](https://phpexcel.codeplex.com/workitem/10558) +- PHPExcel_Writer_Excel5 not working when open_basedir restriction is in effect - @Erik Tilt [CodePlex #10560](https://phpexcel.codeplex.com/workitem/10560) +- IF formula calculation problem in PHPExcel 1.7.0 (string comparisons) - @MarkBaker [CodePlex #10563](https://phpexcel.codeplex.com/workitem/10563) +- Improved CODE() Text function result for UTF-8 characters - @MarkBaker +- Empty rows are collapsed with HTML/PDF writer - @Erik Tilt [CodePlex #10568](https://phpexcel.codeplex.com/workitem/10568) +- Gaps between rows in output by PHPExcel_Writer_PDF (Upgrading to TCPDF 4.7.003) - @Erik Tilt [CodePlex #10569](https://phpexcel.codeplex.com/workitem/10569) +- Problem reading formulas (Excel5 reader problem with "fake" shared formulas) - @Erik Tilt [CodePlex #10575](https://phpexcel.codeplex.com/workitem/10575) +- Error type in formula: "_raiseFormulaError message is Formula Error: An unexpected error occured" - @MarkBaker [CodePlex #10588](https://phpexcel.codeplex.com/workitem/10588) +- Miscellaneous column width problems in Excel5/Excel2007 writer - @Erik Tilt [CodePlex #10599](https://phpexcel.codeplex.com/workitem/10599) +- Reader/Excel5 'Unrecognized token 2D in formula' in latest version - @Erik Tilt [CodePlex #10615](https://phpexcel.codeplex.com/workitem/10615) +- on php 5.3 PHPExcel 1.7 Excel 5 reader fails in _getNextToken, token = 2C, throws exception - @Erik Tilt [CodePlex #10623](https://phpexcel.codeplex.com/workitem/10623) +- Fatal error when altering styles after workbook has been saved - @Erik Tilt [CodePlex #10617](https://phpexcel.codeplex.com/workitem/10617) +- Images vertically stretched or squeezed when default font size is changed (PHPExcel_Writer_Excel5) - @Erik Tilt [CodePlex #10661](https://phpexcel.codeplex.com/workitem/10661) +- Styles not read in "manipulated" Excel2007 workbook - @Erik Tilt [CodePlex #10676](https://phpexcel.codeplex.com/workitem/10676) +- Windows 7 says corrupt file by PHPExcel_Writer_Excel5 when opening in Excel - @Erik Tilt [CodePlex #10059](https://phpexcel.codeplex.com/workitem/10059) +- Calculations sometimes not working with cell references to other sheets - @MarkBaker [CodePlex #10708](https://phpexcel.codeplex.com/workitem/10708) +- Problem with merged cells after insertNewRowBefore() - @Erik Tilt [CodePlex #10706](https://phpexcel.codeplex.com/workitem/10706) +- Applied patch 4023 - @MarkBaker +- Fix to SUMIF() and COUNTIF() Statistical functions for when condition is a match against a string value - @MarkBaker +- PHPExcel_Cell::coordinateFromString should throw exception for bad string parameter - @Erik Tilt [CodePlex #10721](https://phpexcel.codeplex.com/workitem/10721) +- EucrosiaUPC (Thai font) not working with PHPExcel_Writer_Excel5 - @Erik Tilt [CodePlex #10723](https://phpexcel.codeplex.com/workitem/10723) +- Improved the return of calculated results when the result value is an array - @MarkBaker +- Allow calculation engine to support Functions prefixed with @ within formulae - @MarkBaker +- Intersection operator (space operator) fatal error with calculation engine - @MarkBaker [CodePlex #10632](https://phpexcel.codeplex.com/workitem/10632) +- Chinese, Japanese, Korean characters show as squares in PDF - @Erik Tilt [CodePlex #10742](https://phpexcel.codeplex.com/workitem/10742) +- sheet title allows invalid characters - @Erik Tilt [CodePlex #10756](https://phpexcel.codeplex.com/workitem/10756) +- Sheet!$A$1 as function argument in formula causes infinite loop in Excel5 writer - @Erik Tilt [CodePlex #10757](https://phpexcel.codeplex.com/workitem/10757) +- Cell range involving name not working with calculation engine - Modified calculation parser to handle range operator (:), but doesn't currently handle worksheet references with spaces or other non-alphameric characters, or trap erroneous references - @MarkBaker [CodePlex #10740](https://phpexcel.codeplex.com/workitem/10740) +- DATE function problem with calculation engine (says too few arguments given) - @MarkBaker [CodePlex #10798](https://phpexcel.codeplex.com/workitem/10798) +- Blank cell can cause wrong calculated value - @MarkBaker [CodePlex #10799](https://phpexcel.codeplex.com/workitem/10799) +- Modified ROW() and COLUMN() Lookup/Reference Functions to return an array when passed a cell range, plus some additional work on INDEX() - @MarkBaker +- Images not showing in Excel 97 using PHPExcel_Writer_Excel5 (patch by Jordi Gutiérrez Hermoso) - @Erik Tilt [CodePlex #10817](https://phpexcel.codeplex.com/workitem/10817) +- When figures are contained in the excel sheet, Reader was stopped - @Erik Tilt [CodePlex #10785](https://phpexcel.codeplex.com/workitem/10785) +- Formulas changed after insertNewRowBefore() - @MarkBaker [CodePlex #10818](https://phpexcel.codeplex.com/workitem/10818) +- Cell range row offset problem with shared formulas using PHPExcel_Reader_Excel5 - @Erik Tilt [CodePlex #10825](https://phpexcel.codeplex.com/workitem/10825) +- Warning: Call-time pass-by-reference has been deprecated - @MarkBaker [CodePlex #10832](https://phpexcel.codeplex.com/workitem/10832) +- Image should "Move but don't size with cells" instead of "Move and size with cells" with PHPExcel_Writer_Excel5 - @Erik Tilt [CodePlex #10849](https://phpexcel.codeplex.com/workitem/10849) +- Opening a Excel5 generated XLS in Excel 2007 results in header/footer entry not showing on input - @Erik Tilt [CodePlex #10856](https://phpexcel.codeplex.com/workitem/10856) +- addExternalSheet() not returning worksheet - @Erik Tilt [CodePlex #10859](https://phpexcel.codeplex.com/workitem/10859) +- Invalid results in formulas with named ranges - @MarkBaker [CodePlex #10629](https://phpexcel.codeplex.com/workitem/10629) + +## [1.7.0] - 2009-08-10 + +### General + +- Expand documentation: Number formats - @Erik Tilt +- Class 'PHPExcel_Cell_AdvancedValueBinder' not found - @Erik Tilt + +### Features + +- Change return type of date functions to PHPExcel_Calculation_Functions::RETURNDATE_EXCEL - @MarkBaker +- New RPN and stack-based calculation engine for improved performance of formula calculation - @MarkBaker + - Faster (anything between 2 and 12 times faster than the old parser, depending on the complexity and nature of the formula) + - Significantly more memory efficient when formulae reference cells across worksheets + - Correct behaviour when referencing Named Ranges that exist on several worksheets + - Support for Excel ^ (Exponential) and % (Percentage) operators + - Support for matrices within basic arithmetic formulae (e.g. ={1,2,3;4,5,6;7,8,9}/2) + - Better trapping/handling of NaN and infinity results (return #NUM! error) + - Improved handling of empty parameters for Excel functions + - Optional logging of calculation steps- New calculation engine can be accessed independently of workbooks (for use as a standalone calculator) - @MarkBaker +- Implement more Excel calculation functions - @MarkBaker + - Initial implementation of the COUNTIF() and SUMIF() Statistical functions + - Added ACCRINT() Financial function- Modifications to number format handling for dddd and ddd masks in dates, use of thousand separators even when locale only implements it for money, and basic fraction masks (0 ?/? and ?/?) - @MarkBaker +- Support arbitrary fixed number of decimals in PHPExcel_Style_NumberFormat::toFormattedString() - @Erik Tilt +- Improving performance and memory on data dumps - @Erik Tilt + - Various style optimizations (merging from branch wi6857-memory) + - Moving hyperlink and dataValidation properties from cell to worksheet for lower PHP memory usage- Provide fluent interfaces where possible - @MarkBaker +- Make easy way to apply a border to a rectangular selection - @Erik Tilt +- Support for system window colors in PHPExcel_Reader_Excel5 - @Erik Tilt +- Horizontal center across selection - @Erik Tilt +- Merged cells record, write to full record size in PHPExcel_Writer_Excel5 - @Erik Tilt +- Add page break between sheets in exported PDF - @MarkBaker +- Sanitization of UTF-8 input for cell values - @Erik Tilt +- Read cached calculated value with PHPExcel_Reader_Excel5 - @Erik Tilt +- Miscellaneous CSS improvements for PHPExcel_Writer_HTML - @Erik Tilt +- getProperties: setCompany feature request - @Erik Tilt +- Insert worksheet at a specified index - @MarkBaker +- Change worksheet index - @MarkBaker +- Readfilter for CSV reader - @MarkBaker +- Check value of mbstring.func_overload when saving with PHPExcel_Writer_Excel5 - @Erik Tilt [CodePlex #10172](https://phpexcel.codeplex.com/workitem/10172) +- Eliminate dependency of an include path pointing to class directory - @Erik Tilt [CodePlex #10251](https://phpexcel.codeplex.com/workitem/10251) +- Method for getting the correct reader for a certain file (contribution) - @Erik Tilt [CodePlex #10292](https://phpexcel.codeplex.com/workitem/10292) +- Choosing specific row in fromArray method - @Erik Tilt [CodePlex #10287](https://phpexcel.codeplex.com/workitem/10287) +- Shared formula support in PHPExcel_Reader_Excel5 - @Erik Tilt [CodePlex #10319](https://phpexcel.codeplex.com/workitem/10319) + +### Bugfixes + +- Right-to-left column direction in worksheet - @MB,ET [CodePlex #10345](https://phpexcel.codeplex.com/workitem/10345) +- PHPExcel_Reader_Excel5 not reading PHPExcel_Style_NumberFormat::FORMAT_NUMBER ('0') - @Erik Tilt +- Fractional row height in locale other than English results in corrupt output using PHPExcel_Writer_Excel2007 - @Erik Tilt +- Fractional (decimal) numbers not inserted correctly when locale is other than English - @Erik Tilt +- Fractional calculated value in locale other than English results in corrupt output using PHPExcel_Writer_Excel2007 - @Erik Tilt +- Locale aware decimal and thousands separator in exported formats HTML, CSV, PDF - @Erik Tilt +- Cannot Add Image with Space on its Name - @MarkBaker +- Black line at top of every page in output by PHPExcel_Writer_PDF - @Erik Tilt +- Border styles and border colors not showing in HTML output (regression since 1.6.4) - @Erik Tilt +- Hidden screen gridlines setting in worksheet not read by PHPExcel_Reader_Excel2007 - @Erik Tilt +- Some valid sheet names causes corrupt output using PHPExcel_Writer_Excel2007 - @MarkBaker +- More than 32,767 characters in a cell gives corrupt Excel file - @Erik Tilt +- Images not getting copyied with the ->copy() function - @Erik Tilt +- Bad calculation of column width setAutoSize(true) function - @Erik Tilt +- Dates are sometimes offset by 1 day in output by HTML and PDF writers depending on system timezone setting - @Erik Tilt +- Wingdings symbol fonts not working with PHPExcel_Writer_Excel5 - @Erik Tilt [CodePlex #10003](https://phpexcel.codeplex.com/workitem/10003) +- White space string prefix stripped by PHPExcel_Writer_Excel2007 - @MarkBaker [CodePlex #10010](https://phpexcel.codeplex.com/workitem/10010) +- The name of the Workbook stream MUST be "Workbook", not "Book" - @Erik Tilt [CodePlex #10023](https://phpexcel.codeplex.com/workitem/10023) +- Avoid message "Microsoft Excel recalculates formulas..." when closing xls file from Excel - @Erik Tilt [CodePlex #10030](https://phpexcel.codeplex.com/workitem/10030) +- Non-unique newline representation causes problems with LEN formula - @Erik Tilt [CodePlex #10031](https://phpexcel.codeplex.com/workitem/10031) +- Newline in cell not showing with PHPExcel_Writer_HTML and PHPExcel_Writer_PDF - @Erik Tilt [CodePlex #10033](https://phpexcel.codeplex.com/workitem/10033) +- Rich-Text strings get prefixed by   when output by HTML writer - @Erik Tilt [CodePlex #10046](https://phpexcel.codeplex.com/workitem/10046) +- Leading spaces do not appear in output by HTML/PDF writers - @Erik Tilt [CodePlex #10052](https://phpexcel.codeplex.com/workitem/10052) +- Empty Apache POI-generated file can not be read - @MarkBaker [CodePlex #10061](https://phpexcel.codeplex.com/workitem/10061) +- Column width not scaling correctly with font size in HTML and PDF writers - @Erik Tilt [CodePlex #10068](https://phpexcel.codeplex.com/workitem/10068) +- Inaccurate row heights with HTML writer - @Erik Tilt [CodePlex #10069](https://phpexcel.codeplex.com/workitem/10069) +- Reference helper - @MarkBaker +- Excel 5 Named ranges should not be local to the worksheet, but accessible from all worksheets - @MarkBaker +- Row heights are ignored by PHPExcel_Writer_PDF - @Erik Tilt [CodePlex #10088](https://phpexcel.codeplex.com/workitem/10088) +- Write raw XML - @MarkBaker +- removeRow(), removeColumn() not always clearing cell values - @Erik Tilt [CodePlex #10098](https://phpexcel.codeplex.com/workitem/10098) +- Problem reading certain hyperlink records with PHPExcel_Reader_Excel5 - @Erik Tilt [CodePlex #10142](https://phpexcel.codeplex.com/workitem/10142) +- Hyperlink cell range read failure with PHPExcel_Reader_Excel2007 - @Erik Tilt [CodePlex #10143](https://phpexcel.codeplex.com/workitem/10143) +- 'Column string index can not be empty.' - @MarkBaker [CodePlex #10149](https://phpexcel.codeplex.com/workitem/10149) +- getHighestColumn() sometimes says there are 256 columns with PHPExcel_Reader_Excel5 - @Erik Tilt [CodePlex #10204](https://phpexcel.codeplex.com/workitem/10204) +- extractSheetTitle fails when sheet title contains exclamation mark (!) - @Erik Tilt [CodePlex #10220](https://phpexcel.codeplex.com/workitem/10220) +- setTitle() sometimes erroneously appends integer to sheet name - @Erik Tilt [CodePlex #10221](https://phpexcel.codeplex.com/workitem/10221) +- Mac BIFF5 Excel file read failure (missing support for Mac OS Roman character set) - @Erik Tilt [CodePlex #10229](https://phpexcel.codeplex.com/workitem/10229) +- BIFF5 header and footer incorrectly read by PHPExcel_Reader_Excel5 - @Erik Tilt [CodePlex #10230](https://phpexcel.codeplex.com/workitem/10230) +- iconv notices when reading hyperlinks with PHPExcel_Reader_Excel5 - @Erik Tilt [CodePlex #10259](https://phpexcel.codeplex.com/workitem/10259) +- Excel5 reader OLE read failure with small Mac BIFF5 Excel files - @Erik Tilt [CodePlex #10252](https://phpexcel.codeplex.com/workitem/10252) +- Problem in reading formula : IF( IF ) with PHPExcel_Reader_Excel5 - @Erik Tilt [CodePlex #10272](https://phpexcel.codeplex.com/workitem/10272) +- Error reading formulas referencing external sheets with PHPExcel_Reader_Excel5 - @Erik Tilt [CodePlex #10274](https://phpexcel.codeplex.com/workitem/10274) +- Image horizontally stretched when default font size is increased (PHPExcel_Writer_Excel5) - @Erik Tilt [CodePlex #10291](https://phpexcel.codeplex.com/workitem/10291) +- Undefined offset in Reader\Excel5.php on line 3572 - @Erik Tilt [CodePlex #10333](https://phpexcel.codeplex.com/workitem/10333) +- PDF output different then XLS (copied data) - @MarkBaker [CodePlex #10340](https://phpexcel.codeplex.com/workitem/10340) +- Internal hyperlinks with UTF-8 sheet names not working in PHPExcel_Writer_Excel5 - @Erik Tilt [CodePlex #10352](https://phpexcel.codeplex.com/workitem/10352) +- String shared formula result read error with PHPExcel_Reader_Excel5 - @Erik Tilt [CodePlex #10361](https://phpexcel.codeplex.com/workitem/10361) +- Uncaught exception 'Exception' with message 'Valid scale is between 10 and 400.' in Classes/PHPExcel/Worksheet/PageSetup.php:338 - @Erik Tilt [CodePlex #10363](https://phpexcel.codeplex.com/workitem/10363) +- Using setLoadSheetsOnly fails if you do not use setReadDataOnly(true) and sheet is not the first sheet - @Erik Tilt [CodePlex #10355](https://phpexcel.codeplex.com/workitem/10355) +- getCalculatedValue() sometimes incorrect with IF formula and 0-values - @MarkBaker [CodePlex #10362](https://phpexcel.codeplex.com/workitem/10362) +- Excel Reader 2007 problem with "shared" formulae when "master" is an error - @MarkBaker +- Named Range Bug, using the same range name on different worksheets - @MarkBaker +- Java code in JAMA classes - @MarkBaker +- getCalculatedValue() not working with some formulas involving error types - @MarkBaker +- evaluation of both return values in an IF() statement returning an error if either result was an error, irrespective of the IF evaluation - @MarkBaker +- Power in formulas: new calculation engine no longer treats ^ as a bitwise XOR operator - @MarkBaker +- Bugfixes and improvements to many of the Excel functions in PHPExcel - @MarkBaker + - Added optional "places" parameter in the BIN2HEX(), BIN2OCT, DEC2BIN(), DEC2OCT(), DEC2HEX(), HEX2BIN(), HEX2OCT(), OCT2BIN() and OCT2HEX() Engineering Functions + - Trap for unbalanced matrix sizes in MDETERM() and MINVERSE() Mathematic and Trigonometric functions + - Fix for default characters parameter value for LEFT() and RIGHT() Text functions + - Fix for GCD() and LCB() Mathematical functions when the parameters include a zero (0) value + - Fix for BIN2OCT() Engineering Function for 2s complement values (which were returning hex values) + - Fix for BESSELK() and BESSELY() Engineering functions + - Fix for IMDIV() Engineering Function when result imaginary component is positive (wasn't setting the sign) + - Fix for ERF() Engineering Function when called with an upper limit value for the integration + - Fix to DATE() Date/Time Function for year value of 0 + - Set ISPMT() function as category FINANCIAL + - Fix for DOLLARDE() and DOLLARFR() Financial functions + - Fix to EFFECT() Financial function (treating $nominal_rate value as a variable name rather than a value) + - Fix to CRITBINOM() Statistical function (CurrentValue and EssentiallyZero treated as constants rather than variables) + - Note that an Error in the function logic can still lead to a permanent loop + - Fix to MOD() Mathematical function to work with floating point results + - Fix for QUOTIENT() Mathematical function + - Fix to HOUR(), MINUTE() and SECOND() Date/Time functions to return an error when passing in a floating point value of 1.0 or greater, or less than 0 + - LOG() Function now correctly returns base-10 log when called with only one parameter, rather than the natural log as the default base + - Modified text functions to handle multibyte character set (UTF-8). + +## [1.6.7] - 2009-04-22 + +### BREAKING CHANGE + +In previous versions of PHPExcel up to and including 1.6.6, +when a cell had a date-like number format code, it was possible to enter a date +directly using an integer PHP-time without converting to Excel date format. +Starting with PHPExcel 1.6.7 this is no longer supported. Refer to the developer +documentation for more information on entering dates into a cell. + +### General + +- Deprecate misspelled setStriketrough() and getStriketrough() methods - @MarkBaker [CodePlex #9416](https://phpexcel.codeplex.com/workitem/9416) + +### Features + +- Performance improvement when saving file - @MarkBaker [CodePlex #9526](https://phpexcel.codeplex.com/workitem/9526) +- Check that sheet title has maximum 31 characters - @MarkBaker [CodePlex #9598](https://phpexcel.codeplex.com/workitem/9598) +- True support for Excel built-in number format codes - @MB, ET [CodePlex #9631](https://phpexcel.codeplex.com/workitem/9631) +- Ability to read defect BIFF5 Excel file without CODEPAGE record - @Erik Tilt [CodePlex #9683](https://phpexcel.codeplex.com/workitem/9683) +- Auto-detect which reader to invoke - @MarkBaker [CodePlex #9701](https://phpexcel.codeplex.com/workitem/9701) +- Deprecate insertion of dates using PHP-time (Unix time) [request for removal of feature] - @Erik Tilt [CodePlex #9214](https://phpexcel.codeplex.com/workitem/9214) +- Support for entering time values like '9:45', '09:45' using AdvancedValueBinder - @Erik Tilt [CodePlex #9747](https://phpexcel.codeplex.com/workitem/9747) + +### Bugfixes + +- DataType dependent horizontal alignment in HTML and PDF writer - @Erik Tilt [CodePlex #9797](https://phpexcel.codeplex.com/workitem/9797) +- Cloning data validation object causes script to stop - @MarkBaker [CodePlex #9375](https://phpexcel.codeplex.com/workitem/9375) +- Simultaneous repeating rows and repeating columns not working with PHPExcel_Writer_Excel5 - @Erik Tilt [CodePlex #9400](https://phpexcel.codeplex.com/workitem/9400) +- Simultaneous repeating rows and repeating columns not working with PHPExcel_Writer_Excel2007 - @MarkBaker [CodePlex #9399](https://phpexcel.codeplex.com/workitem/9399) +- Row outline level not working with PHPExcel_Writer_Excel5 - @Erik Tilt [CodePlex #9437](https://phpexcel.codeplex.com/workitem/9437) +- Occasional notices with PHPExcel_Reader_Excel5 when Excel file contains drawing elements - @Erik Tilt [CodePlex #9452](https://phpexcel.codeplex.com/workitem/9452) +- PHPExcel_Reader_Excel5 fails as a whole when workbook contains images other than JPEG/PNG - @Erik Tilt [CodePlex #9453](https://phpexcel.codeplex.com/workitem/9453) +- Excel5 writer checks for iconv but does not necessarily use it - @Erik Tilt [CodePlex #9444](https://phpexcel.codeplex.com/workitem/9444) +- Altering a style on copied worksheet alters also the original - @Erik Tilt [CodePlex #9463](https://phpexcel.codeplex.com/workitem/9463) +- Formulas are incorrectly updated when a sheet is renamed - @MarkBaker [CodePlex #9480](https://phpexcel.codeplex.com/workitem/9480) +- PHPExcel_Worksheet::extractSheetTitle not treating single quotes correctly - @MarkBaker [CodePlex #9513](https://phpexcel.codeplex.com/workitem/9513) +- PHP Warning raised in function array_key_exists - @MarkBaker [CodePlex #9477](https://phpexcel.codeplex.com/workitem/9477) +- getAlignWithMargins() gives wrong value when using PHPExcel_Reader_Excel2007 - @MarkBaker [CodePlex #9599](https://phpexcel.codeplex.com/workitem/9599) +- getScaleWithDocument() gives wrong value when using PHPExcel_Reader_Excel2007 - @MarkBaker [CodePlex #9600](https://phpexcel.codeplex.com/workitem/9600) +- PHPExcel_Reader_Excel2007 not reading the first user-defined number format - @MarkBaker [CodePlex #9630](https://phpexcel.codeplex.com/workitem/9630) +- Print area converted to uppercase after read with PHPExcel_Reader_Excel2007 - @MarkBaker [CodePlex #9647](https://phpexcel.codeplex.com/workitem/9647) +- Incorrect reading of scope for named range using PHPExcel_Reader_Excel2007 - @MarkBaker [CodePlex #9661](https://phpexcel.codeplex.com/workitem/9661) +- Error with pattern (getFillType) and rbg (getRGB) - @MarkBaker [CodePlex #9690](https://phpexcel.codeplex.com/workitem/9690) +- AdvancedValueBinder affected by system timezone setting when inserting date values - @Erik Tilt [CodePlex #9712](https://phpexcel.codeplex.com/workitem/9712) +- PHPExcel_Reader_Excel2007 not reading value of active sheet index - @Erik Tilt [CodePlex #9743](https://phpexcel.codeplex.com/workitem/9743) +- getARGB() sometimes returns SimpleXMLElement object instead of string with PHPExcel_Reader_Excel2007 - @Erik Tilt [CodePlex #9742](https://phpexcel.codeplex.com/workitem/9742) +- Negative image offset causes defects in 14excel5.xls and 20readexcel5.xlsx - @Erik Tilt [CodePlex #9731](https://phpexcel.codeplex.com/workitem/9731) +- HTML & PDF Writer not working with mergeCells (regression since 1.6.5) - @Erik Tilt [CodePlex #9758](https://phpexcel.codeplex.com/workitem/9758) +- Too wide columns with HTML and PDF writer - @Erik Tilt [CodePlex #9774](https://phpexcel.codeplex.com/workitem/9774) +- PDF and cyrillic fonts - @MarkBaker [CodePlex #9775](https://phpexcel.codeplex.com/workitem/9775) +- Percentages not working correctly with HTML and PDF writers (shows 0.25% instead of 25%) - @Erik Tilt [CodePlex #9793](https://phpexcel.codeplex.com/workitem/9793) +- PHPExcel_Writer_HTML creates extra borders around cell contents using setUseInlineCss(true) - @Erik Tilt [CodePlex #9791](https://phpexcel.codeplex.com/workitem/9791) +- Problem with text wrap + merged cells in HTML and PDF writer - @Erik Tilt [CodePlex #9784](https://phpexcel.codeplex.com/workitem/9784) +- Adjacent path separators in include_path causing IOFactory to violate open_basedir restriction - @Erik Tilt [CodePlex #9814](https://phpexcel.codeplex.com/workitem/9814) + +## [1.6.6] - 2009-03-02 + +### General + +- Improve support for built-in number formats in PHPExcel_Reader_Excel2007 - @MarkBaker [CodePlex #9102](https://phpexcel.codeplex.com/workitem/9102) +- Source files are in both UNIX and DOS formats - changed to UNIX - @Erik Tilt [CodePlex #9281](https://phpexcel.codeplex.com/workitem/9281) + +### Features + +- Update documentation: Which language to write formulas in? - @MarkBaker [CodePlex #9338](https://phpexcel.codeplex.com/workitem/9338) +- Ignore DEFCOLWIDTH records with value 8 in PHPExcel_Reader_Excel5 - @Erik Tilt [CodePlex #8817](https://phpexcel.codeplex.com/workitem/8817) +- Support for width, height, offsetX, offsetY for images in PHPExcel_Reader_Excel5 - @Erik Tilt [CodePlex #8847](https://phpexcel.codeplex.com/workitem/8847) +- Disk Caching in specific folder - @MarkBaker [CodePlex #8870](https://phpexcel.codeplex.com/workitem/8870) +- Added SUMX2MY2, SUMX2PY2, SUMXMY2, MDETERM and MINVERSE Mathematical and Trigonometric Functions - @MarkBaker [CodePlex #2346](https://phpexcel.codeplex.com/workitem/2346) +- Added CONVERT Engineering Function - @MarkBaker [CodePlex #2346](https://phpexcel.codeplex.com/workitem/2346) +- Added DB, DDB, DISC, DOLLARDE, DOLLARFR, INTRATE, IPMT, PPMT, PRICEDISC, PRICEMAT and RECEIVED Financial Functions - @MarkBaker [CodePlex #2346](https://phpexcel.codeplex.com/workitem/2346) +- Added ACCRINTM, CUMIPMT, CUMPRINC, TBILLEQ, TBILLPRICE, TBILLYIELD, YIELDDISC and YIELDMAT Financial Functions - @MarkBaker [CodePlex #2346](https://phpexcel.codeplex.com/workitem/2346) +- Added DOLLAR Text Function - @MarkBaker [CodePlex #2346](https://phpexcel.codeplex.com/workitem/2346) +- Added CORREL, COVAR, FORECAST, INTERCEPT, RSQ, SLOPE and STEYX Statistical Functions - @MarkBaker [CodePlex #2346](https://phpexcel.codeplex.com/workitem/2346) +- Added PEARSON Statistical Functions as a synonym for CORREL - @MarkBaker [CodePlex #2346](https://phpexcel.codeplex.com/workitem/2346) +- Added LINEST, LOGEST (currently only valid for stats = false), TREND and GROWTH Statistical Functions - @MarkBaker [CodePlex #2346](https://phpexcel.codeplex.com/workitem/2346) +- Added RANK and PERCENTRANK Statistical Functions - @MarkBaker [CodePlex #2346](https://phpexcel.codeplex.com/workitem/2346) +- Added ROMAN Mathematical Function (Classic form only) - @MarkBaker [CodePlex #2346](https://phpexcel.codeplex.com/workitem/2346) +- Update documentation to show example of getCellByColumnAndRow($col, $row) - @MarkBaker [CodePlex #8931](https://phpexcel.codeplex.com/workitem/8931) +- Implement worksheet, row and cell iterators - @MarkBaker [CodePlex #8770](https://phpexcel.codeplex.com/workitem/8770) +- Support for arbitrary defined names (named range) - @MarkBaker [CodePlex #9001](https://phpexcel.codeplex.com/workitem/9001) +- Update formulas when sheet title / named range title changes - @MB, ET [CodePlex #9016](https://phpexcel.codeplex.com/workitem/9016) +- Ability to read cached calculated value - @MarkBaker [CodePlex #9103](https://phpexcel.codeplex.com/workitem/9103) +- Support for Excel 1904 calendar date mode (Mac) - @MBaker, ET [CodePlex #8483](https://phpexcel.codeplex.com/workitem/8483) +- PHPExcel_Writer_Excel5 improvements writing shared strings table - @Erik Tilt [CodePlex #9194](https://phpexcel.codeplex.com/workitem/9194) +- PHPExcel_Writer_Excel5 iconv fallback when mbstring extension is not enabled - @Erik Tilt [CodePlex #9248](https://phpexcel.codeplex.com/workitem/9248) +- UTF-8 support in font names in PHPExcel_Writer_Excel5 - @Erik Tilt [CodePlex #9253](https://phpexcel.codeplex.com/workitem/9253) +- Implement value binding architecture - @MarkBaker [CodePlex #9215](https://phpexcel.codeplex.com/workitem/9215) +- PDF writer not working with UTF-8 - @MarkBaker [CodePlex #6742](https://phpexcel.codeplex.com/workitem/6742) + +### Bugfixes + +- Eliminate duplicate style entries in multisheet workbook written by PHPExcel_Writer_Excel5 - @Erik Tilt [CodePlex #9355](https://phpexcel.codeplex.com/workitem/9355) +- Redirect to client browser fails due to trailing white space in class definitions - @Erik Tilt [CodePlex #8810](https://phpexcel.codeplex.com/workitem/8810) +- Spurious column dimension element introduced in blank worksheet after using PHPExcel_Writer_Excel2007 - @MarkBaker [CodePlex #8816](https://phpexcel.codeplex.com/workitem/8816) +- Image gets slightly narrower than expected when using PHPExcel_Writer_Excel5 - @Erik Tilt [CodePlex #8830](https://phpexcel.codeplex.com/workitem/8830) +- Image laid over non-visible row gets squeezed in height when using PHPExcel_Writer_Excel5 - @Erik Tilt [CodePlex #8831](https://phpexcel.codeplex.com/workitem/8831) +- PHPExcel_Reader_Excel5 fails when there are 10 or more images in the workbook - @Erik Tilt [CodePlex #8860](https://phpexcel.codeplex.com/workitem/8860) +- Different header/footer images in different sheets not working with PHPExcel_Writer_Excel2007 - @MarkBaker [CodePlex #8909](https://phpexcel.codeplex.com/workitem/8909) +- Fractional seconds disappear when using PHPExcel_Reader_Excel2007 and PHPExcel_Reader_Excel5 - @MB, ET [CodePlex #8924](https://phpexcel.codeplex.com/workitem/8924) +- Images not showing in OpenOffice when using PHPExcel_Writer_Excel5 - @Erik Tilt [CodePlex #7994](https://phpexcel.codeplex.com/workitem/7994) +- Images not showing on print using PHPExcel_Writer_Excel5 - @Erik Tilt [CodePlex #9047](https://phpexcel.codeplex.com/workitem/9047) +- PHPExcel_Writer_Excel5 maximum allowed record size 4 bytes too short - @Erik Tilt [CodePlex #9085](https://phpexcel.codeplex.com/workitem/9085) +- Not numeric strings are formatted as dates and numbers using worksheet's toArray method - @MarkBaker [CodePlex #9119](https://phpexcel.codeplex.com/workitem/9119) +- Excel5 simple formula parsing error - @Erik Tilt [CodePlex #9132](https://phpexcel.codeplex.com/workitem/9132) +- Problems writing dates with CSV - @Erik Tilt [CodePlex #9206](https://phpexcel.codeplex.com/workitem/9206) +- PHPExcel_Reader_Excel5 reader fails with fatal error when reading group shapes - @Erik Tilt [CodePlex #9203](https://phpexcel.codeplex.com/workitem/9203) +- PHPExcel_Writer_Excel5 fails completely when workbook contains more than 57 colors - @Erik Tilt [CodePlex #9231](https://phpexcel.codeplex.com/workitem/9231) +- PHPExcel_Writer_PDF not compatible with autoload - @Erik Tilt [CodePlex #9244](https://phpexcel.codeplex.com/workitem/9244) +- Fatal error: Call to a member function getNestingLevel() on a non-object in PHPExcel/Reader/Excel5.php on line 690 - @Erik Tilt [CodePlex #9250](https://phpexcel.codeplex.com/workitem/9250) +- Notices when running test 04printing.php on PHP 5.2.8 - @MarkBaker [CodePlex #9246](https://phpexcel.codeplex.com/workitem/9246) +- insertColumn() spawns creation of spurious RowDimension - @MarkBaker [CodePlex #9294](https://phpexcel.codeplex.com/workitem/9294) +- Fix declarations for methods in extended Trend classes - @MarkBaker [CodePlex #9296](https://phpexcel.codeplex.com/workitem/9296) +- Fix to parameters for the FORECAST Statistical Function - @MarkBaker [CodePlex #2346](https://phpexcel.codeplex.com/workitem/2346) +- PDF writer problems with cell height and text wrapping - @MarkBaker [CodePlex #7083](https://phpexcel.codeplex.com/workitem/7083) +- Fix test for calculated value in case the returned result is an array - @MarkBaker +- Column greater than 256 results in corrupt Excel file using PHPExcel_Writer_Excel5 - @Erik Tilt +- Excel Numberformat 0.00 results in non internal decimal places values in toArray() Method - @MarkBaker [CodePlex #9351](https://phpexcel.codeplex.com/workitem/9351) +- setAutoSize not taking into account text rotation - @MB,ET [CodePlex #9356](https://phpexcel.codeplex.com/workitem/9356) +- Call to undefined method PHPExcel_Worksheet_MemoryDrawing::getPath() in PHPExcel/Writer/HTML.php - @Erik Tilt [CodePlex #9372](https://phpexcel.codeplex.com/workitem/9372) + +## [1.6.5] - 2009-01-05 + +### General + +- Applied patch 2063 - @MarkBaker +- Optimise Shared Strings - @MarkBaker +- Optimise Cell Sorting - @MarkBaker +- Optimise Style Hashing - @MarkBaker +- UTF-8 enhancements - @Erik Tilt +- PHPExcel_Writer_HTML validation errors against strict HTML 4.01 / CSS 2.1 - @Erik Tilt +- Documented work items 6203 and 8110 in manual - @MarkBaker +- Restructure package hierachy so classes can be found more easily in auto-generated API (from work item 8468) - @Erik Tilt + +### Features + +- Redirect output to a client's browser: Update recommendation in documentation - @MarkBaker [CodePlex #8806](https://phpexcel.codeplex.com/workitem/8806) +- PHPExcel_Reader_Excel5 support for print gridlines - @Erik Tilt [CodePlex #7897](https://phpexcel.codeplex.com/workitem/7897) +- Screen gridlines support in Excel5 reader/writer - @Erik Tilt [CodePlex #7899](https://phpexcel.codeplex.com/workitem/7899) +- Option for adding image to spreadsheet from image resource in memory - @MB, ET [CodePlex #7552](https://phpexcel.codeplex.com/workitem/7552) +- PHPExcel_Reader_Excel5 style support for BIFF5 files (Excel 5.0 - Excel 95) - @Erik Tilt [CodePlex #7862](https://phpexcel.codeplex.com/workitem/7862) +- PHPExcel_Reader_Excel5 support for user-defined colors and special built-in colors - @Erik Tilt [CodePlex #7918](https://phpexcel.codeplex.com/workitem/7918) +- Support for freeze panes in PHPExcel_Reader_Excel5 - @Erik Tilt [CodePlex #7992](https://phpexcel.codeplex.com/workitem/7992) +- Support for header and footer margins in PHPExcel_Reader_Excel5 - @Erik Tilt [CodePlex #7996](https://phpexcel.codeplex.com/workitem/7996) +- Support for active sheet index in Excel5 reader/writer - @Erik Tilt [CodePlex #7997](https://phpexcel.codeplex.com/workitem/7997) +- Freeze panes not read by PHPExcel_Reader_Excel2007 - @MarkBaker [CodePlex #7991](https://phpexcel.codeplex.com/workitem/7991) +- Support for screen zoom level (feature request) - @MB, ET [CodePlex #7993](https://phpexcel.codeplex.com/workitem/7993) +- Support for default style in PHPExcel_Reader_Excel5 - @Erik Tilt [CodePlex #8012](https://phpexcel.codeplex.com/workitem/8012) +- Apple iWork / Numbers.app incompatibility - @MarkBaker [CodePlex #8094](https://phpexcel.codeplex.com/workitem/8094) +- Support "between rule" in conditional formatting - @MarkBaker [CodePlex #7931](https://phpexcel.codeplex.com/workitem/7931) +- Comment size, width and height control (feature request) - @MarkBaker [CodePlex #8308](https://phpexcel.codeplex.com/workitem/8308) +- Improve method for storing MERGEDCELLS records in PHPExcel_Writer_Excel5 - @Erik Tilt [CodePlex #8418](https://phpexcel.codeplex.com/workitem/8418) +- Support for protectCells() in Excel5 reader/writer - @Erik Tilt [CodePlex #8435](https://phpexcel.codeplex.com/workitem/8435) +- Support for fitToWidth and fitToHeight pagesetup properties in PHPExcel_Reader_Excel5 - @Erik Tilt [CodePlex #8472](https://phpexcel.codeplex.com/workitem/8472) +- Support for setShowSummaryBelow() and setShowSummaryRight() in PHPExcel_Writer_Excel5 - @Erik Tilt [CodePlex #8489](https://phpexcel.codeplex.com/workitem/8489) +- Support for Excel 1904 calendar date mode (Mac) - @MarkBaker [CodePlex #8483](https://phpexcel.codeplex.com/workitem/8483) +- Excel5 reader: Support for reading images (bitmaps) - @Erik Tilt [CodePlex #7538](https://phpexcel.codeplex.com/workitem/7538) +- Support for default style in PHPExcel_Writer_Excel5 - @Erik Tilt [CodePlex #8787](https://phpexcel.codeplex.com/workitem/8787) +- Modified calculate() method to return either an array or the first value from the array for those functions that return arrays rather than single values (e.g the MMULT and TRANSPOSE function). This performance can be modified based on the $returnArrayAsType which can be set/retrieved by calling the setArrayReturnType() and getArrayReturnType() methods of the PHPExcel_Calculation class. - @MarkBaker + +### Bugfixes + +- Added ERROR.TYPE Information Function, MMULT Mathematical and Trigonometry Function, and TRANSPOSE Lookup and Reference Function - @MarkBaker [CodePlex #2346](https://phpexcel.codeplex.com/workitem/2346) +- setPrintGridlines(true) not working with PHPExcel_Writer_Excel5 - @Erik Tilt [CodePlex #7896](https://phpexcel.codeplex.com/workitem/7896) +- Incorrect mapping of fill patterns in PHPExcel_Writer_Excel5 - @Erik Tilt [CodePlex #7907](https://phpexcel.codeplex.com/workitem/7907) +- setShowGridlines(false) not working with PHPExcel_Writer_Excel2007 - @MarkBaker [CodePlex #7898](https://phpexcel.codeplex.com/workitem/7898) +- getShowGridlines() gives inverted value when reading sheet with PHPExcel_Reader_Excel2007 - @MarkBaker [CodePlex #7905](https://phpexcel.codeplex.com/workitem/7905) +- User-defined column width becomes slightly larger after read/write with Excel5 - @Erik Tilt [CodePlex #7944](https://phpexcel.codeplex.com/workitem/7944) +- Incomplete border style support in PHPExcel_Writer_Excel5 - @Erik Tilt [CodePlex #7949](https://phpexcel.codeplex.com/workitem/7949) +- Conditional formatting "containsText" read/write results in MS Office Excel 2007 crash - @MarkBaker [CodePlex #7928](https://phpexcel.codeplex.com/workitem/7928) +- All sheets are always selected in output when using PHPExcel_Writer_Excel2007 - @MarkBaker [CodePlex #7995](https://phpexcel.codeplex.com/workitem/7995) +- COLUMN function warning message during plain read/write - @MarkBaker [CodePlex #8013](https://phpexcel.codeplex.com/workitem/8013) +- setValue(0) results in string data type '0' - @MarkBaker [CodePlex #8155](https://phpexcel.codeplex.com/workitem/8155) +- Styles not removed when removing rows from sheet - @MarkBaker [CodePlex #8226](https://phpexcel.codeplex.com/workitem/8226) +- =IF formula causes fatal error during $objWriter->save() in Excel2007 format - @MarkBaker [CodePlex #8301](https://phpexcel.codeplex.com/workitem/8301) +- Exception thrown reading valid xls file: "Excel file is corrupt. Didn't find CONTINUE record while reading shared strings" - @Erik Tilt [CodePlex #8333](https://phpexcel.codeplex.com/workitem/8333) +- MS Outlook corrupts files generated by PHPExcel_Writer_Excel5 - @Erik Tilt [CodePlex #8320](https://phpexcel.codeplex.com/workitem/8320) +- Undefined method PHPExcel_Worksheet::setFreezePane() in ReferenceHelper.php on line 271 - @MarkBaker [CodePlex #8351](https://phpexcel.codeplex.com/workitem/8351) +- Ampersands (&), left and right angles (<, >) in Rich-Text strings leads to corrupt output using PHPExcel_Writer_Excel2007 - @MarkBaker [CodePlex #8401](https://phpexcel.codeplex.com/workitem/8401) +- Print header and footer not supporting UTF-8 in PHPExcel_Writer_Excel5 - @Erik Tilt [CodePlex #8408](https://phpexcel.codeplex.com/workitem/8408) +- Vertical page breaks not working with PHPExcel_Writer_Excel5 - @Erik Tilt [CodePlex #8463](https://phpexcel.codeplex.com/workitem/8463) +- Missing support for accounting underline types in PHPExcel_Writer_Excel5 - @Erik Tilt [CodePlex #8476](https://phpexcel.codeplex.com/workitem/8476) +- Infinite loops when reading corrupt xls file using PHPExcel_Reader_Excel5 - @Erik Tilt [CodePlex #8482](https://phpexcel.codeplex.com/workitem/8482) +- Sheet protection password not working with PHPExcel_Writer_Excel5 - @Erik Tilt [CodePlex #8566](https://phpexcel.codeplex.com/workitem/8566) +- PHPExcel_Style_NumberFormat::FORMAT_NUMBER ignored by PHPExcel_Writer_Excel5 - @Erik Tilt [CodePlex #8596](https://phpexcel.codeplex.com/workitem/8596) +- PHPExcel_Reader_Excel5 fails a whole when workbook contains a chart - @Erik Tilt [CodePlex #8781](https://phpexcel.codeplex.com/workitem/8781) +- Occasional loss of column widths using PHPExcel_Writer_Excel5 - @Erik Tilt [CodePlex #8788](https://phpexcel.codeplex.com/workitem/8788) +- Notices while reading formulas with deleted sheet references using PHPExcel_Reader_Excel5 - @Erik Tilt [CodePlex #8795](https://phpexcel.codeplex.com/workitem/8795) +- Default style not read by PHPExcel_Reader_Excel2007 - @MarkBaker [CodePlex #8807](https://phpexcel.codeplex.com/workitem/8807) +- Blank rows occupy too much space in file generated by PHPExcel_Writer_Excel2007 - @MarkBaker [CodePlex #9341](https://phpexcel.codeplex.com/workitem/9341) + +## [1.6.4] - 2008-10-27 + +### Features + +- RK record number error in MS developer documentation: 0x007E should be 0x027E - @Erik Tilt [CodePlex #7882](https://phpexcel.codeplex.com/workitem/7882) +- getHighestColumn() returning "@" for blank worksheet causes corrupt output - @MarkBaker [CodePlex #7878](https://phpexcel.codeplex.com/workitem/7878) +- Implement ROW and COLUMN Lookup/Reference Functions (when specified with a parameter) - @MarkBaker [CodePlex #2346](https://phpexcel.codeplex.com/workitem/2346) +- Implement initial work on OFFSET Lookup/Reference Function (returning address rather than value at address) - @MarkBaker [CodePlex #2346](https://phpexcel.codeplex.com/workitem/2346) +- Excel5 reader: Page margins - @Erik Tilt [CodePlex #7416](https://phpexcel.codeplex.com/workitem/7416) +- Excel5 reader: Header & Footer - @Erik Tilt [CodePlex #7417](https://phpexcel.codeplex.com/workitem/7417) +- Excel5 reader support for page setup (paper size etc.) - @Erik Tilt [CodePlex #7449](https://phpexcel.codeplex.com/workitem/7449) +- Improve speed and memory consumption of PHPExcel_Writer_CSV - @MarkBaker [CodePlex #7445](https://phpexcel.codeplex.com/workitem/7445) +- Better recognition of number format in HTML, CSV, and PDF writer - @MarkBaker [CodePlex #7432](https://phpexcel.codeplex.com/workitem/7432) +- Font support: Superscript and Subscript - @MarkBaker [CodePlex #7485](https://phpexcel.codeplex.com/workitem/7485) +- Excel5 reader font support: Super- and subscript - @Erik Tilt [CodePlex #7509](https://phpexcel.codeplex.com/workitem/7509) +- Excel5 reader style support: Text rotation and stacked text - @Erik Tilt [CodePlex #7521](https://phpexcel.codeplex.com/workitem/7521) +- Excel5 reader: Support for hyperlinks - @Erik Tilt [CodePlex #7530](https://phpexcel.codeplex.com/workitem/7530) +- Import sheet by request - @MB, ET [CodePlex #7557](https://phpexcel.codeplex.com/workitem/7557) +- PHPExcel_Reader_Excel5 support for page breaks - @Erik Tilt [CodePlex #7607](https://phpexcel.codeplex.com/workitem/7607) +- PHPExcel_Reader_Excel5 support for shrink-to-fit - @Erik Tilt [CodePlex #7622](https://phpexcel.codeplex.com/workitem/7622) +- Support for error types - @MB, ET [CodePlex #7675](https://phpexcel.codeplex.com/workitem/7675) +- Excel5 reader true formula support - @Erik Tilt [CodePlex #7388](https://phpexcel.codeplex.com/workitem/7388) +- Support for named ranges (defined names) in PHPExcel_Reader_Excel5 - @Erik Tilt [CodePlex #7701](https://phpexcel.codeplex.com/workitem/7701) +- Support for repeating rows and repeating columns (print titles) in PHPExcel_Reader_Excel5 - @Erik Tilt [CodePlex #7781](https://phpexcel.codeplex.com/workitem/7781) +- Support for print area in PHPExcel_Reader_Excel5 - @Erik Tilt [CodePlex #7783](https://phpexcel.codeplex.com/workitem/7783) +- Excel5 reader and writer support for horizontal and vertical centering of page - @Erik Tilt [CodePlex #7795](https://phpexcel.codeplex.com/workitem/7795) +- Applied patch 1962 - @MarkBaker +- Excel5 reader and writer support for hidden cells (formulas) - @Erik Tilt [CodePlex #7866](https://phpexcel.codeplex.com/workitem/7866) +- Support for indentation in cells (feature request) - @MB, ET [CodePlex #7612](https://phpexcel.codeplex.com/workitem/7612) + +### Bugfixes + +- Option for reading only specified interval of rows in a sheet - @MB, ET [CodePlex #7828](https://phpexcel.codeplex.com/workitem/7828) +- PHPExcel_Calculation_Functions::DATETIMENOW() and PHPExcel_Calculation_Functions::DATENOW() to force UTC - @MarkBaker [CodePlex #7367](https://phpexcel.codeplex.com/workitem/7367) +- Modified PHPExcel_Shared_Date::FormattedPHPToExcel() and PHPExcel_Shared_Date::ExcelToPHP to force datatype for return values - @MarkBaker [CodePlex #7395](https://phpexcel.codeplex.com/workitem/7395) +- Excel5 reader not producing UTF-8 strings with BIFF5 files - @Erik Tilt [CodePlex #7450](https://phpexcel.codeplex.com/workitem/7450) +- Array constant in formula gives run-time notice with Excel2007 writer - @MarkBaker [CodePlex #7470](https://phpexcel.codeplex.com/workitem/7470) +- PHPExcel_Reader_Excel2007 setReadDataOnly(true) returns Rich-Text - @MarkBaker [CodePlex #7494](https://phpexcel.codeplex.com/workitem/7494) +- PHPExcel_Reader_Excel5 setReadDataOnly(true) returns Rich-Text - @Erik Tilt [CodePlex #7496](https://phpexcel.codeplex.com/workitem/7496) +- Characters before superscript or subscript losing style - @MarkBaker [CodePlex #7497](https://phpexcel.codeplex.com/workitem/7497) +- Subscript not working with HTML writer - @MarkBaker [CodePlex #7507](https://phpexcel.codeplex.com/workitem/7507) +- DefaultColumnDimension not working on first column (A) - @MarkBaker [CodePlex #7508](https://phpexcel.codeplex.com/workitem/7508) +- Negative numbers are stored as text in PHPExcel_Writer_2007 - @MarkBaker [CodePlex #7527](https://phpexcel.codeplex.com/workitem/7527) +- Text rotation and stacked text not working with PHPExcel_Writer_Excel5 - @Erik Tilt [CodePlex #7531](https://phpexcel.codeplex.com/workitem/7531) +- PHPExcel_Shared_Date::isDateTimeFormatCode erroneously says true - @MarkBaker [CodePlex #7536](https://phpexcel.codeplex.com/workitem/7536) +- Different images with same filename in separate directories become duplicates - @MarkBaker [CodePlex #7559](https://phpexcel.codeplex.com/workitem/7559) +- PHPExcel_Reader_Excel5 not returning sheet names as UTF-8 using for Excel 95 files - @Erik Tilt [CodePlex #7568](https://phpexcel.codeplex.com/workitem/7568) +- setAutoSize(true) on empty column gives column width of 10 using PHPExcel_Writer_Excel2007 - @MarkBaker [CodePlex #7575](https://phpexcel.codeplex.com/workitem/7575) +- setAutoSize(true) on empty column gives column width of 255 using PHPExcel_Writer_Excel5 - @MB, ET [CodePlex #7573](https://phpexcel.codeplex.com/workitem/7573) +- Worksheet_Drawing bug - @MarkBaker [CodePlex #7514](https://phpexcel.codeplex.com/workitem/7514) +- getCalculatedValue() with REPT function causes script to stop - @MarkBaker [CodePlex #7593](https://phpexcel.codeplex.com/workitem/7593) +- getCalculatedValue() with LEN function causes script to stop - @MarkBaker [CodePlex #7594](https://phpexcel.codeplex.com/workitem/7594) +- Explicit fit-to-width (page setup) results in fit-to-height becoming 1 - @MarkBaker [CodePlex #7600](https://phpexcel.codeplex.com/workitem/7600) +- Fit-to-width value of 1 is lost after read/write of Excel2007 spreadsheet - @MarkBaker [CodePlex #7610](https://phpexcel.codeplex.com/workitem/7610) +- Conditional styles not read properly using PHPExcel_Reader_Excel2007 - @MarkBaker [CodePlex #7516](https://phpexcel.codeplex.com/workitem/7516) +- PHPExcel_Writer_2007: Default worksheet style works only for first sheet - @MarkBaker [CodePlex #7611](https://phpexcel.codeplex.com/workitem/7611) +- Cannot Lock Cells using PHPExcel_Writer_Excel5 - @Erik Tilt [CodePlex #6940](https://phpexcel.codeplex.com/workitem/6940) +- Incorrect cell protection values found when using Excel5 reader - @Erik Tilt [CodePlex #7621](https://phpexcel.codeplex.com/workitem/7621) +- Default row height not working above highest row using PHPExcel_Writer_Excel5 - @Erik Tilt [CodePlex #7623](https://phpexcel.codeplex.com/workitem/7623) +- Default column width does not get applied when using PHPExcel_Writer_Excel5 - @Erik Tilt [CodePlex #7637](https://phpexcel.codeplex.com/workitem/7637) +- Broken support for UTF-8 string formula results in PHPExcel_Reader_Excel5 - @Erik Tilt [CodePlex #7642](https://phpexcel.codeplex.com/workitem/7642) +- UTF-8 sheet names not working with PHPExcel_Writer_Excel5 - @Erik Tilt [CodePlex #7643](https://phpexcel.codeplex.com/workitem/7643) +- getCalculatedValue() with ISNONTEXT function causes script to stop - @MarkBaker [CodePlex #7631](https://phpexcel.codeplex.com/workitem/7631) +- Missing BIFF3 functions in PHPExcel_Writer_Excel5: USDOLLAR (YEN), FINDB, SEARCHB, REPLACEB, LEFTB, RIGHTB, MIDB, LENB, ASC, DBCS (JIS) - @Erik Tilt [CodePlex #7652](https://phpexcel.codeplex.com/workitem/7652) +- Excel5 reader doesn't read numbers correctly in 64-bit systems - @Erik Tilt [CodePlex #7663](https://phpexcel.codeplex.com/workitem/7663) +- Missing BIFF5 functions in PHPExcel_Writer_Excel5: ISPMT, DATEDIF, DATESTRING, NUMBERSTRING - @Erik Tilt [CodePlex #7667](https://phpexcel.codeplex.com/workitem/7667) +- Missing BIFF8 functions in PHPExcel_Writer_Excel5: GETPIVOTDATA, HYPERLINK, PHONETIC, AVERAGEA, MAXA, MINA, STDEVPA, VARPA, STDEVA, VARA - @Erik Tilt [CodePlex #7668](https://phpexcel.codeplex.com/workitem/7668) +- Wrong host value in PHPExcel_Shared_ZipStreamWrapper::stream_open() - @MarkBaker [CodePlex #7657](https://phpexcel.codeplex.com/workitem/7657) +- PHPExcel_Reader_Excel5 not reading explicitly entered error types in cells - @Erik Tilt [CodePlex #7676](https://phpexcel.codeplex.com/workitem/7676) +- Boolean and error data types not preserved for formula results in PHPExcel_Reader_Excel5 - @Erik Tilt [CodePlex #7678](https://phpexcel.codeplex.com/workitem/7678) +- PHPExcel_Reader_Excel2007 ignores cell data type - @MarkBaker [CodePlex #7695](https://phpexcel.codeplex.com/workitem/7695) +- PHPExcel_Reader_Excel5 ignores cell data type - @Erik Tilt [CodePlex #7712](https://phpexcel.codeplex.com/workitem/7712) +- PHPExcel_Writer_Excel5 not aware of data type - @Erik Tilt [CodePlex #7587](https://phpexcel.codeplex.com/workitem/7587) +- Long strings sometimes truncated when using PHPExcel_Reader_Excel5 - @Erik Tilt [CodePlex #7713](https://phpexcel.codeplex.com/workitem/7713) +- Direct entry of boolean or error type in cell not supported by PHPExcel_Writer_Excel5 - @Erik Tilt [CodePlex #7727](https://phpexcel.codeplex.com/workitem/7727) +- PHPExcel_Reader_Excel2007: Error reading cell with data type string, date number format, and numeric-like cell value - @MarkBaker [CodePlex #7714](https://phpexcel.codeplex.com/workitem/7714) +- Row and column outlines (group indent level) not showing after using PHPExcel_Writer_Excel5 - @Erik Tilt [CodePlex #7735](https://phpexcel.codeplex.com/workitem/7735) +- Missing UTF-8 support in number format codes for PHPExcel_Writer_Excel5 - @Erik Tilt [CodePlex #7737](https://phpexcel.codeplex.com/workitem/7737) +- Missing UTF-8 support with PHPExcel_Writer_Excel5 for explicit string in formula - @Erik Tilt [CodePlex #7750](https://phpexcel.codeplex.com/workitem/7750) +- Problem with class constants in PHPExcel_Style_NumberFormat - @MarkBaker [CodePlex #7726](https://phpexcel.codeplex.com/workitem/7726) +- Sometimes errors with PHPExcel_Reader_Excel5 reading hyperlinks - @Erik Tilt [CodePlex #7758](https://phpexcel.codeplex.com/workitem/7758) +- Hyperlink in cell always results in string data type when using PHPExcel_Writer_Excel5 - @Erik Tilt [CodePlex #7759](https://phpexcel.codeplex.com/workitem/7759) +- Excel file with blank sheet seen as broken in MS Office Excel 2007 when created by PHPExcel_Writer_Excel5 - @Erik Tilt [CodePlex #7771](https://phpexcel.codeplex.com/workitem/7771) +- PHPExcel_Reader_Excel5: Incorrect reading of formula with explicit string containing (escaped) double-quote - @Erik Tilt [CodePlex #7785](https://phpexcel.codeplex.com/workitem/7785) +- getCalculatedValue() fails on formula with sheet name containing (escaped) single-quote - @MarkBaker [CodePlex #7787](https://phpexcel.codeplex.com/workitem/7787) +- getCalculatedValue() fails on formula with explicit string containing (escaped) double-quote - @MarkBaker [CodePlex #7786](https://phpexcel.codeplex.com/workitem/7786) +- Problems with simultaneous repeatRowsAtTop and repeatColumnsAtLeft using Excel2007 reader and writer - @MarkBaker [CodePlex #7780](https://phpexcel.codeplex.com/workitem/7780) +- PHPExcel_Reader_Excel5: Error reading formulas with sheet reference containing special characters - @Erik Tilt [CodePlex #7802](https://phpexcel.codeplex.com/workitem/7802) +- Off-sheet references sheet!A1 not working with PHPExcel_Writer_Excel5 - @Erik Tilt [CodePlex #7831](https://phpexcel.codeplex.com/workitem/7831) +- Repeating rows/columns (print titles), print area not working with PHPExcel_Writer_Excel5 - @Erik Tilt [CodePlex #7834](https://phpexcel.codeplex.com/workitem/7834) +- Formula having datetime number format shows as text when using PHPExcel_Writer_Excel5 - @Erik Tilt [CodePlex #7849](https://phpexcel.codeplex.com/workitem/7849) +- Cannot set formula to hidden using applyFromArray() - @MarkBaker [CodePlex #7863](https://phpexcel.codeplex.com/workitem/7863) +- HTML/PDF Writers limited to 26 columns by calculateWorksheetDimension (erroneous comparison in getHighestColumn() method) - @MarkBaker [CodePlex #7805](https://phpexcel.codeplex.com/workitem/7805) +- Formula returning error type is lost when read by PHPExcel_Reader_Excel2007 - @MarkBaker [CodePlex #7873](https://phpexcel.codeplex.com/workitem/7873) +- PHPExcel_Reader_Excel5: Cell style lost for last column in group of blank cells - @Erik Tilt [CodePlex #7883](https://phpexcel.codeplex.com/workitem/7883) +- Column width sometimes collapses to auto size using Excel2007 reader/writer - @MarkBaker [CodePlex #7886](https://phpexcel.codeplex.com/workitem/7886) +- Data Validation Formula = 0 crashes Excel - @MarkBaker [CodePlex #9343](https://phpexcel.codeplex.com/workitem/9343) + +## [1.6.3] - 2008-08-25 + +### General + +- Modified PHPExcel_Shared_Date::PHPToExcel() to force UTC - @MarkBaker [CodePlex #7367](https://phpexcel.codeplex.com/workitem/7367) +- Applied patch 1629 - @MarkBaker +- Applied patch 1644 - @MarkBaker +- Implement repeatRow and repeatColumn in Excel5 writer - @MarkBaker [CodePlex #6485](https://phpexcel.codeplex.com/workitem/6485) + +### Features + +- Remove scene3d filter in Excel2007 drawing - @MarkBaker [CodePlex #6838](https://phpexcel.codeplex.com/workitem/6838) +- Implement CHOOSE and INDEX Lookup/Reference Functions - @MarkBaker [CodePlex #2346](https://phpexcel.codeplex.com/workitem/2346) +- Implement CLEAN Text Functions - @MarkBaker [CodePlex #2346](https://phpexcel.codeplex.com/workitem/2346) +- Implement YEARFRAC Date/Time Functions - @MarkBaker [CodePlex #2346](https://phpexcel.codeplex.com/workitem/2346) +- Implement 2 options for print/show gridlines - @MarkBaker [CodePlex #6508](https://phpexcel.codeplex.com/workitem/6508) +- Add VLOOKUP function (contribution) - @MarkBaker [CodePlex #7270](https://phpexcel.codeplex.com/workitem/7270) +- Implemented: ShrinkToFit - @MarkBaker [CodePlex #7182](https://phpexcel.codeplex.com/workitem/7182) +- Row heights not updated correctly when inserting new rows - @MarkBaker [CodePlex #7218](https://phpexcel.codeplex.com/workitem/7218) +- Copy worksheets within the same workbook - @MarkBaker [CodePlex #7157](https://phpexcel.codeplex.com/workitem/7157) +- Excel5 reader style support: horizontal and vertical alignment plus text wrap - @Erik Tilt [CodePlex #7290](https://phpexcel.codeplex.com/workitem/7290) +- Excel5 reader support for merged cells - @Erik Tilt [CodePlex #7294](https://phpexcel.codeplex.com/workitem/7294) +- Excel5 reader: Sheet Protection - @Erik Tilt [CodePlex #7296](https://phpexcel.codeplex.com/workitem/7296) +- Excel5 reader: Password for sheet protection - @Erik Tilt [CodePlex #7297](https://phpexcel.codeplex.com/workitem/7297) +- Excel5 reader: Column width - @Erik Tilt [CodePlex #7299](https://phpexcel.codeplex.com/workitem/7299) +- Excel5 reader: Row height - @Erik Tilt [CodePlex #7301](https://phpexcel.codeplex.com/workitem/7301) +- Excel5 reader: Font support - @Erik Tilt [CodePlex #7304](https://phpexcel.codeplex.com/workitem/7304) +- Excel5 reader: support for locked cells - @Erik Tilt [CodePlex #7324](https://phpexcel.codeplex.com/workitem/7324) +- Excel5 reader style support: Fill (background colors and patterns) - @Erik Tilt [CodePlex #7330](https://phpexcel.codeplex.com/workitem/7330) +- Excel5 reader style support: Borders (style and color) - @Erik Tilt [CodePlex #7332](https://phpexcel.codeplex.com/workitem/7332) +- Excel5 reader: Rich-Text support - @Erik Tilt [CodePlex #7346](https://phpexcel.codeplex.com/workitem/7346) +- Read Excel built-in number formats with Excel 2007 reader - @MarkBaker [CodePlex #7313](https://phpexcel.codeplex.com/workitem/7313) +- Excel5 reader: Number format support - @Erik Tilt [CodePlex #7317](https://phpexcel.codeplex.com/workitem/7317) +- Creating a copy of PHPExcel object - @MarkBaker [CodePlex #7362](https://phpexcel.codeplex.com/workitem/7362) +- Excel5 reader: support for row / column outline (group) - @Erik Tilt [CodePlex #7373](https://phpexcel.codeplex.com/workitem/7373) +- Implement default row/column sizes - @MarkBaker [CodePlex #7380](https://phpexcel.codeplex.com/workitem/7380) +- Writer HTML - option to return styles and table separately - @MarkBaker [CodePlex #7364](https://phpexcel.codeplex.com/workitem/7364) + +### Bugfixes + +- Excel5 reader: Support for remaining built-in number formats - @Erik Tilt [CodePlex #7393](https://phpexcel.codeplex.com/workitem/7393) +- Fixed rounding in HOUR MINUTE and SECOND Time functions, and improved performance for these - @MarkBaker +- Fix to TRIM function - @MarkBaker +- Fixed range validation in TIME Functions.php - @MarkBaker +- EDATE and EOMONTH functions now return date values based on the returnDateType flag - @MarkBaker +- Write date values that are the result of a calculation function correctly as Excel serialized dates rather than PHP serialized date values - @MarkBaker +- Excel2007 reader not always reading boolean correctly - @MarkBaker [CodePlex #6690](https://phpexcel.codeplex.com/workitem/6690) +- Columns above IZ - @MarkBaker [CodePlex #6275](https://phpexcel.codeplex.com/workitem/6275) +- Other locale than English causes Excel2007 writer to produce broken xlsx - @MarkBaker [CodePlex #6853](https://phpexcel.codeplex.com/workitem/6853) +- Typo: Number_fromat in NumberFormat.php - @MarkBaker [CodePlex #7061](https://phpexcel.codeplex.com/workitem/7061) +- Bug in Worksheet_BaseDrawing setWidth() - @MarkBaker [CodePlex #6865](https://phpexcel.codeplex.com/workitem/6865) +- PDF writer collapses column width for merged cells - @MarkBaker [CodePlex #6891](https://phpexcel.codeplex.com/workitem/6891) +- Issues with drawings filenames - @MarkBaker [CodePlex #6867](https://phpexcel.codeplex.com/workitem/6867) +- fromArray() local variable isn't defined - @MarkBaker [CodePlex #7073](https://phpexcel.codeplex.com/workitem/7073) +- PHPExcel_Writer_Excel5->setTempDir() not passed to all classes involved in writing to a file - @MarkBaker [CodePlex #7276](https://phpexcel.codeplex.com/workitem/7276) +- Excel5 reader not handling UTF-8 properly - @MarkBaker [CodePlex #7277](https://phpexcel.codeplex.com/workitem/7277) +- If you write a 0 value in cell, cell shows as empty - @MarkBaker [CodePlex #7327](https://phpexcel.codeplex.com/workitem/7327) +- Excel2007 writer: Row height ignored for empty rows - @MarkBaker [CodePlex #7302](https://phpexcel.codeplex.com/workitem/7302) +- Excel2007 (comments related error) - @MarkBaker [CodePlex #7281](https://phpexcel.codeplex.com/workitem/7281) +- Column width in other locale - @MarkBaker [CodePlex #7345](https://phpexcel.codeplex.com/workitem/7345) +- Excel2007 reader not reading underlined Rich-Text - @MarkBaker [CodePlex #7347](https://phpexcel.codeplex.com/workitem/7347) +- Excel5 reader converting booleans to strings - @Erik Tilt [CodePlex #7357](https://phpexcel.codeplex.com/workitem/7357) +- Recursive Object Memory Leak - @MarkBaker [CodePlex #7365](https://phpexcel.codeplex.com/workitem/7365) +- Excel2007 writer ignoring row dimensions without cells - @MarkBaker [CodePlex #7372](https://phpexcel.codeplex.com/workitem/7372) +- Excel5 reader is converting formatted numbers / dates to strings - @Erik Tilt [CodePlex #7382](https://phpexcel.codeplex.com/workitem/7382) + +## [1.6.2] - 2008-06-23 + +### General + +- Document style array values - @MarkBaker [CodePlex #6088](https://phpexcel.codeplex.com/workitem/6088) +- Applied patch 1195 - @MarkBaker +- Redirecting output to a client’s web browser - http headers - @MarkBaker [CodePlex #6178](https://phpexcel.codeplex.com/workitem/6178) +- Improve worksheet garbage collection - @MarkBaker [CodePlex #6187](https://phpexcel.codeplex.com/workitem/6187) +- Functions that return date values can now be configured to return as Excel serialized date/time, PHP serialized date/time, or a PHP date/time object. - @MarkBaker +- Functions that explicitly accept dates as parameters now permit values as Excel serialized date/time, PHP serialized date/time, a valid date string, or a PHP date/time object. - @MarkBaker +- Implement ACOSH, ASINH and ATANH functions for those operating platforms/PHP versions that don't include these functions - @MarkBaker +- Implement ATAN2 logic reversing the arguments as per Excel - @MarkBaker +- Additional validation of parameters for COMBIN - @MarkBaker + +### Features + +- Fixed validation for CEILING and FLOOR when the value and significance parameters have different signs; and allowed default value of 1 or -1 for significance when in GNUMERIC compatibility mode - @MarkBaker +- Implement ADDRESS, ISLOGICAL, ISTEXT and ISNONTEXT functions - @MarkBaker [CodePlex #2346](https://phpexcel.codeplex.com/workitem/2346) +- Implement COMPLEX, IMAGINARY, IMREAL, IMARGUMENT, IMCONJUGATE, IMABS, IMSUB, IMDIV, IMSUM, IMPRODUCT, IMSQRT, IMEXP, IMLN, IMLOG10, IMLOG2, IMPOWER IMCOS and IMSIN Engineering functions - @MarkBaker [CodePlex #2346](https://phpexcel.codeplex.com/workitem/2346) +- Implement NETWORKDAYS and WORKDAY Date/Time functions - @MarkBaker [CodePlex #2346](https://phpexcel.codeplex.com/workitem/2346) +- Make cell column AAA available - @MarkBaker [CodePlex #6100](https://phpexcel.codeplex.com/workitem/6100) +- Mark particular cell as selected when opening Excel - @MarkBaker [CodePlex #6095](https://phpexcel.codeplex.com/workitem/6095) +- Multiple sheets in PDF and HTML - @MarkBaker [CodePlex #6120](https://phpexcel.codeplex.com/workitem/6120) +- Implement PHPExcel_ReaderFactory and PHPExcel_WriterFactory - @MarkBaker [CodePlex #6227](https://phpexcel.codeplex.com/workitem/6227) +- Set image root of PHPExcel_Writer_HTML - @MarkBaker [CodePlex #6249](https://phpexcel.codeplex.com/workitem/6249) +- Enable/disable calculation cache - @MarkBaker [CodePlex #6264](https://phpexcel.codeplex.com/workitem/6264) +- PDF writer and multi-line text - @MarkBaker [CodePlex #6259](https://phpexcel.codeplex.com/workitem/6259) +- Feature request - setCacheExpirationTime() - @MarkBaker [CodePlex #6350](https://phpexcel.codeplex.com/workitem/6350) +- Implement late-binding mechanisms to reduce memory footprint - @JB [CodePlex #6370](https://phpexcel.codeplex.com/workitem/6370) +- Implement shared styles - @JB [CodePlex #6430](https://phpexcel.codeplex.com/workitem/6430) +- Copy sheet from external Workbook to active Workbook - @MarkBaker [CodePlex #6391](https://phpexcel.codeplex.com/workitem/6391) + +### Bugfixes + +- Functions in Conditional Formatting - @MarkBaker [CodePlex #6428](https://phpexcel.codeplex.com/workitem/6428) +- Default Style in Excel5 - @MarkBaker [CodePlex #6096](https://phpexcel.codeplex.com/workitem/6096) +- Numbers starting with '+' cause Excel 2007 errors - @MarkBaker [CodePlex #6150](https://phpexcel.codeplex.com/workitem/6150) +- ExcelWriter5 is not PHP5 compatible, using it with E_STRICT results in a bunch of errors (applied patches) - @MarkBaker [CodePlex #6092](https://phpexcel.codeplex.com/workitem/6092) +- Error Reader Excel2007 line 653 foreach ($relsDrawing->Relationship as $ele) - @MarkBaker [CodePlex #6179](https://phpexcel.codeplex.com/workitem/6179) +- Worksheet toArray() screws up DATE - @MarkBaker [CodePlex #6229](https://phpexcel.codeplex.com/workitem/6229) +- References to a Richtext cell in a formula - @MarkBaker [CodePlex #6253](https://phpexcel.codeplex.com/workitem/6253) +- insertNewColumnBefore Bug - @MarkBaker [CodePlex #6285](https://phpexcel.codeplex.com/workitem/6285) +- Error reading Excel2007 file with shapes - @MarkBaker [CodePlex #6319](https://phpexcel.codeplex.com/workitem/6319) +- Determine whether date values need conversion from PHP dates to Excel dates before writing to file, based on the data type (float or integer) - @MarkBaker [CodePlex #6302](https://phpexcel.codeplex.com/workitem/6302) +- Fixes to DATE function when it is given negative input parameters - @MarkBaker +- PHPExcel handles empty cells other than Excel - @MarkBaker [CodePlex #6347](https://phpexcel.codeplex.com/workitem/6347) +- PHPExcel handles 0 and "" as being the same - @MarkBaker [CodePlex #6348](https://phpexcel.codeplex.com/workitem/6348) +- Problem Using Excel2007 Reader for Spreadsheets containing images - @MarkBaker [CodePlex #6357](https://phpexcel.codeplex.com/workitem/6357) +- ShowGridLines ignored when reading/writing Excel 2007 - @MarkBaker [CodePlex #6359](https://phpexcel.codeplex.com/workitem/6359) +- Bug With Word Wrap in Excel 2007 Reader - @MarkBaker [CodePlex #6426](https://phpexcel.codeplex.com/workitem/6426) + +## [1.6.1] - 2008-04-28 + +### General + +- Fix documentation printing - @MarkBaker [CodePlex #5532](https://phpexcel.codeplex.com/workitem/5532) +- Memory usage improvements - @MarkBaker [CodePlex #5586](https://phpexcel.codeplex.com/workitem/5586) +- Applied patch 990 - @MarkBaker + +### Features + +- Applied patch 991 - @MarkBaker +- Implement PHPExcel_Reader_Excel5 - @BM [CodePlex #2841](https://phpexcel.codeplex.com/workitem/2841) +- Implement "toArray" and "fromArray" method - @MarkBaker [CodePlex #5564](https://phpexcel.codeplex.com/workitem/5564) +- Read shared formula - @MarkBaker [CodePlex #5665](https://phpexcel.codeplex.com/workitem/5665) +- Read image twoCellAnchor - @MarkBaker [CodePlex #5681](https://phpexcel.codeplex.com/workitem/5681) +- &G Image as bg for headerfooter - @MarkBaker [CodePlex #4446](https://phpexcel.codeplex.com/workitem/4446) +- Implement page layout functionality for Excel5 format - @MarkBaker [CodePlex #5834](https://phpexcel.codeplex.com/workitem/5834) + +### Bugfixes + +- Feature request: PHPExcel_Writer_PDF - @MarkBaker [CodePlex #6039](https://phpexcel.codeplex.com/workitem/6039) +- DefinedNames null check - @MarkBaker [CodePlex #5517](https://phpexcel.codeplex.com/workitem/5517) +- Hyperlinks should not always have trailing slash - @MarkBaker [CodePlex #5463](https://phpexcel.codeplex.com/workitem/5463) +- Saving Error - Uncaught exception (#REF! named range) - @MarkBaker [CodePlex #5592](https://phpexcel.codeplex.com/workitem/5592) +- Error when creating Zip file on Linux System (Not Windows) - @MarkBaker [CodePlex #5634](https://phpexcel.codeplex.com/workitem/5634) +- Time incorrecly formated - @MarkBaker [CodePlex #5876](https://phpexcel.codeplex.com/workitem/5876) +- Conditional formatting - second rule not applied - @MarkBaker [CodePlex #5914](https://phpexcel.codeplex.com/workitem/5914) +- PHPExcel_Reader_Excel2007 cannot load PHPExcel_Shared_File - @MarkBaker [CodePlex #5978](https://phpexcel.codeplex.com/workitem/5978) +- Output redirection to web browser - @MarkBaker [CodePlex #6020](https://phpexcel.codeplex.com/workitem/6020) + +## [1.6.0] - 2008-02-14 + +### Features + +- Use PHPExcel datatypes in formula calculation - @MarkBaker [CodePlex #3156](https://phpexcel.codeplex.com/workitem/3156) +- Center on page when printing - @MarkBaker [CodePlex #5019](https://phpexcel.codeplex.com/workitem/5019) +- Hyperlink to other spreadsheet - @MarkBaker [CodePlex #5099](https://phpexcel.codeplex.com/workitem/5099) +- Set the print area of a worksheet - @MarkBaker [CodePlex #5104](https://phpexcel.codeplex.com/workitem/5104) +- Read "definedNames" property of worksheet - @MarkBaker [CodePlex #5118](https://phpexcel.codeplex.com/workitem/5118) +- Set default style for all cells - @MarkBaker [CodePlex #5338](https://phpexcel.codeplex.com/workitem/5338) +- Named Ranges - @MarkBaker [CodePlex #4216](https://phpexcel.codeplex.com/workitem/4216) + +### Bugfixes + +- Implement worksheet references (Sheet1!A1) - @MarkBaker [CodePlex #5398](https://phpexcel.codeplex.com/workitem/5398) +- Redirect output to a client's web browser - @MarkBaker [CodePlex #4967](https://phpexcel.codeplex.com/workitem/4967) +- "File Error: data may have been lost." seen in Excel 2007 and Excel 2003 SP3 when opening XLS file - @MarkBaker [CodePlex #5008](https://phpexcel.codeplex.com/workitem/5008) +- Bug in style's getHashCode() - @MarkBaker [CodePlex #5165](https://phpexcel.codeplex.com/workitem/5165) +- PHPExcel_Reader not correctly reading numeric values - @MarkBaker [CodePlex #5165](https://phpexcel.codeplex.com/workitem/5165) +- Text rotation is read incorrectly - @MarkBaker [CodePlex #5324](https://phpexcel.codeplex.com/workitem/5324) +- Enclosure " and data " result a bad data : \" instead of "" - @MarkBaker [CodePlex #5326](https://phpexcel.codeplex.com/workitem/5326) +- Formula parser - IF statement returning array instead of scalar - @MarkBaker [CodePlex #5332](https://phpexcel.codeplex.com/workitem/5332) +- setFitToWidth(nbpage) & setFitToWidth(nbpage) work partially - @MarkBaker [CodePlex #5351](https://phpexcel.codeplex.com/workitem/5351) +- Worksheet::setTitle() causes unwanted renaming - @MarkBaker [CodePlex #5361](https://phpexcel.codeplex.com/workitem/5361) +- Hyperlinks not working. Results in broken xlsx file. - @MarkBaker [CodePlex #5407](https://phpexcel.codeplex.com/workitem/5407) + +## [1.5.5] - 2007-12-24 + +### General + +- Grouping Rows - @MarkBaker [CodePlex #4135](https://phpexcel.codeplex.com/workitem/4135) + +### Features + +- Semi-nightly builds - @MarkBaker [CodePlex #4427](https://phpexcel.codeplex.com/workitem/4427) +- Implement "date" datatype - @MarkBaker [CodePlex #3155](https://phpexcel.codeplex.com/workitem/3155) +- Date format not honored in CSV writer - @MarkBaker [CodePlex #4150](https://phpexcel.codeplex.com/workitem/4150) +- RichText and sharedStrings - @MarkBaker [CodePlex #4199](https://phpexcel.codeplex.com/workitem/4199) +- Implement more Excel calculation functions - @MarkBaker [CodePlex #2346](https://phpexcel.codeplex.com/workitem/2346) + - Addition of DATE, DATEDIF, DATEVALUE, DAY, DAYS360- Implement more Excel calculation functions - @MarkBaker [CodePlex #2346](https://phpexcel.codeplex.com/workitem/2346) + - Addition of AVEDEV, HARMEAN and GEOMEAN + - Addition of the BINOMDIST (Non-cumulative only), COUNTBLANK, EXPONDIST, FISHER, FISHERINV, NORMDIST, NORMSDIST, PERMUT, POISSON (Non-cumulative only) and STANDARDIZE Statistical Functions + - Addition of the CEILING, COMBIN, EVEN, FACT, FACTDOUBLE, FLOOR, MULTINOMIAL, ODD, ROUNDDOWN, ROUNDUP, SIGN, SQRTPI and SUMSQ Mathematical Functions + - Addition of the NORMINV, NORMSINV, CONFIDENCE and SKEW Statistical Functions + - Addition of the CRITBINOM, HYPGEOMDIST, KURT, LOGINV, LOGNORMDIST, NEGBINOMDIST and WEIBULL Statistical Functions + - Addition of the LARGE, PERCENTILE, QUARTILE, SMALL and TRIMMEAN Statistical Functions + - Addition of the BIN2HEX, BIN2OCT, DELTA, ERF, ERFC, GESTEP, HEX2BIN, HEX2DEC, HEX2OCT, OCT2BIN and OCT2HEX Engineering Functions + - Addition of the CHIDIST, GAMMADIST and GAMMALN Statistical Functions + - Addition of the GCD, LCM, MROUND and SUBTOTAL Mathematical Functions + - Addition of the LOWER, PROPER and UPPER Text Functions + - Addition of the BETADIST and BETAINV Statistical Functions + - Addition of the CHIINV and GAMMAINV Statistical Functions + - Addition of the SERIESSUM Mathematical Function + - Addition of the CHAR, CODE, FIND, LEN, REPT, SEARCH, T, TRIM Text Functions + - Addition of the FALSE and TRUE Boolean Functions + - Addition of the TDIST and TINV Statistical Functions + - Addition of the EDATE, EOMONTH, YEAR, MONTH, TIME, TIMEVALUE, HOUR, MINUTE, SECOND, WEEKDAY, WEEKNUM, NOW, TODAY and Date/Time Function + - Addition of the BESSELI, BESSELJ, BESSELK and BESSELY Engineering Functions + - Addition of the SLN and SYD Financial Functions + - reworked MODE calculation to handle floating point numbers + - Improved error trapping for invalid input values + - Fix to SMALL, LARGE, PERCENTILE and TRIMMEAN to eliminate non-numeric values + - Added CDF to BINOMDIST and POISSON + - Fix to a potential endless loop in CRITBINOM, together with other bugfixes to the algorithm + - Fix to SQRTPI so that it will work with a real value parameter rather than just integers + - Trap for passing negative values to FACT + - Improved accuracy of the NORMDIST cumulative function, and of the ERF and ERFC functions + - Replicated Excel data-type and error handling for BIN, DEC, OCT and HEX conversion functions + - Replicated Excel data-type and error handling for AND and OR Boolean functions + - Bugfix to MROUND + - Rework of the DATE, DATEVALUE, DAY, DAYS360 and DATEDIF date/Time functions to use Excel dates rather than straight PHP dates + - Rework of the AND, OR Boolean functions to ignore string values + - Rework of the BIN2DEC, BIN2HEX, BIN2OCT, DEC2BIN, DEC2HEX, DEC2OCT Engineering functions to handle two's complement + - Excel, Gnumeric and OpenOffice Calc compatibility flag for functions + - Note, not all functions have yet been written to work with the Gnumeric and OpenOffice Calc compatibility flags + - 1900 or 1904 Calendar flag for date functions + - Reworked ExcelToPHP date method to handle the Excel 1900 leap year + - Note that this will not correctly return values prior to 13-Dec-1901 20:45:52 as this is the minimum value that PHP date serial values can handle. If you need to work with dates prior to this, then an ExcelToPHPObject method has been added which will work correctly with values between Excel's 1900 calendar base date of 1-Jan-1900, and 13-Dec-1901 + - Addition of ExcelToPHPObject date method to return a PHP DateTime object from an Excel date serial value + - PHPToExcel method modified to accept either PHP date serial numbers or PHP DateTime objects + - Addition of FormattedPHPToExcel which will accept a date and time broken to into year, month, day, hour, minute, second and return an Excel date serial value- Control characters in Excel 2007 - @MarkBaker [CodePlex #4485](https://phpexcel.codeplex.com/workitem/4485) +- BaseDrawing::setWidthAndHeight method request - @MarkBaker [CodePlex #4796](https://phpexcel.codeplex.com/workitem/4796) +- Page Setup -> Print Titles -> Sheet -> 'Rows to repeat at top' - @MarkBaker [CodePlex #4798](https://phpexcel.codeplex.com/workitem/4798) + +### Bugfixes + +- Comment functionality - @MarkBaker [CodePlex #4433](https://phpexcel.codeplex.com/workitem/4433) +- Undefined variable in PHPExcel_Writer_Serialized - @MarkBaker [CodePlex #4124](https://phpexcel.codeplex.com/workitem/4124) +- Notice: Object of class PHPExcel_RichText could not be converted to int - @MarkBaker [CodePlex #4125](https://phpexcel.codeplex.com/workitem/4125) +- Excel5Writer: utf8 string not converted to utf16 - @MarkBaker [CodePlex #4126](https://phpexcel.codeplex.com/workitem/4126) +- PHPExcel_RichText and autosize - @MarkBaker [CodePlex #4180](https://phpexcel.codeplex.com/workitem/4180) +- Excel5Writer produces broken xls files after change mentioned in work item 4126 - @MarkBaker [CodePlex #4574](https://phpexcel.codeplex.com/workitem/4574) +- Small bug in PHPExcel_Reader_Excel2007 function _readStyle - @MarkBaker [CodePlex #4797](https://phpexcel.codeplex.com/workitem/4797) + +## [1.5.0] - 2007-10-23 + +### Features + +- Refactor PHPExcel Drawing - @MarkBaker [CodePlex #3265](https://phpexcel.codeplex.com/workitem/3265) +- Update Shared/OLE.php to latest version from PEAR - @CS [CodePlex #3079](https://phpexcel.codeplex.com/workitem/3079) +- Excel2007 vs Excel2003 compatibility pack - @MarkBaker [CodePlex #3217](https://phpexcel.codeplex.com/workitem/3217) +- Cell protection (lock/unlock) - @MarkBaker [CodePlex #3234](https://phpexcel.codeplex.com/workitem/3234) +- Create clickable links (hyperlinks) - @MarkBaker [CodePlex #3543](https://phpexcel.codeplex.com/workitem/3543) +- Additional page setup parameters - @MarkBaker [CodePlex #3241](https://phpexcel.codeplex.com/workitem/3241) +- Make temporary file path configurable (Excel5) - @MarkBaker [CodePlex #3300](https://phpexcel.codeplex.com/workitem/3300) +- Small addition to applyFromArray for font - @MarkBaker [CodePlex #3306](https://phpexcel.codeplex.com/workitem/3306) + +### Bugfixes + +- Better feedback when save of file is not possible - @MarkBaker [CodePlex #3373](https://phpexcel.codeplex.com/workitem/3373) +- Text Rotation - @MarkBaker [CodePlex #3181](https://phpexcel.codeplex.com/workitem/3181) +- Small bug in Page Orientation - @MarkBaker [CodePlex #3237](https://phpexcel.codeplex.com/workitem/3237) +- insertNewColumnBeforeByColumn undefined - @MarkBaker [CodePlex #3812](https://phpexcel.codeplex.com/workitem/3812) +- Sheet references not working in formula (Excel5 Writer) - @MarkBaker [CodePlex #3893](https://phpexcel.codeplex.com/workitem/3893) + +## [1.4.5] - 2007-08-23 + +### General + +- Class file endings - @MarkBaker [CodePlex #3003](https://phpexcel.codeplex.com/workitem/3003) +- Different calculation engine improvements - @MarkBaker [CodePlex #3081](https://phpexcel.codeplex.com/workitem/3081) +- Different improvements in PHPExcel_Reader_Excel2007 - @MarkBaker [CodePlex #3082](https://phpexcel.codeplex.com/workitem/3082) + +### Features + +- Set XML indentation in PHPExcel_Writer_Excel2007 - @MarkBaker [CodePlex #3146](https://phpexcel.codeplex.com/workitem/3146) +- Optionally store temporary Excel2007 writer data in file instead of memory - @MarkBaker [CodePlex #3159](https://phpexcel.codeplex.com/workitem/3159) +- Implement show/hide gridlines - @MarkBaker [CodePlex #3063](https://phpexcel.codeplex.com/workitem/3063) +- Implement option to read only data - @MarkBaker [CodePlex #3064](https://phpexcel.codeplex.com/workitem/3064) +- Optionally disable formula precalculation - @MarkBaker [CodePlex #3080](https://phpexcel.codeplex.com/workitem/3080) +- Explicitly set cell datatype - @MarkBaker [CodePlex #3154](https://phpexcel.codeplex.com/workitem/3154) + +### Bugfixes + +- Implement more Excel calculation functions - @MarkBaker [CodePlex #2346](https://phpexcel.codeplex.com/workitem/2346) + - Addition of MINA, MAXA, COUNTA, AVERAGEA, MEDIAN, MODE, DEVSQ, STDEV, STDEVA, STDEVP, STDEVPA, VAR, VARA, VARP and VARPA Excel Functions + - Fix to SUM, PRODUCT, QUOTIENT, MIN, MAX, COUNT and AVERAGE functions when cell contains a numeric value in a string datatype, bringing it in line with MS Excel behaviour- File_exists on ZIP fails on some installations - @MarkBaker [CodePlex #2881](https://phpexcel.codeplex.com/workitem/2881) +- Argument in textRotation should be -90..90 - @MarkBaker [CodePlex #2879](https://phpexcel.codeplex.com/workitem/2879) +- Excel2007 reader/writer not implementing OpenXML/SpreadsheetML styles 100% correct - @MarkBaker [CodePlex #2883](https://phpexcel.codeplex.com/workitem/2883) +- Active sheet index not read/saved - @MarkBaker [CodePlex #2513](https://phpexcel.codeplex.com/workitem/2513) +- Print and print preview of generated XLSX causes Excel2007 to crash - @MarkBaker [CodePlex #2935](https://phpexcel.codeplex.com/workitem/2935) +- Error in Calculations - COUNT() function - @MarkBaker [CodePlex #2952](https://phpexcel.codeplex.com/workitem/2952) +- HTML and CSV writer not writing last row - @MarkBaker [CodePlex #3002](https://phpexcel.codeplex.com/workitem/3002) +- Memory leak in Excel5 writer - @MarkBaker [CodePlex #3017](https://phpexcel.codeplex.com/workitem/3017) +- Printing (PHPExcel_Writer_Excel5) - @MarkBaker [CodePlex #3044](https://phpexcel.codeplex.com/workitem/3044) +- Problems reading zip:// - @MarkBaker [CodePlex #3046](https://phpexcel.codeplex.com/workitem/3046) +- Error reading conditional formatting - @MarkBaker [CodePlex #3047](https://phpexcel.codeplex.com/workitem/3047) +- Bug in Excel5 writer (storePanes) - @MarkBaker [CodePlex #3067](https://phpexcel.codeplex.com/workitem/3067) +- Memory leak in PHPExcel_Style_Color - @MarkBaker [CodePlex #3077](https://phpexcel.codeplex.com/workitem/3077) + +## [1.4.0] - 2007-07-23 + +### General + +- Coding convention / code cleanup - @MarkBaker [CodePlex #2687](https://phpexcel.codeplex.com/workitem/2687) +- Use set_include_path in tests - @MarkBaker [CodePlex #2717](https://phpexcel.codeplex.com/workitem/2717) + +### Features + +- Move PHPExcel_Writer_Excel5 OLE to PHPExcel_Shared_OLE - @MarkBaker [CodePlex #2812](https://phpexcel.codeplex.com/workitem/2812) +- Hide/Unhide Column or Row - @MarkBaker [CodePlex #2679](https://phpexcel.codeplex.com/workitem/2679) +- Implement multi-cell styling - @MarkBaker [CodePlex #2271](https://phpexcel.codeplex.com/workitem/2271) +- Implement CSV file format (reader/writer) - @MarkBaker [CodePlex #2720](https://phpexcel.codeplex.com/workitem/2720) + +### Bugfixes + +- Implement HTML file format - @MarkBaker [CodePlex #2845](https://phpexcel.codeplex.com/workitem/2845) +- Active sheet index not read/saved - @MarkBaker [CodePlex #2513](https://phpexcel.codeplex.com/workitem/2513) +- Freeze Panes with PHPExcel_Writer_Excel5 - @MarkBaker [CodePlex #2678](https://phpexcel.codeplex.com/workitem/2678) +- OLE.php - @MarkBaker [CodePlex #2680](https://phpexcel.codeplex.com/workitem/2680) +- Copy and pasting multiple drop-down list cells breaks reader - @MarkBaker [CodePlex #2736](https://phpexcel.codeplex.com/workitem/2736) +- Function setAutoFilterByColumnAndRow takes wrong arguments - @MarkBaker [CodePlex #2775](https://phpexcel.codeplex.com/workitem/2775) +- Simplexml_load_file fails on ZipArchive - @MarkBaker [CodePlex #2858](https://phpexcel.codeplex.com/workitem/2858) + +## [1.3.5] - 2007-06-27 + +### Features + +- Documentation - @MarkBaker [CodePlex #15](https://phpexcel.codeplex.com/workitem/15) +- PHPExcel_Writer_Excel5 - @JV +- PHPExcel_Reader_Excel2007: Image shadows - @JV +- Data validation - @MarkBaker [CodePlex #2385](https://phpexcel.codeplex.com/workitem/2385) + +### Bugfixes + +- Implement richtext strings - @MarkBaker +- Empty relations when adding image to any sheet but the first one - @MarkBaker [CodePlex #2443](https://phpexcel.codeplex.com/workitem/2443) +- Excel2007 crashes on print preview - @MarkBaker [CodePlex #2536](https://phpexcel.codeplex.com/workitem/2536) + +## [1.3.0] - 2007-06-05 + +### General + +- Create PEAR package - @MarkBaker [CodePlex #1942](https://phpexcel.codeplex.com/workitem/1942) + +### Features + +- Replace *->duplicate() by __clone() - @MarkBaker [CodePlex #2331](https://phpexcel.codeplex.com/workitem/2331) +- PHPExcel_Reader_Excel2007: Column auto-size, Protection, Merged cells, Wrap text, Page breaks, Auto filter, Images - @JV +- Implement "freezing" panes - @MarkBaker [CodePlex #245](https://phpexcel.codeplex.com/workitem/245) +- Cell addressing alternative - @MarkBaker [CodePlex #2273](https://phpexcel.codeplex.com/workitem/2273) +- Implement cell word-wrap attribute - @MarkBaker [CodePlex #2270](https://phpexcel.codeplex.com/workitem/2270) +- Auto-size column - @MarkBaker [CodePlex #2282](https://phpexcel.codeplex.com/workitem/2282) +- Implement formula calculation - @MarkBaker [CodePlex #241](https://phpexcel.codeplex.com/workitem/241) + +### Bugfixes + +- Insert/remove row/column - @MarkBaker [CodePlex #2375](https://phpexcel.codeplex.com/workitem/2375) +- PHPExcel_Worksheet::getCell() should not accept absolute coordinates - @MarkBaker [CodePlex #1931](https://phpexcel.codeplex.com/workitem/1931) +- Cell reference without row number - @MarkBaker [CodePlex #2272](https://phpexcel.codeplex.com/workitem/2272) +- Styles with same coordinate but different worksheet - @MarkBaker [CodePlex #2276](https://phpexcel.codeplex.com/workitem/2276) +- PHPExcel_Worksheet->getCellCollection() usort error - @MarkBaker [CodePlex #2290](https://phpexcel.codeplex.com/workitem/2290) +- Bug in PHPExcel_Cell::stringFromColumnIndex - @SS [CodePlex #2353](https://phpexcel.codeplex.com/workitem/2353) +- Reader: numFmts can be missing, use cellStyleXfs instead of cellXfs in styles - @JV [CodePlex #2353](https://phpexcel.codeplex.com/workitem/2353) + +## [1.2.0] - 2007-04-26 + +### General + +- Stringtable attribute "count" not necessary, provides wrong info to Excel sometimes... - @MarkBaker +- Updated tests to address more document properties - @MarkBaker +- Some refactoring in PHPExcel_Writer_Excel2007_Workbook - @MarkBaker +- New package: PHPExcel_Shared - @MarkBaker +- Password hashing algorithm implemented in PHPExcel_Shared_PasswordHasher - @MarkBaker +- Moved pixel conversion functions to PHPExcel_Shared_Drawing - @MarkBaker +- Switch over to LGPL license - @MarkBaker [CodePlex #244](https://phpexcel.codeplex.com/workitem/244) + +### Features + +- Include PHPExcel version in file headers - @MarkBaker [CodePlex #5](https://phpexcel.codeplex.com/workitem/5) +- Autofilter - @MarkBaker [CodePlex #6](https://phpexcel.codeplex.com/workitem/6) +- Extra document property: keywords - @MarkBaker [CodePlex #7](https://phpexcel.codeplex.com/workitem/7) +- Extra document property: category - @MarkBaker [CodePlex #8](https://phpexcel.codeplex.com/workitem/8) +- Document security - @MarkBaker [CodePlex #9](https://phpexcel.codeplex.com/workitem/9) +- PHPExcel_Writer_Serialized and PHPExcel_Reader_Serialized - @MarkBaker [CodePlex #10](https://phpexcel.codeplex.com/workitem/10) +- Alternative syntax: Addressing a cell - @MarkBaker [CodePlex #11](https://phpexcel.codeplex.com/workitem/11) +- Merge cells - @MarkBaker [CodePlex #12](https://phpexcel.codeplex.com/workitem/12) + +### Bugfixes + +- Protect ranges of cells with a password - @MarkBaker [CodePlex #13](https://phpexcel.codeplex.com/workitem/13) +- (style/fill/patternFill/fgColor or bgColor can be empty) - @JV [CodePlex #14](https://phpexcel.codeplex.com/workitem/14) + +## [1.1.1] - 2007-03-26 + +### General + +- Syntax error in "Classes/PHPExcel/Writer/Excel2007.php" on line 243 - @MarkBaker [CodePlex #1250](https://phpexcel.codeplex.com/workitem/1250) +- Reader should check if file exists and throws an exception when it doesn't - @MarkBaker [CodePlex #1282](https://phpexcel.codeplex.com/workitem/1282) + +## [1.1.0] - 2007-03-22 + +### Bugfixes + +- Style information lost after passing trough Excel2007_Reader - @MarkBaker [CodePlex #836](https://phpexcel.codeplex.com/workitem/836) + +### General + +- Number of columns > AZ fails fixed in PHPExcel_Cell::columnIndexFromString - @MarkBaker [CodePlex #913](https://phpexcel.codeplex.com/workitem/913) + +### Features + +- Added a brief file with installation instructions - @MarkBaker +- Page breaks (horizontal and vertical) - @MarkBaker +- Image shadows - @MarkBaker + +## [1.0.0] - 2007-02-22 + +### Bugfixes + +- PHPExcel->removeSheetByIndex now re-orders sheets after deletion, so no array indexes are lost - @JV +- PHPExcel_Writer_Excel2007_Worksheet::_writeCols() used direct assignment to $pSheet->getColumnDimension('A')->Width instead of $pSheet->getColumnDimension('A')->setWidth() - @JV +- DocumentProperties used $this->LastModifiedBy instead of $this->_lastModifiedBy. - @JV + +### General + +- Only first = should be removed when writing formula in PHPExcel_Writer_Excel2007_Worksheet. - @JV +- Consistency of method names to camelCase - @JV +- Updated tests to match consistency changes - @JV +- Detection of mime-types now with image_type_to_mime_type() - @JV +- Constants now hold string value used in Excel 2007 - @JV + +### Features + +- Fixed folder name case (WorkSheet -> Worksheet) - @MarkBaker +- PHPExcel classes (not the Writer classes) can be duplicated, using a duplicate() method. - @MarkBaker +- Cell styles can now be duplicated to a range of cells using PHPExcel_Worksheet->duplicateStyle() - @MarkBaker +- Conditional formatting - @MarkBaker +- Reader for Excel 2007 (not supporting full specification yet!) - @JV + +## [1.0.0 RC] - 2007-01-31 + +- Project name has been changed to PHPExcel +- Project homepage is now http://www.codeplex.com/PHPExcel +- Started versioning at number: PHPExcel 1.0.0 RC + +## 2007-01-22 + +- Fixed some performance issues on large-scale worksheets (mainly loops vs. indexed arrays) +- Performance on creating StringTable has been increased +- Performance on writing Excel2007 worksheet has been increased + +## 2007-01-18 + +- Images can now be rotated +- Fixed bug: When drawings have full path specified, no mime type can be deducted +- Fixed bug: Only one drawing can be added to a worksheet + +## 2007-01-12 + +- Refactoring of some classes to use ArrayObject instead of array() +- Cell style now has support for number format (i.e. #,##0) +- Implemented embedding images + +## 2007-01-02 + +- Cell style now has support for fills, including gradient fills +- Cell style now has support for fonts +- Cell style now has support for border colors +- Cell style now has support for font colors +- Cell style now has support for alignment + +## 2006-12-21 + +- Support for cell style borders +- Support for cell styles +- Refactoring of Excel2007 Writer into multiple classes in package SpreadSheet_Writer_Excel2007 +- Refactoring of all classes, changed public members to public properties using getter/setter +- Worksheet names are now unique. On duplicate worksheet names, a number is appended. +- Worksheet now has parent SpreadSheet object +- Worksheet now has support for page header and footer +- Worksheet now has support for page margins +- Worksheet now has support for page setup (only Paper size and Orientation) +- Worksheet properties now accessible by using getProperties() +- Worksheet now has support for row and column dimensions (height / width) +- Exceptions thrown have a more clear description + +## Initial version + +- Create a Spreadsheet object +- Add one or more Worksheet objects +- Add cells to Worksheet objects +- Export Spreadsheet object to Excel 2007 OpenXML format +- Each cell supports the following data formats: string, number, formula, boolean. diff --git a/vendor/phpoffice/phpspreadsheet/CHANGELOG.md b/vendor/phpoffice/phpspreadsheet/CHANGELOG.md new file mode 100644 index 0000000..897a8e5 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/CHANGELOG.md @@ -0,0 +1,471 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com) +and this project adheres to [Semantic Versioning](https://semver.org). + +## [1.12.0] - 2020-04-27 + +### Added + +- Improved the ARABIC function to also handle short-hand roman numerals +- Added support for the FLOOR.MATH and FLOOR.PRECISE functions [#1351](https://github.com/PHPOffice/PhpSpreadsheet/pull/1351) + +### Fixed + +- Fix ROUNDUP and ROUNDDOWN for floating-point rounding error [#1404](https://github.com/PHPOffice/PhpSpreadsheet/pull/1404) +- Fix ROUNDUP and ROUNDDOWN for negative number [#1417](https://github.com/PHPOffice/PhpSpreadsheet/pull/1417) +- Fix loading styles from vmlDrawings when containing whitespace [#1347](https://github.com/PHPOffice/PhpSpreadsheet/issues/1347) +- Fix incorrect behavior when removing last row [#1365](https://github.com/PHPOffice/PhpSpreadsheet/pull/1365) +- MATCH with a static array should return the position of the found value based on the values submitted [#1332](https://github.com/PHPOffice/PhpSpreadsheet/pull/1332) +- Fix Xlsx Reader's handling of undefined fill color [#1353](https://github.com/PHPOffice/PhpSpreadsheet/pull/1353) + +## [1.11.0] - 2020-03-02 + +### Added + +- Added support for the BASE function +- Added support for the ARABIC function +- Conditionals - Extend Support for (NOT)CONTAINSBLANKS [#1278](https://github.com/PHPOffice/PhpSpreadsheet/pull/1278) + +### Fixed + +- Handle Error in Formula Processing Better for Xls [#1267](https://github.com/PHPOffice/PhpSpreadsheet/pull/1267) +- Handle ConditionalStyle NumberFormat When Reading Xlsx File [#1296](https://github.com/PHPOffice/PhpSpreadsheet/pull/1296) +- Fix Xlsx Writer's handling of decimal commas [#1282](https://github.com/PHPOffice/PhpSpreadsheet/pull/1282) +- Fix for issue by removing test code mistakenly left in [#1328](https://github.com/PHPOffice/PhpSpreadsheet/pull/1328) +- Fix for Xls writer wrong selected cells and active sheet [#1256](https://github.com/PHPOffice/PhpSpreadsheet/pull/1256) +- Fix active cell when freeze pane is used [#1323](https://github.com/PHPOffice/PhpSpreadsheet/pull/1323) +- Fix XLSX file loading with autofilter containing '$' [#1326](https://github.com/PHPOffice/PhpSpreadsheet/pull/1326) +- PHPDoc - Use `@return $this` for fluent methods [#1362](https://github.com/PHPOffice/PhpSpreadsheet/pull/1362) + +## [1.10.1] - 2019-12-02 + +### Changed + +- PHP 7.4 compatibility + +### Fixed + +- FLOOR() function accept negative number and negative significance [#1245](https://github.com/PHPOffice/PhpSpreadsheet/pull/1245) +- Correct column style even when using rowspan [#1249](https://github.com/PHPOffice/PhpSpreadsheet/pull/1249) +- Do not confuse defined names and cell refs [#1263](https://github.com/PHPOffice/PhpSpreadsheet/pull/1263) +- XLSX reader/writer keep decimal for floats with a zero decimal part [#1262](https://github.com/PHPOffice/PhpSpreadsheet/pull/1262) +- ODS writer prevent invalid numeric value if locale decimal separator is comma [#1268](https://github.com/PHPOffice/PhpSpreadsheet/pull/1268) +- Xlsx writer actually writes plotVisOnly and dispBlanksAs from chart properties [#1266](https://github.com/PHPOffice/PhpSpreadsheet/pull/1266) + +## [1.10.0] - 2019-11-18 + +### Changed + +- Change license from LGPL 2.1 to MIT [#140](https://github.com/PHPOffice/PhpSpreadsheet/issues/140) + +### Added + +- Implementation of IFNA() logical function +- Support "showZeros" worksheet option to change how Excel shows and handles "null" values returned from a calculation +- Allow HTML Reader to accept HTML as a string into an existing spreadsheet [#1212](https://github.com/PHPOffice/PhpSpreadsheet/pull/1212) + +### Fixed + +- IF implementation properly handles the value `#N/A` [#1165](https://github.com/PHPOffice/PhpSpreadsheet/pull/1165) +- Formula Parser: Wrong line count for stuff like "MyOtherSheet!A:D" [#1215](https://github.com/PHPOffice/PhpSpreadsheet/issues/1215) +- Call garbage collector after removing a column to prevent stale cached values +- Trying to remove a column that doesn't exist deletes the latest column +- Keep big integer as integer instead of lossely casting to float [#874](https://github.com/PHPOffice/PhpSpreadsheet/pull/874) +- Fix branch pruning handling of non boolean conditions [#1167](https://github.com/PHPOffice/PhpSpreadsheet/pull/1167) +- Fix ODS Reader when no DC namespace are defined [#1182](https://github.com/PHPOffice/PhpSpreadsheet/pull/1182) +- Fixed Functions->ifCondition for allowing <> and empty condition [#1206](https://github.com/PHPOffice/PhpSpreadsheet/pull/1206) +- Validate XIRR inputs and return correct error values [#1120](https://github.com/PHPOffice/PhpSpreadsheet/issues/1120) +- Allow to read xlsx files with exotic workbook names like "workbook2.xml" [#1183](https://github.com/PHPOffice/PhpSpreadsheet/pull/1183) + +## [1.9.0] - 2019-08-17 + +### Changed + +- Drop support for PHP 5.6 and 7.0, according to https://phpspreadsheet.readthedocs.io/en/latest/#php-version-support + +### Added + +- When <br> appears in a table cell, set the cell to wrap [#1071](https://github.com/PHPOffice/PhpSpreadsheet/issues/1071) and [#1070](https://github.com/PHPOffice/PhpSpreadsheet/pull/1070) +- Add MAXIFS, MINIFS, COUNTIFS and Remove MINIF, MAXIF [#1056](https://github.com/PHPOffice/PhpSpreadsheet/issues/1056) +- HLookup needs an ordered list even if range_lookup is set to false [#1055](https://github.com/PHPOffice/PhpSpreadsheet/issues/1055) and [#1076](https://github.com/PHPOffice/PhpSpreadsheet/pull/1076) +- Improve performance of IF function calls via ranch pruning to avoid resolution of every branches [#844](https://github.com/PHPOffice/PhpSpreadsheet/pull/844) +- MATCH function supports `*?~` Excel functionality, when match_type=0 [#1116](https://github.com/PHPOffice/PhpSpreadsheet/issues/1116) +- Allow HTML Reader to accept HTML as a string [#1136](https://github.com/PHPOffice/PhpSpreadsheet/pull/1136) + +### Fixed + +- Fix to AVERAGEIF() function when called with a third argument +- Eliminate duplicate fill none style entries [#1066](https://github.com/PHPOffice/PhpSpreadsheet/issues/1066) +- Fix number format masks containing literal (non-decimal point) dots [#1079](https://github.com/PHPOffice/PhpSpreadsheet/issues/1079) +- Fix number format masks containing named colours that were being misinterpreted as date formats; and add support for masks that fully replace the value with a full text string [#1009](https://github.com/PHPOffice/PhpSpreadsheet/issues/1009) +- Stricter-typed comparison testing in COUNTIF() and COUNTIFS() evaluation [#1046](https://github.com/PHPOffice/PhpSpreadsheet/issues/1046) +- COUPNUM should not return zero when settlement is in the last period [#1020](https://github.com/PHPOffice/PhpSpreadsheet/issues/1020) and [#1021](https://github.com/PHPOffice/PhpSpreadsheet/pull/1021) +- Fix handling of named ranges referencing sheets with spaces or "!" in their title +- Cover `getSheetByName()` with tests for name with quote and spaces [#739](https://github.com/PHPOffice/PhpSpreadsheet/issues/739) +- Best effort to support invalid colspan values in HTML reader - [#878](https://github.com/PHPOffice/PhpSpreadsheet/pull/878) +- Fixes incorrect rows deletion [#868](https://github.com/PHPOffice/PhpSpreadsheet/issues/868) +- MATCH function fix (value search by type, stop search when match_type=-1 and unordered element encountered) [#1116](https://github.com/PHPOffice/PhpSpreadsheet/issues/1116) +- Fix `getCalculatedValue()` error with more than two INDIRECT [#1115](https://github.com/PHPOffice/PhpSpreadsheet/pull/1115) +- Writer\Html did not hide columns [#985](https://github.com/PHPOffice/PhpSpreadsheet/pull/985) + +## [1.8.2] - 2019-07-08 + +### Fixed + +- Uncaught error when opening ods file and properties aren't defined [#1047](https://github.com/PHPOffice/PhpSpreadsheet/issues/1047) +- Xlsx Reader Cell datavalidations bug [#1052](https://github.com/PHPOffice/PhpSpreadsheet/pull/1052) + +## [1.8.1] - 2019-07-02 + +### Fixed + +- Allow nullable theme for Xlsx Style Reader class [#1043](https://github.com/PHPOffice/PhpSpreadsheet/issues/1043) + +## [1.8.0] - 2019-07-01 + +### Security Fix (CVE-2019-12331) + +- Detect double-encoded xml in the Security scanner, and reject as suspicious. +- This change also broadens the scope of the `libxml_disable_entity_loader` setting when reading XML-based formats, so that it is enabled while the xml is being parsed and not simply while it is loaded. + On some versions of PHP, this can cause problems because it is not thread-safe, and can affect other PHP scripts running on the same server. This flag is set to true when instantiating a loader, and back to its original setting when the Reader is no longer in scope, or manually unset. +- Provide a check to identify whether libxml_disable_entity_loader is thread-safe or not. + + `XmlScanner::threadSafeLibxmlDisableEntityLoaderAvailability()` +- Provide an option to disable the libxml_disable_entity_loader call through settings. This is not recommended as it reduces the security of the XML-based readers, and should only be used if you understand the consequences and have no other choice. + +### Added + +- Added support for the SWITCH function [#963](https://github.com/PHPOffice/PhpSpreadsheet/issues/963) and [#983](https://github.com/PHPOffice/PhpSpreadsheet/pull/983) +- Add accounting number format style [#974](https://github.com/PHPOffice/PhpSpreadsheet/pull/974) + +### Fixed + +- Whitelist `tsv` extension when opening CSV files [#429](https://github.com/PHPOffice/PhpSpreadsheet/issues/429) +- Fix a SUMIF warning with some versions of PHP when having different length of arrays provided as input [#873](https://github.com/PHPOffice/PhpSpreadsheet/pull/873) +- Fix incorrectly handled backslash-escaped space characters in number format + +## [1.7.0] - 2019-05-26 + +- Added support for inline styles in Html reader (borders, alignment, width, height) +- QuotedText cells no longer treated as formulae if the content begins with a `=` +- Clean handling for DDE in formulae + +### Fixed + +- Fix handling for escaped enclosures and new lines in CSV Separator Inference +- Fix MATCH an error was appearing when comparing strings against 0 (always true) +- Fix wrong calculation of highest column with specified row [#700](https://github.com/PHPOffice/PhpSpreadsheet/issues/700) +- Fix VLOOKUP +- Fix return type hint + +## [1.6.0] - 2019-01-02 + +### Added + +- Refactored Matrix Functions to use external Matrix library +- Possibility to specify custom colors of values for pie and donut charts [#768](https://github.com/PHPOffice/PhpSpreadsheet/pull/768) + +### Fixed + +- Improve XLSX parsing speed if no readFilter is applied [#772](https://github.com/PHPOffice/PhpSpreadsheet/issues/772) +- Fix column names if read filter calls in XLSX reader skip columns [#777](https://github.com/PHPOffice/PhpSpreadsheet/pull/777) +- XLSX reader can now ignore blank cells, using the setReadEmptyCells(false) method. [#810](https://github.com/PHPOffice/PhpSpreadsheet/issues/810) +- Fix LOOKUP function which was breaking on edge cases [#796](https://github.com/PHPOffice/PhpSpreadsheet/issues/796) +- Fix VLOOKUP with exact matches [#809](https://github.com/PHPOffice/PhpSpreadsheet/pull/809) +- Support COUNTIFS multiple arguments [#830](https://github.com/PHPOffice/PhpSpreadsheet/pull/830) +- Change `libxml_disable_entity_loader()` as shortly as possible [#819](https://github.com/PHPOffice/PhpSpreadsheet/pull/819) +- Improved memory usage and performance when loading large spreadsheets [#822](https://github.com/PHPOffice/PhpSpreadsheet/pull/822) +- Improved performance when loading large spreadsheets [#825](https://github.com/PHPOffice/PhpSpreadsheet/pull/825) +- Improved performance when loading large spreadsheets [#824](https://github.com/PHPOffice/PhpSpreadsheet/pull/824) +- Fix color from CSS when reading from HTML [#831](https://github.com/PHPOffice/PhpSpreadsheet/pull/831) +- Fix infinite loop when reading invalid ODS files [#832](https://github.com/PHPOffice/PhpSpreadsheet/pull/832) +- Fix time format for duration is incorrect [#666](https://github.com/PHPOffice/PhpSpreadsheet/pull/666) +- Fix iconv unsupported `//IGNORE//TRANSLIT` on IBM i [#791](https://github.com/PHPOffice/PhpSpreadsheet/issues/791) + +### Changed + +- `master` is the new default branch, `develop` does not exist anymore + +## [1.5.2] - 2018-11-25 + +### Security + +- Improvements to the design of the XML Security Scanner [#771](https://github.com/PHPOffice/PhpSpreadsheet/issues/771) + +## [1.5.1] - 2018-11-20 + +### Security + +- Fix and improve XXE security scanning for XML-based and HTML Readers [#771](https://github.com/PHPOffice/PhpSpreadsheet/issues/771) + +### Added + +- Support page margin in mPDF [#750](https://github.com/PHPOffice/PhpSpreadsheet/issues/750) + +### Fixed + +- Support numeric condition in SUMIF, SUMIFS, AVERAGEIF, COUNTIF, MAXIF and MINIF [#683](https://github.com/PHPOffice/PhpSpreadsheet/issues/683) +- SUMIFS containing multiple conditions [#704](https://github.com/PHPOffice/PhpSpreadsheet/issues/704) +- Csv reader avoid notice when the file is empty [#743](https://github.com/PHPOffice/PhpSpreadsheet/pull/743) +- Fix print area parser for XLSX reader [#734](https://github.com/PHPOffice/PhpSpreadsheet/pull/734) +- Support overriding `DefaultValueBinder::dataTypeForValue()` without overriding `DefaultValueBinder::bindValue()` [#735](https://github.com/PHPOffice/PhpSpreadsheet/pull/735) +- Mpdf export can exceed pcre.backtrack_limit [#637](https://github.com/PHPOffice/PhpSpreadsheet/issues/637) +- Fix index overflow on data values array [#748](https://github.com/PHPOffice/PhpSpreadsheet/pull/748) + +## [1.5.0] - 2018-10-21 + +### Added + +- PHP 7.3 support +- Add the DAYS() function [#594](https://github.com/PHPOffice/PhpSpreadsheet/pull/594) + +### Fixed + +- Sheet title can contain exclamation mark [#325](https://github.com/PHPOffice/PhpSpreadsheet/issues/325) +- Xls file cause the exception during open by Xls reader [#402](https://github.com/PHPOffice/PhpSpreadsheet/issues/402) +- Skip non numeric value in SUMIF [#618](https://github.com/PHPOffice/PhpSpreadsheet/pull/618) +- OFFSET should allow omitted height and width [#561](https://github.com/PHPOffice/PhpSpreadsheet/issues/561) +- Correctly determine delimiter when CSV contains line breaks inside enclosures [#716](https://github.com/PHPOffice/PhpSpreadsheet/issues/716) + +## [1.4.1] - 2018-09-30 + +### Fixed + +- Remove locale from formatting string [#644](https://github.com/PHPOffice/PhpSpreadsheet/pull/644) +- Allow iterators to go out of bounds with prev [#587](https://github.com/PHPOffice/PhpSpreadsheet/issues/587) +- Fix warning when reading xlsx without styles [#631](https://github.com/PHPOffice/PhpSpreadsheet/pull/631) +- Fix broken sample links on windows due to $baseDir having backslash [#653](https://github.com/PHPOffice/PhpSpreadsheet/pull/653) + +## [1.4.0] - 2018-08-06 + +### Added + +- Add excel function EXACT(value1, value2) support [#595](https://github.com/PHPOffice/PhpSpreadsheet/pull/595) +- Support workbook view attributes for Xlsx format [#523](https://github.com/PHPOffice/PhpSpreadsheet/issues/523) +- Read and write hyperlink for drawing image [#490](https://github.com/PHPOffice/PhpSpreadsheet/pull/490) +- Added calculation engine support for the new bitwise functions that were added in MS Excel 2013 + - BITAND() Returns a Bitwise 'And' of two numbers + - BITOR() Returns a Bitwise 'Or' of two number + - BITXOR() Returns a Bitwise 'Exclusive Or' of two numbers + - BITLSHIFT() Returns a number shifted left by a specified number of bits + - BITRSHIFT() Returns a number shifted right by a specified number of bits +- Added calculation engine support for other new functions that were added in MS Excel 2013 and MS Excel 2016 + - Text Functions + - CONCAT() Synonym for CONCATENATE() + - NUMBERVALUE() Converts text to a number, in a locale-independent way + - UNICHAR() Synonym for CHAR() in PHPSpreadsheet, which has always used UTF-8 internally + - UNIORD() Synonym for ORD() in PHPSpreadsheet, which has always used UTF-8 internally + - TEXTJOIN() Joins together two or more text strings, separated by a delimiter + - Logical Functions + - XOR() Returns a logical Exclusive Or of all arguments + - Date/Time Functions + - ISOWEEKNUM() Returns the ISO 8601 week number of the year for a given date + - Lookup and Reference Functions + - FORMULATEXT() Returns a formula as a string + - Financial Functions + - PDURATION() Calculates the number of periods required for an investment to reach a specified value + - RRI() Calculates the interest rate required for an investment to grow to a specified future value + - Engineering Functions + - ERF.PRECISE() Returns the error function integrated between 0 and a supplied limit + - ERFC.PRECISE() Synonym for ERFC + - Math and Trig Functions + - SEC() Returns the secant of an angle + - SECH() Returns the hyperbolic secant of an angle + - CSC() Returns the cosecant of an angle + - CSCH() Returns the hyperbolic cosecant of an angle + - COT() Returns the cotangent of an angle + - COTH() Returns the hyperbolic cotangent of an angle + - ACOT() Returns the cotangent of an angle + - ACOTH() Returns the hyperbolic cotangent of an angle +- Refactored Complex Engineering Functions to use external complex number library +- Added calculation engine support for the new complex number functions that were added in MS Excel 2013 + - IMCOSH() Returns the hyperbolic cosine of a complex number + - IMCOT() Returns the cotangent of a complex number + - IMCSC() Returns the cosecant of a complex number + - IMCSCH() Returns the hyperbolic cosecant of a complex number + - IMSEC() Returns the secant of a complex number + - IMSECH() Returns the hyperbolic secant of a complex number + - IMSINH() Returns the hyperbolic sine of a complex number + - IMTAN() Returns the tangent of a complex number + +### Fixed + +- Fix ISFORMULA() function to work with a cell reference to another worksheet +- Xlsx reader crashed when reading a file with workbook protection [#553](https://github.com/PHPOffice/PhpSpreadsheet/pull/553) +- Cell formats with escaped spaces were causing incorrect date formatting [#557](https://github.com/PHPOffice/PhpSpreadsheet/issues/557) +- Could not open CSV file containing HTML fragment [#564](https://github.com/PHPOffice/PhpSpreadsheet/issues/564) +- Exclude the vendor folder in migration [#481](https://github.com/PHPOffice/PhpSpreadsheet/issues/481) +- Chained operations on cell ranges involving borders operated on last cell only [#428](https://github.com/PHPOffice/PhpSpreadsheet/issues/428) +- Avoid memory exhaustion when cloning worksheet with a drawing [#437](https://github.com/PHPOffice/PhpSpreadsheet/issues/437) +- Migration tool keep variables containing $PHPExcel untouched [#598](https://github.com/PHPOffice/PhpSpreadsheet/issues/598) +- Rowspans/colspans were incorrect when adding worksheet using loadIntoExisting [#619](https://github.com/PHPOffice/PhpSpreadsheet/issues/619) + +## [1.3.1] - 2018-06-12 + +### Fixed + +- Ranges across Z and AA columns incorrectly threw an exception [#545](https://github.com/PHPOffice/PhpSpreadsheet/issues/545) + +## [1.3.0] - 2018-06-10 + +### Added + +- Support to read Xlsm templates with form elements, macros, printer settings, protected elements and back compatibility drawing, and save result without losing important elements of document [#435](https://github.com/PHPOffice/PhpSpreadsheet/issues/435) +- Expose sheet title maximum length as `Worksheet::SHEET_TITLE_MAXIMUM_LENGTH` [#482](https://github.com/PHPOffice/PhpSpreadsheet/issues/482) +- Allow escape character to be set in CSV reader [#492](https://github.com/PHPOffice/PhpSpreadsheet/issues/492) + +### Fixed + +- Subtotal 9 in a group that has other subtotals 9 exclude the totals of the other subtotals in the range [#332](https://github.com/PHPOffice/PhpSpreadsheet/issues/332) +- `Helper\Html` support UTF-8 HTML input [#444](https://github.com/PHPOffice/PhpSpreadsheet/issues/444) +- Xlsx loaded an extra empty comment for each real comment [#375](https://github.com/PHPOffice/PhpSpreadsheet/issues/375) +- Xlsx reader do not read rows and columns filtered out in readFilter at all [#370](https://github.com/PHPOffice/PhpSpreadsheet/issues/370) +- Make newer Excel versions properly recalculate formulas on document open [#456](https://github.com/PHPOffice/PhpSpreadsheet/issues/456) +- `Coordinate::extractAllCellReferencesInRange()` throws an exception for an invalid range [#519](https://github.com/PHPOffice/PhpSpreadsheet/issues/519) +- Fixed parsing of conditionals in COUNTIF functions [#526](https://github.com/PHPOffice/PhpSpreadsheet/issues/526) +- Corruption errors for saved Xlsx docs with frozen panes [#532](https://github.com/PHPOffice/PhpSpreadsheet/issues/532) + +## [1.2.1] - 2018-04-10 + +### Fixed + +- Plain text and richtext mixed in same cell can be read [#442](https://github.com/PHPOffice/PhpSpreadsheet/issues/442) + +## [1.2.0] - 2018-03-04 + +### Added + +- HTML writer creates a generator meta tag [#312](https://github.com/PHPOffice/PhpSpreadsheet/issues/312) +- Support invalid zoom value in XLSX format [#350](https://github.com/PHPOffice/PhpSpreadsheet/pull/350) +- Support for `_xlfn.` prefixed functions and `ISFORMULA`, `MODE.SNGL`, `STDEV.S`, `STDEV.P` [#390](https://github.com/PHPOffice/PhpSpreadsheet/pull/390) + +### Fixed + +- Avoid potentially unsupported PSR-16 cache keys [#354](https://github.com/PHPOffice/PhpSpreadsheet/issues/354) +- Check for MIME type to know if CSV reader can read a file [#167](https://github.com/PHPOffice/PhpSpreadsheet/issues/167) +- Use proper € symbol for currency format [#379](https://github.com/PHPOffice/PhpSpreadsheet/pull/379) +- Read printing area correctly when skipping some sheets [#371](https://github.com/PHPOffice/PhpSpreadsheet/issues/371) +- Avoid incorrectly overwriting calculated value type [#394](https://github.com/PHPOffice/PhpSpreadsheet/issues/394) +- Select correct cell when calling freezePane [#389](https://github.com/PHPOffice/PhpSpreadsheet/issues/389) +- `setStrikethrough()` did not set the font [#403](https://github.com/PHPOffice/PhpSpreadsheet/issues/403) + +## [1.1.0] - 2018-01-28 + +### Added + +- Support for PHP 7.2 +- Support cell comments in HTML writer and reader [#308](https://github.com/PHPOffice/PhpSpreadsheet/issues/308) +- Option to stop at a conditional styling, if it matches (only XLSX format) [#292](https://github.com/PHPOffice/PhpSpreadsheet/pull/292) +- Support for line width for data series when rendering Xlsx [#329](https://github.com/PHPOffice/PhpSpreadsheet/pull/329) + +### Fixed + +- Better auto-detection of CSV separators [#305](https://github.com/PHPOffice/PhpSpreadsheet/issues/305) +- Support for shape style ending with `;` [#304](https://github.com/PHPOffice/PhpSpreadsheet/issues/304) +- Freeze Panes takes wrong coordinates for XLSX [#322](https://github.com/PHPOffice/PhpSpreadsheet/issues/322) +- `COLUMNS` and `ROWS` functions crashed in some cases [#336](https://github.com/PHPOffice/PhpSpreadsheet/issues/336) +- Support XML file without styles [#331](https://github.com/PHPOffice/PhpSpreadsheet/pull/331) +- Cell coordinates which are already a range cause an exception [#319](https://github.com/PHPOffice/PhpSpreadsheet/issues/319) + +## [1.0.0] - 2017-12-25 + +### Added + +- Support to write merged cells in ODS format [#287](https://github.com/PHPOffice/PhpSpreadsheet/issues/287) +- Able to set the `topLeftCell` in freeze panes [#261](https://github.com/PHPOffice/PhpSpreadsheet/pull/261) +- Support `DateTimeImmutable` as cell value +- Support migration of prefixed classes + +### Fixed + +- Can read very small HTML files [#194](https://github.com/PHPOffice/PhpSpreadsheet/issues/194) +- Written DataValidation was corrupted [#290](https://github.com/PHPOffice/PhpSpreadsheet/issues/290) +- Date format compatible with both LibreOffice and Excel [#298](https://github.com/PHPOffice/PhpSpreadsheet/issues/298) + +### BREAKING CHANGE + +- Constant `TYPE_DOUGHTNUTCHART` is now `TYPE_DOUGHNUTCHART`. + +## [1.0.0-beta2] - 2017-11-26 + +### Added + +- Support for chart fill color - @CrazyBite [#158](https://github.com/PHPOffice/PhpSpreadsheet/pull/158) +- Support for read Hyperlink for xml - @GreatHumorist [#223](https://github.com/PHPOffice/PhpSpreadsheet/pull/223) +- Support for cell value validation according to data validation rules - @SailorMax [#257](https://github.com/PHPOffice/PhpSpreadsheet/pull/257) +- Support for custom implementation, or configuration, of PDF libraries - @SailorMax [#266](https://github.com/PHPOffice/PhpSpreadsheet/pull/266) + +### Changed + +- Merge data-validations to reduce written worksheet size - @billblume [#131](https://github.com/PHPOffice/PhpSpreadSheet/issues/131) +- Throws exception if a XML file is invalid - @GreatHumorist [#222](https://github.com/PHPOffice/PhpSpreadsheet/pull/222) +- Upgrade to mPDF 7.0+ [#144](https://github.com/PHPOffice/PhpSpreadsheet/issues/144) + +### Fixed + +- Control characters in cell values are automatically escaped [#212](https://github.com/PHPOffice/PhpSpreadsheet/issues/212) +- Prevent color changing when copy/pasting xls files written by PhpSpreadsheet to another file - @al-lala [#218](https://github.com/PHPOffice/PhpSpreadsheet/issues/218) +- Add cell reference automatic when there is no cell reference('r' attribute) in Xlsx file. - @GreatHumorist [#225](https://github.com/PHPOffice/PhpSpreadsheet/pull/225) Refer to [#201](https://github.com/PHPOffice/PhpSpreadsheet/issues/201) +- `Reader\Xlsx::getFromZipArchive()` function return false if the zip entry could not be located. - @anton-harvey [#268](https://github.com/PHPOffice/PhpSpreadsheet/pull/268) + +### BREAKING CHANGE + +- Extracted coordinate method to dedicate class [migration guide](./docs/topics/migration-from-PHPExcel.md). +- Column indexes are based on 1, see the [migration guide](./docs/topics/migration-from-PHPExcel.md). +- Standardization of array keys used for style, see the [migration guide](./docs/topics/migration-from-PHPExcel.md). +- Easier usage of PDF writers, and other custom readers and writers, see the [migration guide](./docs/topics/migration-from-PHPExcel.md). +- Easier usage of chart renderers, see the [migration guide](./docs/topics/migration-from-PHPExcel.md). +- Rename a few more classes to keep them in their related namespaces: + - `CalcEngine` => `Calculation\Engine` + - `PhpSpreadsheet\Calculation` => `PhpSpreadsheet\Calculation\Calculation` + - `PhpSpreadsheet\Cell` => `PhpSpreadsheet\Cell\Cell` + - `PhpSpreadsheet\Chart` => `PhpSpreadsheet\Chart\Chart` + - `PhpSpreadsheet\RichText` => `PhpSpreadsheet\RichText\RichText` + - `PhpSpreadsheet\Style` => `PhpSpreadsheet\Style\Style` + - `PhpSpreadsheet\Worksheet` => `PhpSpreadsheet\Worksheet\Worksheet` + +## [1.0.0-beta] - 2017-08-17 + +### Added + +- Initial implementation of SUMIFS() function +- Additional codepages +- MemoryDrawing not working in HTML writer [#808](https://github.com/PHPOffice/PHPExcel/issues/808) +- CSV Reader can auto-detect the separator used in file [#141](https://github.com/PHPOffice/PhpSpreadsheet/pull/141) +- HTML Reader supports some basic inline styles [#180](https://github.com/PHPOffice/PhpSpreadsheet/pull/180) + +### Changed + +- Start following [SemVer](https://semver.org) properly. + +### Fixed + +- Fix to getCell() method when cell reference includes a worksheet reference - @MarkBaker +- Ignore inlineStr type if formula element exists - @ncrypthic [#570](https://github.com/PHPOffice/PHPExcel/issues/570) +- Excel 2007 Reader freezes because of conditional formatting - @rentalhost [#575](https://github.com/PHPOffice/PHPExcel/issues/575) +- Readers will now parse files containing worksheet titles over 31 characters [#176](https://github.com/PHPOffice/PhpSpreadsheet/pull/176) + +### General + +- Whitespace after toRichTextObject() - @MarkBaker [#554](https://github.com/PHPOffice/PHPExcel/issues/554) +- Optimize vlookup() sort - @umpirsky [#548](https://github.com/PHPOffice/PHPExcel/issues/548) +- c:max and c:min elements shall NOT be inside c:orientation elements - @vitalyrepin [#869](https://github.com/PHPOffice/PHPExcel/pull/869) +- Implement actual timezone adjustment into PHPExcel_Shared_Date::PHPToExcel - @sim642 [#489](https://github.com/PHPOffice/PHPExcel/pull/489) + +### BREAKING CHANGE + +- Introduction of namespaces for all classes, eg: `PHPExcel_Calculation_Functions` becomes `PhpOffice\PhpSpreadsheet\Calculation\Functions` +- Some classes were renamed for clarity and/or consistency: + +For a comprehensive list of all class changes, and a semi-automated migration path, read the [migration guide](./docs/topics/migration-from-PHPExcel.md). + +- Dropped `PHPExcel_Calculation_Functions::VERSION()`. Composer or git should be used to know the version. +- Dropped `PHPExcel_Settings::setPdfRenderer()` and `PHPExcel_Settings::setPdfRenderer()`. Composer should be used to autoload PDF libs. +- Dropped support for HHVM + +## Previous versions of PHPExcel + +The changelog for the project when it was called PHPExcel is [still available](./CHANGELOG.PHPExcel.md). diff --git a/vendor/phpoffice/phpspreadsheet/CONTRIBUTING.md b/vendor/phpoffice/phpspreadsheet/CONTRIBUTING.md new file mode 100644 index 0000000..aed13fe --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/CONTRIBUTING.md @@ -0,0 +1,11 @@ +# Want to contribute? + +If you would like to contribute, here are some notes and guidelines: + + - All new development happens on feature/fix branches, and are then merged to the `master` branch once stable; so the `master` branch is always the most up-to-date, working code + - Tagged releases are made from the `master` branch + - If you are going to be submitting a pull request, please fork from `master`, and submit your pull request back as a fix/feature branch referencing the GitHub issue number + - Code style might be automatically fixed by `composer fix` + - All code changes must be validated by `composer check` + - [Helpful article about forking](https://help.github.com/articles/fork-a-repo/ "Forking a GitHub repository") + - [Helpful article about pull requests](https://help.github.com/articles/using-pull-requests/ "Pull Requests") diff --git a/vendor/phpoffice/phpspreadsheet/LICENSE b/vendor/phpoffice/phpspreadsheet/LICENSE new file mode 100644 index 0000000..3ec5723 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 PhpSpreadsheet Authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +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/phpoffice/phpspreadsheet/bin/generate-document b/vendor/phpoffice/phpspreadsheet/bin/generate-document new file mode 100644 index 0000000..10ac811 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/bin/generate-document @@ -0,0 +1,24 @@ +#!/usr/bin/env php +getProperty('phpSpreadsheetFunctions'); + $phpSpreadsheetFunctionsProperty->setAccessible(true); + $phpSpreadsheetFunctions = $phpSpreadsheetFunctionsProperty->getValue(); + ksort($phpSpreadsheetFunctions); + + file_put_contents(__DIR__ . '/../docs/references/function-list-by-category.md', + DocumentGenerator::generateFunctionListByCategory($phpSpreadsheetFunctions) + ); + file_put_contents(__DIR__ . '/../docs/references/function-list-by-name.md', + DocumentGenerator::generateFunctionListByName($phpSpreadsheetFunctions) + ); +} catch (ReflectionException $e) { + fwrite(STDERR, (string)$e); + exit(1); +} diff --git a/vendor/phpoffice/phpspreadsheet/bin/migrate-from-phpexcel b/vendor/phpoffice/phpspreadsheet/bin/migrate-from-phpexcel new file mode 100644 index 0000000..51c60d4 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/bin/migrate-from-phpexcel @@ -0,0 +1,8 @@ +#!/usr/bin/env php +migrate(); diff --git a/vendor/phpoffice/phpspreadsheet/bin/pre-commit b/vendor/phpoffice/phpspreadsheet/bin/pre-commit new file mode 100644 index 0000000..8d93f8a --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/bin/pre-commit @@ -0,0 +1,33 @@ +#!/bin/bash + +pass=true + +files=$(git diff --cached --name-only --diff-filter=ACMR | grep -E '\.(php|phtml)$') +if [ "$files" != "" ]; then + + # Run php syntax check before commit + while read -r file; do + php -l "$file" + if [ $? -ne 0 ]; then + pass=false + fi + done <<< "$files" + + # Run php-cs-fixer validation before commit + echo "$files" | xargs ./vendor/bin/php-cs-fixer fix --diff --config .php_cs.dist + if [ $? -ne 0 ]; then + pass=false + fi + + # Automatically add files that may have been fixed by php-cs-fixer + echo "$files" | xargs git add +fi + +if $pass; then + exit 0 +else + echo "" + echo "PRE-COMMIT HOOK FAILED:" + echo "Code style validation failed. Please fix errors and try committing again." + exit 1 +fi diff --git a/vendor/phpoffice/phpspreadsheet/composer.json b/vendor/phpoffice/phpspreadsheet/composer.json new file mode 100644 index 0000000..cfff1cb --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/composer.json @@ -0,0 +1,86 @@ +{ + "name": "phpoffice/phpspreadsheet", + "description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine", + "keywords": ["PHP", "OpenXML", "Excel", "xlsx", "xls", "ods", "gnumeric", "spreadsheet"], + "homepage": "https://github.com/PHPOffice/PhpSpreadsheet", + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "Maarten Balliauw", + "homepage": "https://blog.maartenballiauw.be" + }, + { + "name": "Mark Baker", + "homepage": "https://markbakeruk.net" + }, + { + "name": "Franck Lefevre", + "homepage": "https://rootslabs.net" + }, + { + "name": "Erik Tilt" + }, + { + "name": "Adrien Crivelli" + } + ], + "scripts": { + "check": [ + "php-cs-fixer fix --ansi --dry-run --diff", + "phpcs --report-width=200 samples/ src/ tests/ --ignore=samples/Header.php --standard=PSR2 -n", + "phpunit --color=always" + ], + "fix": [ + "php-cs-fixer fix --ansi" + ], + "versions": [ + "phpcs --report-width=200 samples/ src/ tests/ --ignore=samples/Header.php --standard=PHPCompatibility --runtime-set testVersion 7.1- -n" + ] + }, + "require": { + "php": "^7.1", + "ext-ctype": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-iconv": "*", + "ext-fileinfo": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-SimpleXML": "*", + "ext-xml": "*", + "ext-xmlreader": "*", + "ext-xmlwriter": "*", + "ext-zip": "*", + "ext-zlib": "*", + "markbaker/complex": "^1.4", + "markbaker/matrix": "^1.2", + "psr/simple-cache": "^1.0" + }, + "require-dev": { + "dompdf/dompdf": "^0.8.3", + "friendsofphp/php-cs-fixer": "^2.16", + "jpgraph/jpgraph": "^4.0", + "mpdf/mpdf": "^8.0", + "phpcompatibility/php-compatibility": "^9.3", + "phpunit/phpunit": "^7.5", + "squizlabs/php_codesniffer": "^3.5", + "tecnickcom/tcpdf": "^6.3" + }, + "suggest": { + "mpdf/mpdf": "Option for rendering PDF with PDF Writer", + "dompdf/dompdf": "Option for rendering PDF with PDF Writer", + "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer", + "jpgraph/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers" + }, + "autoload": { + "psr-4": { + "PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet" + } + }, + "autoload-dev": { + "psr-4": { + "PhpOffice\\PhpSpreadsheetTests\\": "tests/PhpSpreadsheetTests" + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/composer.lock b/vendor/phpoffice/phpspreadsheet/composer.lock new file mode 100644 index 0000000..9299919 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/composer.lock @@ -0,0 +1,3503 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "0fd32acfbb0d21f168f495840ffc8d7e", + "packages": [ + { + "name": "markbaker/complex", + "version": "1.4.7", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPComplex.git", + "reference": "1ea674a8308baf547cbcbd30c5fcd6d301b7c000" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/1ea674a8308baf547cbcbd30c5fcd6d301b7c000", + "reference": "1ea674a8308baf547cbcbd30c5fcd6d301b7c000", + "shasum": "" + }, + "require": { + "php": "^5.6.0|^7.0.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.4.3", + "phpcompatibility/php-compatibility": "^8.0", + "phpdocumentor/phpdocumentor": "2.*", + "phploc/phploc": "2.*", + "phpmd/phpmd": "2.*", + "phpunit/phpunit": "^4.8.35|^5.4.0", + "sebastian/phpcpd": "2.*", + "squizlabs/php_codesniffer": "^3.3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Complex\\": "classes/src/" + }, + "files": [ + "classes/src/functions/abs.php", + "classes/src/functions/acos.php", + "classes/src/functions/acosh.php", + "classes/src/functions/acot.php", + "classes/src/functions/acoth.php", + "classes/src/functions/acsc.php", + "classes/src/functions/acsch.php", + "classes/src/functions/argument.php", + "classes/src/functions/asec.php", + "classes/src/functions/asech.php", + "classes/src/functions/asin.php", + "classes/src/functions/asinh.php", + "classes/src/functions/atan.php", + "classes/src/functions/atanh.php", + "classes/src/functions/conjugate.php", + "classes/src/functions/cos.php", + "classes/src/functions/cosh.php", + "classes/src/functions/cot.php", + "classes/src/functions/coth.php", + "classes/src/functions/csc.php", + "classes/src/functions/csch.php", + "classes/src/functions/exp.php", + "classes/src/functions/inverse.php", + "classes/src/functions/ln.php", + "classes/src/functions/log2.php", + "classes/src/functions/log10.php", + "classes/src/functions/negative.php", + "classes/src/functions/pow.php", + "classes/src/functions/rho.php", + "classes/src/functions/sec.php", + "classes/src/functions/sech.php", + "classes/src/functions/sin.php", + "classes/src/functions/sinh.php", + "classes/src/functions/sqrt.php", + "classes/src/functions/tan.php", + "classes/src/functions/tanh.php", + "classes/src/functions/theta.php", + "classes/src/operations/add.php", + "classes/src/operations/subtract.php", + "classes/src/operations/multiply.php", + "classes/src/operations/divideby.php", + "classes/src/operations/divideinto.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@lange.demon.co.uk" + } + ], + "description": "PHP Class for working with complex numbers", + "homepage": "https://github.com/MarkBaker/PHPComplex", + "keywords": [ + "complex", + "mathematics" + ], + "time": "2018-10-13T23:28:42+00:00" + }, + { + "name": "markbaker/matrix", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPMatrix.git", + "reference": "5348c5a67e3b75cd209d70103f916a93b1f1ed21" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/5348c5a67e3b75cd209d70103f916a93b1f1ed21", + "reference": "5348c5a67e3b75cd209d70103f916a93b1f1ed21", + "shasum": "" + }, + "require": { + "php": "^5.6.0|^7.0.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-master", + "phpcompatibility/php-compatibility": "dev-master", + "phploc/phploc": "^4", + "phpmd/phpmd": "dev-master", + "phpunit/phpunit": "^5.7", + "sebastian/phpcpd": "^3.0", + "squizlabs/php_codesniffer": "^3.0@dev" + }, + "type": "library", + "autoload": { + "psr-4": { + "Matrix\\": "classes/src/" + }, + "files": [ + "classes/src/functions/adjoint.php", + "classes/src/functions/antidiagonal.php", + "classes/src/functions/cofactors.php", + "classes/src/functions/determinant.php", + "classes/src/functions/diagonal.php", + "classes/src/functions/identity.php", + "classes/src/functions/inverse.php", + "classes/src/functions/minors.php", + "classes/src/functions/trace.php", + "classes/src/functions/transpose.php", + "classes/src/operations/add.php", + "classes/src/operations/directsum.php", + "classes/src/operations/subtract.php", + "classes/src/operations/multiply.php", + "classes/src/operations/divideby.php", + "classes/src/operations/divideinto.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@lange.demon.co.uk" + } + ], + "description": "PHP Class for working with matrices", + "homepage": "https://github.com/MarkBaker/PHPMatrix", + "keywords": [ + "mathematics", + "matrix", + "vector" + ], + "time": "2019-10-06T11:29:25+00:00" + }, + { + "name": "psr/simple-cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "time": "2017-10-23T01:57:42+00:00" + } + ], + "packages-dev": [ + { + "name": "composer/semver", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "46d9139568ccb8d9e7cdd4539cab7347568a5e2e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/46d9139568ccb8d9e7cdd4539cab7347568a5e2e", + "reference": "46d9139568ccb8d9e7cdd4539cab7347568a5e2e", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.5 || ^5.0.5", + "phpunit/phpunit-mock-objects": "2.3.0 || ^3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "time": "2019-03-19T17:25:45+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "cbe23383749496fe0f373345208b79568e4bc248" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/cbe23383749496fe0f373345208b79568e4bc248", + "reference": "cbe23383749496fe0f373345208b79568e4bc248", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0", + "psr/log": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "time": "2019-11-06T16:40:04+00:00" + }, + { + "name": "doctrine/annotations", + "version": "v1.8.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/annotations.git", + "reference": "904dca4eb10715b92569fbcd79e201d5c349b6bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/904dca4eb10715b92569fbcd79e201d5c349b6bc", + "reference": "904dca4eb10715b92569fbcd79e201d5c349b6bc", + "shasum": "" + }, + "require": { + "doctrine/lexer": "1.*", + "php": "^7.1" + }, + "require-dev": { + "doctrine/cache": "1.*", + "phpunit/phpunit": "^7.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Docblock Annotations Parser", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "annotations", + "docblock", + "parser" + ], + "time": "2019-10-01T18:55:10+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "a2c590166b2133a4633738648b6b064edae0814a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/a2c590166b2133a4633738648b6b064edae0814a", + "reference": "a2c590166b2133a4633738648b6b064edae0814a", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.13", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-shim": "^0.11", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2019-03-17T17:37:11+00:00" + }, + { + "name": "doctrine/lexer", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "1febd6c3ef84253d7c815bed85fc622ad207a9f8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/1febd6c3ef84253d7c815bed85fc622ad207a9f8", + "reference": "1febd6c3ef84253d7c815bed85fc622ad207a9f8", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "^4.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "keywords": [ + "annotations", + "docblock", + "lexer", + "parser", + "php" + ], + "time": "2019-06-08T11:03:04+00:00" + }, + { + "name": "dompdf/dompdf", + "version": "v0.8.3", + "source": { + "type": "git", + "url": "https://github.com/dompdf/dompdf.git", + "reference": "75f13c700009be21a1965dc2c5b68a8708c22ba2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/dompdf/zipball/75f13c700009be21a1965dc2c5b68a8708c22ba2", + "reference": "75f13c700009be21a1965dc2c5b68a8708c22ba2", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "phenx/php-font-lib": "0.5.*", + "phenx/php-svg-lib": "0.3.*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8|^5.5|^6.5", + "squizlabs/php_codesniffer": "2.*" + }, + "suggest": { + "ext-gd": "Needed to process images", + "ext-gmagick": "Improves image processing performance", + "ext-imagick": "Improves image processing performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-develop": "0.7-dev" + } + }, + "autoload": { + "psr-4": { + "Dompdf\\": "src/" + }, + "classmap": [ + "lib/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1" + ], + "authors": [ + { + "name": "Fabien Ménager", + "email": "fabien.menager@gmail.com" + }, + { + "name": "Brian Sweeney", + "email": "eclecticgeek@gmail.com" + }, + { + "name": "Gabriel Bull", + "email": "me@gabrielbull.com" + } + ], + "description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter", + "homepage": "https://github.com/dompdf/dompdf", + "time": "2018-12-14T02:40:31+00:00" + }, + { + "name": "friendsofphp/php-cs-fixer", + "version": "v2.16.1", + "source": { + "type": "git", + "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", + "reference": "c8afb599858876e95e8ebfcd97812d383fa23f02" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/c8afb599858876e95e8ebfcd97812d383fa23f02", + "reference": "c8afb599858876e95e8ebfcd97812d383fa23f02", + "shasum": "" + }, + "require": { + "composer/semver": "^1.4", + "composer/xdebug-handler": "^1.2", + "doctrine/annotations": "^1.2", + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^5.6 || ^7.0", + "php-cs-fixer/diff": "^1.3", + "symfony/console": "^3.4.17 || ^4.1.6 || ^5.0", + "symfony/event-dispatcher": "^3.0 || ^4.0 || ^5.0", + "symfony/filesystem": "^3.0 || ^4.0 || ^5.0", + "symfony/finder": "^3.0 || ^4.0 || ^5.0", + "symfony/options-resolver": "^3.0 || ^4.0 || ^5.0", + "symfony/polyfill-php70": "^1.0", + "symfony/polyfill-php72": "^1.4", + "symfony/process": "^3.0 || ^4.0 || ^5.0", + "symfony/stopwatch": "^3.0 || ^4.0 || ^5.0" + }, + "require-dev": { + "johnkary/phpunit-speedtrap": "^1.1 || ^2.0 || ^3.0", + "justinrainbow/json-schema": "^5.0", + "keradus/cli-executor": "^1.2", + "mikey179/vfsstream": "^1.6", + "php-coveralls/php-coveralls": "^2.1", + "php-cs-fixer/accessible-object": "^1.0", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.1", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.1", + "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.1", + "phpunitgoodpractices/traits": "^1.8", + "symfony/phpunit-bridge": "^4.3 || ^5.0", + "symfony/yaml": "^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "ext-mbstring": "For handling non-UTF8 characters in cache signature.", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "For IsIdenticalString constraint.", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "For XmlMatchesXsd constraint.", + "symfony/polyfill-mbstring": "When enabling `ext-mbstring` is not possible." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "autoload": { + "psr-4": { + "PhpCsFixer\\": "src/" + }, + "classmap": [ + "tests/Test/AbstractFixerTestCase.php", + "tests/Test/AbstractIntegrationCaseFactory.php", + "tests/Test/AbstractIntegrationTestCase.php", + "tests/Test/Assert/AssertTokensTrait.php", + "tests/Test/IntegrationCase.php", + "tests/Test/IntegrationCaseFactory.php", + "tests/Test/IntegrationCaseFactoryInterface.php", + "tests/Test/InternalIntegrationCaseFactory.php", + "tests/TestCase.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "time": "2019-11-25T22:10:32+00:00" + }, + { + "name": "jpgraph/jpgraph", + "version": "4.0.2", + "source": { + "type": "git", + "url": "https://github.com/ztec/JpGraph.git", + "reference": "e82db7da6a546d3926c24c9a346226da7aa49094" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ztec/JpGraph/zipball/e82db7da6a546d3926c24c9a346226da7aa49094", + "reference": "e82db7da6a546d3926c24c9a346226da7aa49094", + "shasum": "" + }, + "type": "library", + "autoload": { + "classmap": [ + "lib/JpGraph.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "QPL 1.0" + ], + "authors": [ + { + "name": "JpGraph team" + } + ], + "description": "jpGraph, library to make graphs and charts", + "homepage": "http://jpgraph.net/", + "keywords": [ + "chart", + "data", + "graph", + "jpgraph", + "pie" + ], + "time": "2017-02-23T09:44:15+00:00" + }, + { + "name": "mpdf/mpdf", + "version": "v8.0.4", + "source": { + "type": "git", + "url": "https://github.com/mpdf/mpdf.git", + "reference": "d3147a0d790b6d11936fd9c73fa31a7ed45e3f6f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mpdf/mpdf/zipball/d3147a0d790b6d11936fd9c73fa31a7ed45e3f6f", + "reference": "d3147a0d790b6d11936fd9c73fa31a7ed45e3f6f", + "shasum": "" + }, + "require": { + "ext-gd": "*", + "ext-mbstring": "*", + "myclabs/deep-copy": "^1.7", + "paragonie/random_compat": "^1.4|^2.0|9.99.99", + "php": "^5.6 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0", + "psr/log": "^1.0", + "setasign/fpdi": "^2.1" + }, + "require-dev": { + "mockery/mockery": "^0.9.5", + "mpdf/qrcode": "^1.0.0", + "phpunit/phpunit": "^5.0", + "squizlabs/php_codesniffer": "^3.5.0", + "tracy/tracy": "^2.4" + }, + "suggest": { + "ext-bcmath": "Needed for generation of some types of barcodes", + "ext-xml": "Needed mainly for SVG manipulation", + "ext-zlib": "Needed for compression of embedded resources, such as fonts" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-development": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "Mpdf\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-only" + ], + "authors": [ + { + "name": "Matěj Humpál", + "role": "Developer, maintainer" + }, + { + "name": "Ian Back", + "role": "Developer (retired)" + } + ], + "description": "PHP library generating PDF files from UTF-8 encoded HTML", + "homepage": "https://mpdf.github.io", + "keywords": [ + "pdf", + "php", + "utf-8" + ], + "time": "2019-11-28T09:39:33+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.9.3", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/007c053ae6f31bba39dfa19a7726f56e9763bbea", + "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "replace": { + "myclabs/deep-copy": "self.version" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "time": "2019-08-09T12:45:53+00:00" + }, + { + "name": "paragonie/random_compat", + "version": "v9.99.99", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", + "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", + "shasum": "" + }, + "require": { + "php": "^7" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "polyfill", + "pseudorandom", + "random" + ], + "time": "2018-07-02T15:55:56+00:00" + }, + { + "name": "phar-io/manifest", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", + "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "phar-io/version": "^2.0", + "php": "^5.6 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "time": "2018-07-08T19:23:20+00:00" + }, + { + "name": "phar-io/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/45a2ec53a73c70ce41d55cedef9063630abaf1b6", + "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "time": "2018-07-08T19:19:57+00:00" + }, + { + "name": "phenx/php-font-lib", + "version": "0.5.1", + "source": { + "type": "git", + "url": "https://github.com/PhenX/php-font-lib.git", + "reference": "760148820110a1ae0936e5cc35851e25a938bc97" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PhenX/php-font-lib/zipball/760148820110a1ae0936e5cc35851e25a938bc97", + "reference": "760148820110a1ae0936e5cc35851e25a938bc97", + "shasum": "" + }, + "require-dev": { + "phpunit/phpunit": "^4.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "FontLib\\": "src/FontLib" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0" + ], + "authors": [ + { + "name": "Fabien Ménager", + "email": "fabien.menager@gmail.com" + } + ], + "description": "A library to read, parse, export and make subsets of different types of font files.", + "homepage": "https://github.com/PhenX/php-font-lib", + "time": "2017-09-13T16:14:37+00:00" + }, + { + "name": "phenx/php-svg-lib", + "version": "v0.3.3", + "source": { + "type": "git", + "url": "https://github.com/PhenX/php-svg-lib.git", + "reference": "5fa61b65e612ce1ae15f69b3d223cb14ecc60e32" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PhenX/php-svg-lib/zipball/5fa61b65e612ce1ae15f69b3d223cb14ecc60e32", + "reference": "5fa61b65e612ce1ae15f69b3d223cb14ecc60e32", + "shasum": "" + }, + "require": { + "sabberworm/php-css-parser": "^8.3" + }, + "require-dev": { + "phpunit/phpunit": "^5.5|^6.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Svg\\": "src/Svg" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0" + ], + "authors": [ + { + "name": "Fabien Ménager", + "email": "fabien.menager@gmail.com" + } + ], + "description": "A library to read, parse and export to PDF SVG files.", + "homepage": "https://github.com/PhenX/php-svg-lib", + "time": "2019-09-11T20:02:13+00:00" + }, + { + "name": "php-cs-fixer/diff", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/diff.git", + "reference": "78bb099e9c16361126c86ce82ec4405ebab8e756" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/diff/zipball/78bb099e9c16361126c86ce82ec4405ebab8e756", + "reference": "78bb099e9c16361126c86ce82ec4405ebab8e756", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.23 || ^6.4.3", + "symfony/process": "^3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "SpacePossum" + } + ], + "description": "sebastian/diff v2 backport support for PHP5.6", + "homepage": "https://github.com/PHP-CS-Fixer", + "keywords": [ + "diff" + ], + "time": "2018-02-15T16:58:55+00:00" + }, + { + "name": "phpcompatibility/php-compatibility", + "version": "9.3.2", + "source": { + "type": "git", + "url": "https://github.com/PHPCompatibility/PHPCompatibility.git", + "reference": "bfca2be3992f40e92206e5a7ebe5eaee37280b58" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/bfca2be3992f40e92206e5a7ebe5eaee37280b58", + "reference": "bfca2be3992f40e92206e5a7ebe5eaee37280b58", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.3 || ^3.0.2" + }, + "conflict": { + "squizlabs/php_codesniffer": "2.6.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.5 || ^5.0 || ^6.0 || ^7.0" + }, + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically.", + "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Wim Godden", + "homepage": "https://github.com/wimg", + "role": "lead" + }, + { + "name": "Juliette Reinders Folmer", + "homepage": "https://github.com/jrfnl", + "role": "lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCompatibility/PHPCompatibility/graphs/contributors" + } + ], + "description": "A set of sniffs for PHP_CodeSniffer that checks for PHP cross-version compatibility.", + "homepage": "http://techblog.wimgodden.be/tag/codesniffer/", + "keywords": [ + "compatibility", + "phpcs", + "standards" + ], + "time": "2019-10-16T21:24:24+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/63a995caa1ca9e5590304cd845c15ad6d482a62a", + "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "~6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2018-08-07T13:53:10+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "4.3.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "b83ff7cfcfee7827e1e78b637a5904fe6a96698e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/b83ff7cfcfee7827e1e78b637a5904fe6a96698e", + "reference": "b83ff7cfcfee7827e1e78b637a5904fe6a96698e", + "shasum": "" + }, + "require": { + "php": "^7.0", + "phpdocumentor/reflection-common": "^1.0.0 || ^2.0.0", + "phpdocumentor/type-resolver": "~0.4 || ^1.0.0", + "webmozart/assert": "^1.0" + }, + "require-dev": { + "doctrine/instantiator": "^1.0.5", + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2019-09-12T14:27:41+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/2e32a6d48972b2c1976ed5d8967145b6cec4a4a9", + "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9", + "shasum": "" + }, + "require": { + "php": "^7.1", + "phpdocumentor/reflection-common": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "^7.1", + "mockery/mockery": "~1", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "time": "2019-08-22T18:11:29+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "1.9.0", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "f6811d96d97bdf400077a0cc100ae56aa32b9203" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/f6811d96d97bdf400077a0cc100ae56aa32b9203", + "reference": "f6811d96d97bdf400077a0cc100ae56aa32b9203", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", + "sebastian/comparator": "^1.1|^2.0|^3.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5|^3.2", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8.x-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\": "src/Prophecy" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2019-10-03T11:07:50+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "6.1.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/807e6013b00af69b6c5d9ceb4282d0393dbb9d8d", + "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlwriter": "*", + "php": "^7.1", + "phpunit/php-file-iterator": "^2.0", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-token-stream": "^3.0", + "sebastian/code-unit-reverse-lookup": "^1.0.1", + "sebastian/environment": "^3.1 || ^4.0", + "sebastian/version": "^2.0.1", + "theseer/tokenizer": "^1.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.0" + }, + "suggest": { + "ext-xdebug": "^2.6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2018-10-31T16:06:48+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "050bedf145a257b1ff02746c31894800e5122946" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/050bedf145a257b1ff02746c31894800e5122946", + "reference": "050bedf145a257b1ff02746c31894800e5122946", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2018-09-13T20:33:42+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21T13:50:34+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "2.1.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "1038454804406b0b5f5f520358e78c1c2f71501e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/1038454804406b0b5f5f520358e78c1c2f71501e", + "reference": "1038454804406b0b5f5f520358e78c1c2f71501e", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2019-06-07T04:22:29+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/995192df77f63a59e47f025390d2d1fdf8f425ff", + "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2019-09-17T06:23:10+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "7.5.17", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "4c92a15296e58191a4cd74cff3b34fc8e374174a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/4c92a15296e58191a4cd74cff3b34fc8e374174a", + "reference": "4c92a15296e58191a4cd74cff3b34fc8e374174a", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.1", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "myclabs/deep-copy": "^1.7", + "phar-io/manifest": "^1.0.2", + "phar-io/version": "^2.0", + "php": "^7.1", + "phpspec/prophecy": "^1.7", + "phpunit/php-code-coverage": "^6.0.7", + "phpunit/php-file-iterator": "^2.0.1", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-timer": "^2.1", + "sebastian/comparator": "^3.0", + "sebastian/diff": "^3.0", + "sebastian/environment": "^4.0", + "sebastian/exporter": "^3.1", + "sebastian/global-state": "^2.0", + "sebastian/object-enumerator": "^3.0.3", + "sebastian/resource-operations": "^2.0", + "sebastian/version": "^2.0.1" + }, + "conflict": { + "phpunit/phpunit-mock-objects": "*" + }, + "require-dev": { + "ext-pdo": "*" + }, + "suggest": { + "ext-soap": "*", + "ext-xdebug": "*", + "phpunit/php-invoker": "^2.0" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.5-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2019-10-28T10:37:36+00:00" + }, + { + "name": "psr/container", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "time": "2017-02-14T16:28:37+00:00" + }, + { + "name": "psr/log", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/446d54b4cb6bf489fc9d75f55843658e6f25d801", + "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2019-11-01T11:05:21+00:00" + }, + { + "name": "sabberworm/php-css-parser", + "version": "8.3.0", + "source": { + "type": "git", + "url": "https://github.com/sabberworm/PHP-CSS-Parser.git", + "reference": "91bcc3e3fdb7386c9a2e0e0aa09ca75cc43f121f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sabberworm/PHP-CSS-Parser/zipball/91bcc3e3fdb7386c9a2e0e0aa09ca75cc43f121f", + "reference": "91bcc3e3fdb7386c9a2e0e0aa09ca75cc43f121f", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "codacy/coverage": "^1.4", + "phpunit/phpunit": "~4.8" + }, + "type": "library", + "autoload": { + "psr-0": { + "Sabberworm\\CSS": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Raphael Schweikert" + } + ], + "description": "Parser for CSS Files written in PHP", + "homepage": "http://www.sabberworm.com/blog/2010/6/10/php-css-parser", + "keywords": [ + "css", + "parser", + "stylesheet" + ], + "time": "2019-02-22T07:42:52+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "time": "2017-03-04T06:30:41+00:00" + }, + { + "name": "sebastian/comparator", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/5de4fc177adf9bce8df98d8d141a7559d7ccf6da", + "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da", + "shasum": "" + }, + "require": { + "php": "^7.1", + "sebastian/diff": "^3.0", + "sebastian/exporter": "^3.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2018-07-12T15:12:46+00:00" + }, + { + "name": "sebastian/diff", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/720fcc7e9b5cf384ea68d9d930d480907a0c1a29", + "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.5 || ^8.0", + "symfony/process": "^2 || ^3.3 || ^4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "time": "2019-02-04T06:01:07+00:00" + }, + { + "name": "sebastian/environment", + "version": "4.2.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "f2a2c8e1c97c11ace607a7a667d73d47c19fe404" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/f2a2c8e1c97c11ace607a7a667d73d47c19fe404", + "reference": "f2a2c8e1c97c11ace607a7a667d73d47c19fe404", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.5" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2019-05-05T09:05:15+00:00" + }, + { + "name": "sebastian/exporter", + "version": "3.1.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/68609e1261d215ea5b21b7987539cbfbe156ec3e", + "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2019-09-14T09:02:43+00:00" + }, + { + "name": "sebastian/global-state", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2017-04-27T15:39:26+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/object-reflector": "^1.1.1", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "time": "2017-08-03T12:35:26+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "773f97c67f28de00d397be301821b06708fca0be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", + "reference": "773f97c67f28de00d397be301821b06708fca0be", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "time": "2017-03-29T09:07:27+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2017-03-03T06:23:57+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/4d7a795d35b889bf80a0cc04e08d77cedfa917a9", + "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "time": "2018-10-04T04:07:39+00:00" + }, + { + "name": "sebastian/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2016-10-03T07:35:21+00:00" + }, + { + "name": "setasign/fpdi", + "version": "v2.2.0", + "source": { + "type": "git", + "url": "https://github.com/Setasign/FPDI.git", + "reference": "3c266002f8044f61b17329f7cd702d44d73f0f7f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Setasign/FPDI/zipball/3c266002f8044f61b17329f7cd702d44d73f0f7f", + "reference": "3c266002f8044f61b17329f7cd702d44d73f0f7f", + "shasum": "" + }, + "require": { + "ext-zlib": "*", + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "~5.7", + "setasign/fpdf": "~1.8", + "setasign/tfpdf": "1.25", + "tecnickcom/tcpdf": "~6.2" + }, + "suggest": { + "setasign/fpdf": "FPDI will extend this class but as it is also possible to use TCPDF or tFPDF as an alternative. There's no fixed dependency configured.", + "setasign/fpdi-fpdf": "Use this package to automatically evaluate dependencies to FPDF.", + "setasign/fpdi-tcpdf": "Use this package to automatically evaluate dependencies to TCPDF.", + "setasign/fpdi-tfpdf": "Use this package to automatically evaluate dependencies to tFPDF." + }, + "type": "library", + "autoload": { + "psr-4": { + "setasign\\Fpdi\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Slabon", + "email": "jan.slabon@setasign.com", + "homepage": "https://www.setasign.com" + }, + { + "name": "Maximilian Kresse", + "email": "maximilian.kresse@setasign.com", + "homepage": "https://www.setasign.com" + } + ], + "description": "FPDI is a collection of PHP classes facilitating developers to read pages from existing PDF documents and use them as templates in FPDF. Because it is also possible to use FPDI with TCPDF, there are no fixed dependencies defined. Please see suggestions for packages which evaluates the dependencies automatically.", + "homepage": "https://www.setasign.com/fpdi", + "keywords": [ + "fpdf", + "fpdi", + "pdf" + ], + "time": "2019-01-30T14:11:19+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.5.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "65b12cdeaaa6cd276d4c3033a95b9b88b12701e7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/65b12cdeaaa6cd276d4c3033a95b9b88b12701e7", + "reference": "65b12cdeaaa6cd276d4c3033a95b9b88b12701e7", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "time": "2019-10-28T04:36:32+00:00" + }, + { + "name": "symfony/console", + "version": "v4.3.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "136c4bd62ea871d00843d1bc0316de4c4a84bb78" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/136c4bd62ea871d00843d1bc0316de4c4a84bb78", + "reference": "136c4bd62ea871d00843d1bc0316de4c4a84bb78", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.8", + "symfony/service-contracts": "^1.1" + }, + "conflict": { + "symfony/dependency-injection": "<3.4", + "symfony/event-dispatcher": "<4.3", + "symfony/process": "<3.3" + }, + "provide": { + "psr/log-implementation": "1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~3.4|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/event-dispatcher": "^4.3", + "symfony/lock": "~3.4|~4.0", + "symfony/process": "~3.4|~4.0", + "symfony/var-dumper": "^4.3" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "time": "2019-10-30T12:58:49+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v4.3.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "6229f58993e5a157f6096fc7145c0717d0be8807" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/6229f58993e5a157f6096fc7145c0717d0be8807", + "reference": "6229f58993e5a157f6096fc7145c0717d0be8807", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/event-dispatcher-contracts": "^1.1" + }, + "conflict": { + "symfony/dependency-injection": "<3.4" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "1.1" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~3.4|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/expression-language": "~3.4|~4.0", + "symfony/http-foundation": "^3.4|^4.0", + "symfony/service-contracts": "^1.1", + "symfony/stopwatch": "~3.4|~4.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com", + "time": "2019-10-01T16:40:32+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v1.1.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "c43ab685673fb6c8d84220c77897b1d6cdbe1d18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/c43ab685673fb6c8d84220c77897b1d6cdbe1d18", + "reference": "c43ab685673fb6c8d84220c77897b1d6cdbe1d18", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "suggest": { + "psr/event-dispatcher": "", + "symfony/event-dispatcher-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "time": "2019-09-17T09:54:03+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v4.3.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "9abbb7ef96a51f4d7e69627bc6f63307994e4263" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/9abbb7ef96a51f4d7e69627bc6f63307994e4263", + "reference": "9abbb7ef96a51f4d7e69627bc6f63307994e4263", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/polyfill-ctype": "~1.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Filesystem Component", + "homepage": "https://symfony.com", + "time": "2019-08-20T14:07:54+00:00" + }, + { + "name": "symfony/finder", + "version": "v4.3.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "72a068f77e317ae77c0a0495236ad292cfb5ce6f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/72a068f77e317ae77c0a0495236ad292cfb5ce6f", + "reference": "72a068f77e317ae77c0a0495236ad292cfb5ce6f", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Finder Component", + "homepage": "https://symfony.com", + "time": "2019-10-30T12:53:54+00:00" + }, + { + "name": "symfony/options-resolver", + "version": "v4.3.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "f46c7fc8e207bd8a2188f54f8738f232533765a4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/f46c7fc8e207bd8a2188f54f8738f232533765a4", + "reference": "f46c7fc8e207bd8a2188f54f8738f232533765a4", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony OptionsResolver Component", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "time": "2019-10-28T20:59:01+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.12.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "550ebaac289296ce228a706d0867afc34687e3f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4", + "reference": "550ebaac289296ce228a706d0867afc34687e3f4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.12-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "time": "2019-08-06T08:03:45+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.12.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/b42a2f66e8f1b15ccf25652c3424265923eb4f17", + "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.12-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2019-08-06T08:03:45+00:00" + }, + { + "name": "symfony/polyfill-php70", + "version": "v1.12.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php70.git", + "reference": "54b4c428a0054e254223797d2713c31e08610831" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/54b4c428a0054e254223797d2713c31e08610831", + "reference": "54b4c428a0054e254223797d2713c31e08610831", + "shasum": "" + }, + "require": { + "paragonie/random_compat": "~1.0|~2.0|~9.99", + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.12-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php70\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2019-08-06T08:03:45+00:00" + }, + { + "name": "symfony/polyfill-php72", + "version": "v1.12.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "04ce3335667451138df4307d6a9b61565560199e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/04ce3335667451138df4307d6a9b61565560199e", + "reference": "04ce3335667451138df4307d6a9b61565560199e", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.12-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php72\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2019-08-06T08:03:45+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.12.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "2ceb49eaccb9352bff54d22570276bb75ba4a188" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/2ceb49eaccb9352bff54d22570276bb75ba4a188", + "reference": "2ceb49eaccb9352bff54d22570276bb75ba4a188", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.12-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2019-08-06T08:03:45+00:00" + }, + { + "name": "symfony/process", + "version": "v4.3.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "3b2e0cb029afbb0395034509291f21191d1a4db0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/3b2e0cb029afbb0395034509291f21191d1a4db0", + "reference": "3b2e0cb029afbb0395034509291f21191d1a4db0", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Process Component", + "homepage": "https://symfony.com", + "time": "2019-10-28T17:07:32+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v1.1.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "ffc7f5692092df31515df2a5ecf3b7302b3ddacf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/ffc7f5692092df31515df2a5ecf3b7302b3ddacf", + "reference": "ffc7f5692092df31515df2a5ecf3b7302b3ddacf", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "psr/container": "^1.0" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "time": "2019-10-14T12:27:06+00:00" + }, + { + "name": "symfony/stopwatch", + "version": "v4.3.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "1e4ff456bd625be5032fac9be4294e60442e9b71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/1e4ff456bd625be5032fac9be4294e60442e9b71", + "reference": "1e4ff456bd625be5032fac9be4294e60442e9b71", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/service-contracts": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Stopwatch\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Stopwatch Component", + "homepage": "https://symfony.com", + "time": "2019-08-07T11:52:19+00:00" + }, + { + "name": "tecnickcom/tcpdf", + "version": "6.3.2", + "source": { + "type": "git", + "url": "https://github.com/tecnickcom/TCPDF.git", + "reference": "9fde7bb9b404b945e7ea88fb7eccd23d9a4e324b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tecnickcom/TCPDF/zipball/9fde7bb9b404b945e7ea88fb7eccd23d9a4e324b", + "reference": "9fde7bb9b404b945e7ea88fb7eccd23d9a4e324b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "config", + "include", + "tcpdf.php", + "tcpdf_parser.php", + "tcpdf_import.php", + "tcpdf_barcodes_1d.php", + "tcpdf_barcodes_2d.php", + "include/tcpdf_colors.php", + "include/tcpdf_filters.php", + "include/tcpdf_font_data.php", + "include/tcpdf_fonts.php", + "include/tcpdf_images.php", + "include/tcpdf_static.php", + "include/barcodes/datamatrix.php", + "include/barcodes/pdf417.php", + "include/barcodes/qrcode.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0" + ], + "authors": [ + { + "name": "Nicola Asuni", + "email": "info@tecnick.com", + "role": "lead" + } + ], + "description": "TCPDF is a PHP class for generating PDF documents and barcodes.", + "homepage": "http://www.tcpdf.org/", + "keywords": [ + "PDFD32000-2008", + "TCPDF", + "barcodes", + "datamatrix", + "pdf", + "pdf417", + "qrcode" + ], + "time": "2019-09-20T09:35:01+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/11336f6f84e16a720dae9d8e6ed5019efa85a0f9", + "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "time": "2019-06-13T22:48:21+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "88e6d84706d09a236046d686bbea96f07b3a34f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/88e6d84706d09a236046d686bbea96f07b3a34f4", + "reference": "88e6d84706d09a236046d686bbea96f07b3a34f4", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0", + "symfony/polyfill-ctype": "^1.8" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36 || ^7.5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2019-08-24T08:43:50+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^7.1", + "ext-ctype": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-iconv": "*", + "ext-fileinfo": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "ext-xml": "*", + "ext-xmlreader": "*", + "ext-xmlwriter": "*", + "ext-zip": "*", + "ext-zlib": "*" + }, + "platform-dev": [] +} diff --git a/vendor/phpoffice/phpspreadsheet/docs/assets/logo.svg b/vendor/phpoffice/phpspreadsheet/docs/assets/logo.svg new file mode 100644 index 0000000..229debc --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/docs/assets/logo.svg @@ -0,0 +1,947 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/vendor/phpoffice/phpspreadsheet/docs/extra/extra.css b/vendor/phpoffice/phpspreadsheet/docs/extra/extra.css new file mode 100644 index 0000000..2addeb7 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/docs/extra/extra.css @@ -0,0 +1,8 @@ +/* Make the huge table always visible */ +table.features-cross-reference { + overflow: visible !important; +} +.rst-content table.features-cross-reference.docutils th, +.rst-content table.features-cross-reference.docutils td { + background-color: white; +} diff --git a/vendor/phpoffice/phpspreadsheet/docs/faq.md b/vendor/phpoffice/phpspreadsheet/docs/faq.md new file mode 100644 index 0000000..19f5f8f --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/docs/faq.md @@ -0,0 +1,57 @@ +# Frequently asked questions + +## There seems to be a problem with character encoding... + +It is necessary to use UTF-8 encoding for all texts in PhpSpreadsheet. +If the script uses different encoding then you can convert those texts +with PHP's `iconv()` or `mb_convert_encoding()` functions. + +## Fatal error: Allowed memory size of xxx bytes exhausted (tried to allocate yyy bytes) in zzz on line aaa + +PhpSpreadsheet holds an "in memory" representation of a spreadsheet, so +it is susceptible to PHP's memory limitations. The memory made available +to PHP can be increased by editing the value of the `memory_limit` +directive in your `php.ini` file, or by using +`ini_set('memory_limit', '128M')` within your code. + +Some Readers and Writers are faster than others, and they also use +differing amounts of memory. + +## Protection on my worksheet is not working? + +When you make use of any of the worksheet protection features (e.g. cell +range protection, prohibiting deleting rows, ...), make sure you enable +worksheet security. This can for example be done like this: + +``` php +$spreadsheet->getActiveSheet()->getProtection()->setSheet(true); +``` + +## Feature X is not working with Reader\_Y / Writer\_Z + +Not all features of PhpSpreadsheet are implemented in all of the Reader +/ Writer classes. This is mostly due to underlying libraries not +supporting a specific feature or not having implemented a specific +feature. + +For example autofilter is not implemented in PEAR +Spreadsheet\_Excel\_writer, which is the base of our Xls writer. + +We are slowly building up a list of features, together with the +different readers and writers that support them, in the [features cross +reference](./references/features-cross-reference.md). + +## Formulas don't seem to be calculated in Excel2003 using compatibility pack? + +This is normal behaviour of the compatibility pack, `Xlsx` displays this +correctly. Use `\PhpOffice\PhpSpreadsheet\Writer\Xls` if you really need +calculated values, or force recalculation in Excel2003. + +## Setting column width is not 100% accurate + +Trying to set column width, I experience one problem. When I open the +file in Excel, the actual width is 0.71 less than it should be. + +The short answer is that PhpSpreadsheet uses a measure where padding is +included. See [how to set a column's width](./topics/recipes.md#setting-a-columns-width) +for more details. diff --git a/vendor/phpoffice/phpspreadsheet/docs/index.md b/vendor/phpoffice/phpspreadsheet/docs/index.md new file mode 100644 index 0000000..e7bb466 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/docs/index.md @@ -0,0 +1,98 @@ +# Welcome to PhpSpreadsheet's documentation + +![Logo](./assets/logo.svg) + +PhpSpreadsheet is a library written in pure PHP and providing a set of +classes that allow you to read from and to write to different +spreadsheet file formats, like Excel and LibreOffice Calc. + +## File formats supported + +|Format |Reading|Writing| +|--------------------------------------------|:-----:|:-----:| +|Open Document Format/OASIS (.ods) | ✓ | ✓ | +|Office Open XML (.xlsx) Excel 2007 and above| ✓ | ✓ | +|BIFF 8 (.xls) Excel 97 and above | ✓ | ✓ | +|BIFF 5 (.xls) Excel 95 | ✓ | | +|SpreadsheetML (.xml) Excel 2003 | ✓ | | +|Gnumeric | ✓ | | +|HTML | ✓ | ✓ | +|SYLK | ✓ | | +|CSV | ✓ | ✓ | +|PDF (using either the TCPDF, Dompdf or mPDF libraries, which need to be installed separately)| | ✓ | + +# Getting started + +## Software requirements + +PHP version 7.1 or newer to develop using PhpSpreadsheet. Other requirements, such as PHP extensions, are enforced by +composer. See the `require` section of [the composer.json file](https://github.com/PHPOffice/PhpSpreadsheet/blob/master/composer.json) +for details. + +### PHP version support + +Support for PHP versions will only be maintained for a period of six months beyond the end-of-life of that PHP version + +## Installation + +Use [composer](https://getcomposer.org) to install PhpSpreadsheet into your project: + +```sh +composer require phpoffice/phpspreadsheet +``` + +## Hello World + +This would be the simplest way to write a spreadsheet: + +```php +getActiveSheet(); +$sheet->setCellValue('A1', 'Hello World !'); + +$writer = new Xlsx($spreadsheet); +$writer->save('hello world.xlsx'); +``` + +## Learn by example + +A good way to get started is to run some of the samples. Serve the samples via +PHP built-in webserver: + +```sh +php -S localhost:8000 -t vendor/phpoffice/phpspreadsheet/samples +``` + +Then point your browser to: + +> http://localhost:8000/ + +The samples may also be run directly from the command line, for example: + +```sh +php vendor/phpoffice/phpspreadsheet/samples/Basic/01_Simple.php +``` + +## Learn by documentation + +For more in-depth documentation, you may read about an [overview of the +architecture](./topics/architecture.md), +[creating a spreadsheet](./topics/creating-spreadsheet.md), +[worksheets](./topics/worksheets.md), +[accessing cells](./topics/accessing-cells.md) and +[reading and writing to files](./topics/reading-and-writing-to-file.md). + +Or browse the [API documentation](https://phpoffice.github.io/PhpSpreadsheet). + +# Credits + +Please refer to the [contributor +list](https://github.com/PHPOffice/PhpSpreadsheet/graphs/contributors) +for up-to-date credits. diff --git a/vendor/phpoffice/phpspreadsheet/docs/references/features-cross-reference.md b/vendor/phpoffice/phpspreadsheet/docs/references/features-cross-reference.md new file mode 100644 index 0000000..716a378 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/docs/references/features-cross-reference.md @@ -0,0 +1,1591 @@ +# Features cross reference + +- Supported +- Partially supported +- Not supported +- N/A Cannot be supported + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    ReadersWritersMethods
    XLSXLSXExcel2003XMLOdsGnumericCSVSYLKXLSXLSXOdsCSVHTMLPDFGettersSetters
    Reader OptionsN/AN/AN/AN/AN/AN/AN/AN/A
    Read Data Only (no formatting)N/AN/AN/AN/AN/AN/AN/AN/A$reader->getReadDataOnly()$reader->setReadDataOnly()
    Read Only Specified WorksheetsN/AN/AN/AN/AN/AN/AN/AN/A$reader->getLoadSheetsOnly()$reader->setLoadSheetsOnly()
    $reader->setLoadAllSheets()
    Read Only Specified CellsN/AN/AN/AN/AN/AN/AN/AN/A$reader->getReadFilter()$reader->setReadFilter()
    Document PropertiesN/AN/AN/AN/A
    Standard PropertiesN/AN/AN/AN/A
    CreatorN/AN/AN/A$spreadsheet->getProperties()->getCreator()$spreadsheet->getProperties()->setCreator()
    Creation Date/TimeN/AN/AN/AN/A$spreadsheet->getProperties()->getCreated()$spreadsheet->getProperties()->setCreated()
    ModifierN/AN/AN/AN/AN/AN/A$spreadsheet->getProperties()->getLastModifiedBy()$spreadsheet->getProperties()->setLastModifiedBy()
    Modified Date/TimeN/AN/AN/AN/AN/A$spreadsheet->getProperties()->getModified()$spreadsheet->getProperties()->setModified()
    TitleN/AN/AN/A$spreadsheet->getProperties()->getTitle()$spreadsheet->getProperties()->setTitle()
    DescriptionN/AN/AN/AN/A$spreadsheet->getProperties()->getDescription()$spreadsheet->getProperties()->setDescription()
    SubjectN/AN/AN/A$spreadsheet->getProperties()->getSubject()$spreadsheet->getProperties()->setSubject()
    KeywordsN/AN/AN/A$spreadsheet->getProperties()->getKeywords()$spreadsheet->getProperties()->setKeywords()
    Extended PropertiesN/AN/AN/AN/AN/A
    CategoryN/AN/AN/AN/AN/A$spreadsheet->getProperties()->getCategory()$spreadsheet->getProperties()->setCategory()
    CompanyN/AN/AN/AN/AN/A$spreadsheet->getProperties()->getCompany()$spreadsheet->getProperties()->setCompany()
    ManagerN/AN/AN/AN/AN/A$spreadsheet->getProperties()->getManager()$spreadsheet->getProperties()->setManager()
    User-Defined (Custom) PropertiesN/AN/AN/AN/AN/A$spreadsheet->getProperties()->getCustomProperties()
    $spreadsheet->getProperties()->isCustomPropertySet()
    $spreadsheet->getProperties()->getCustomPropertyValue()
    $spreadsheet->getProperties()->getCustomPropertyType()
    $spreadsheet->getProperties()->setCustomProperty()
    Text PropertiesN/AN/AN/AN/AN/A
    Number PropertiesN/AN/AN/AN/AN/A
    Date PropertiesN/AN/AN/AN/AN/A
    Yes/No (Boolean) PropertiesN/AN/AN/AN/AN/A
    Cell Data Types
    Empty/NULL
    Boolean
    Integer
    Floating Point
    String
    Error
    Formula
    Array
    Rich TextN/AN/A
    Conditional FormattingN/AN/A
    Rows and Column Properties
    Row Height/Column Width
    Hidden
    Worksheet Properties
    Frozen Panes
    Coloured TabsN/A
    Drawing hyperlink$drawing->getHyperlink()->getUrl()$drawing->setHyperlink()->setUrl($url)
    Cell Formatting
    Number Format Mask
    Alignment
    Horizontal
    Vertical
    Wrapping
    Shring-to-Fit
    Indent
    Background Colour
    Patterned
    Font Attributes
    Font Face
    Font Size
    Bold
    Italic
    Strikethrough
    Underline
    Superscript
    Subscript
    Borders
    Line Style
    Position
    Diagonal
    Hyperlinks$cell->getHyperlink()->getUrl($url)$cell->getHyperlink()->setUrl($url)
    http
    Merged Cells
    Cell CommentsN/AN/AN/A1N/A
    Rich Text2N/AN/AN/AN/AN/A
    Alignment3N/AN/AN/AN/AN/A
    Cell ValidationN/AN/AN/AN/AN/A$cell->getDataValidation()$cell->setDataValidation()
    AutoFilters$sheet->getAutoFilter()$sheet->setAutoFilter()
    AutoFilter Expressions
    Filter
    Custom Filter
    DateGroup Filter
    Dynamic Filter
    Colour Filter
    Icon Filter
    Top 10 Filter
    Macros$spreadsheet->getMacrosCode();$spreadsheet->setMacrosCode();
    Form Controls
    Security
    Protection (prevent editing)$sheet->getProtection()$sheet->getProtection()->setSheet(true)
    Encryption (prevent viewing)
    XLSXLSXExcel2003XMLOdsGnumericCSVSYLKXLSXLSXOdsCSVHTMLPDFGettersSetters
    ReadersWritersMethods
    + +1. Only text contents +2. Only BIFF8 files support Rich Text. Prior to that, comments could only be plain text +3. Only BIFF8 files support alignment and rotation. Prior to that, comments could only be unformatted text diff --git a/vendor/phpoffice/phpspreadsheet/docs/references/function-list-by-category.md b/vendor/phpoffice/phpspreadsheet/docs/references/function-list-by-category.md new file mode 100644 index 0000000..9f76845 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/docs/references/function-list-by-category.md @@ -0,0 +1,458 @@ +# Function list by category + +## CATEGORY_CUBE + +Excel Function | PhpSpreadsheet Function +--------------------|------------------------------------------- +CUBEKPIMEMBER | **Not yet Implemented** +CUBEMEMBER | **Not yet Implemented** +CUBEMEMBERPROPERTY | **Not yet Implemented** +CUBERANKEDMEMBER | **Not yet Implemented** +CUBESET | **Not yet Implemented** +CUBESETCOUNT | **Not yet Implemented** +CUBEVALUE | **Not yet Implemented** + +## CATEGORY_DATABASE + +Excel Function | PhpSpreadsheet Function +--------------------|------------------------------------------- +DAVERAGE | \PhpOffice\PhpSpreadsheet\Calculation\Database::DAVERAGE +DCOUNT | \PhpOffice\PhpSpreadsheet\Calculation\Database::DCOUNT +DCOUNTA | \PhpOffice\PhpSpreadsheet\Calculation\Database::DCOUNTA +DGET | \PhpOffice\PhpSpreadsheet\Calculation\Database::DGET +DMAX | \PhpOffice\PhpSpreadsheet\Calculation\Database::DMAX +DMIN | \PhpOffice\PhpSpreadsheet\Calculation\Database::DMIN +DPRODUCT | \PhpOffice\PhpSpreadsheet\Calculation\Database::DPRODUCT +DSTDEV | \PhpOffice\PhpSpreadsheet\Calculation\Database::DSTDEV +DSTDEVP | \PhpOffice\PhpSpreadsheet\Calculation\Database::DSTDEVP +DSUM | \PhpOffice\PhpSpreadsheet\Calculation\Database::DSUM +DVAR | \PhpOffice\PhpSpreadsheet\Calculation\Database::DVAR +DVARP | \PhpOffice\PhpSpreadsheet\Calculation\Database::DVARP + +## CATEGORY_DATE_AND_TIME + +Excel Function | PhpSpreadsheet Function +--------------------|------------------------------------------- +DATE | \PhpOffice\PhpSpreadsheet\Calculation\DateTime::DATE +DATEDIF | \PhpOffice\PhpSpreadsheet\Calculation\DateTime::DATEDIF +DATEVALUE | \PhpOffice\PhpSpreadsheet\Calculation\DateTime::DATEVALUE +DAY | \PhpOffice\PhpSpreadsheet\Calculation\DateTime::DAYOFMONTH +DAYS | \PhpOffice\PhpSpreadsheet\Calculation\DateTime::DAYS +DAYS360 | \PhpOffice\PhpSpreadsheet\Calculation\DateTime::DAYS360 +EDATE | \PhpOffice\PhpSpreadsheet\Calculation\DateTime::EDATE +EOMONTH | \PhpOffice\PhpSpreadsheet\Calculation\DateTime::EOMONTH +HOUR | \PhpOffice\PhpSpreadsheet\Calculation\DateTime::HOUROFDAY +ISOWEEKNUM | \PhpOffice\PhpSpreadsheet\Calculation\DateTime::ISOWEEKNUM +MINUTE | \PhpOffice\PhpSpreadsheet\Calculation\DateTime::MINUTE +MONTH | \PhpOffice\PhpSpreadsheet\Calculation\DateTime::MONTHOFYEAR +NETWORKDAYS | \PhpOffice\PhpSpreadsheet\Calculation\DateTime::NETWORKDAYS +NETWORKDAYS.INTL | **Not yet Implemented** +NOW | \PhpOffice\PhpSpreadsheet\Calculation\DateTime::DATETIMENOW +SECOND | \PhpOffice\PhpSpreadsheet\Calculation\DateTime::SECOND +TIME | \PhpOffice\PhpSpreadsheet\Calculation\DateTime::TIME +TIMEVALUE | \PhpOffice\PhpSpreadsheet\Calculation\DateTime::TIMEVALUE +TODAY | \PhpOffice\PhpSpreadsheet\Calculation\DateTime::DATENOW +WEEKDAY | \PhpOffice\PhpSpreadsheet\Calculation\DateTime::WEEKDAY +WEEKNUM | \PhpOffice\PhpSpreadsheet\Calculation\DateTime::WEEKNUM +WORKDAY | \PhpOffice\PhpSpreadsheet\Calculation\DateTime::WORKDAY +WORKDAY.INTL | **Not yet Implemented** +YEAR | \PhpOffice\PhpSpreadsheet\Calculation\DateTime::YEAR +YEARFRAC | \PhpOffice\PhpSpreadsheet\Calculation\DateTime::YEARFRAC + +## CATEGORY_ENGINEERING + +Excel Function | PhpSpreadsheet Function +--------------------|------------------------------------------- +BESSELI | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::BESSELI +BESSELJ | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::BESSELJ +BESSELK | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::BESSELK +BESSELY | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::BESSELY +BIN2DEC | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::BINTODEC +BIN2HEX | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::BINTOHEX +BIN2OCT | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::BINTOOCT +BITAND | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::BITAND +BITLSHIFT | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::BITLSHIFT +BITOR | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::BITOR +BITRSHIFT | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::BITRSHIFT +BITXOR | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::BITOR +COMPLEX | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::COMPLEX +CONVERT | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::CONVERTUOM +DEC2BIN | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::DECTOBIN +DEC2HEX | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::DECTOHEX +DEC2OCT | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::DECTOOCT +DELTA | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::DELTA +ERF | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::ERF +ERF.PRECISE | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::ERFPRECISE +ERFC | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::ERFC +ERFC.PRECISE | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::ERFC +GESTEP | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::GESTEP +HEX2BIN | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::HEXTOBIN +HEX2DEC | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::HEXTODEC +HEX2OCT | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::HEXTOOCT +IMABS | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMABS +IMAGINARY | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMAGINARY +IMARGUMENT | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMARGUMENT +IMCONJUGATE | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMCONJUGATE +IMCOS | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMCOS +IMCOSH | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMCOSH +IMCOT | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMCOT +IMCSC | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMCSC +IMCSCH | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMCSCH +IMDIV | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMDIV +IMEXP | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMEXP +IMLN | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMLN +IMLOG10 | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMLOG10 +IMLOG2 | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMLOG2 +IMPOWER | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMPOWER +IMPRODUCT | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMPRODUCT +IMREAL | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMREAL +IMSEC | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMSEC +IMSECH | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMSECH +IMSIN | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMSIN +IMSINH | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMSINH +IMSQRT | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMSQRT +IMSUB | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMSUB +IMSUM | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMSUM +IMTAN | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMTAN +OCT2BIN | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::OCTTOBIN +OCT2DEC | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::OCTTODEC +OCT2HEX | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::OCTTOHEX + +## CATEGORY_FINANCIAL + +Excel Function | PhpSpreadsheet Function +--------------------|------------------------------------------- +ACCRINT | \PhpOffice\PhpSpreadsheet\Calculation\Financial::ACCRINT +ACCRINTM | \PhpOffice\PhpSpreadsheet\Calculation\Financial::ACCRINTM +AMORDEGRC | \PhpOffice\PhpSpreadsheet\Calculation\Financial::AMORDEGRC +AMORLINC | \PhpOffice\PhpSpreadsheet\Calculation\Financial::AMORLINC +COUPDAYBS | \PhpOffice\PhpSpreadsheet\Calculation\Financial::COUPDAYBS +COUPDAYS | \PhpOffice\PhpSpreadsheet\Calculation\Financial::COUPDAYS +COUPDAYSNC | \PhpOffice\PhpSpreadsheet\Calculation\Financial::COUPDAYSNC +COUPNCD | \PhpOffice\PhpSpreadsheet\Calculation\Financial::COUPNCD +COUPNUM | \PhpOffice\PhpSpreadsheet\Calculation\Financial::COUPNUM +COUPPCD | \PhpOffice\PhpSpreadsheet\Calculation\Financial::COUPPCD +CUMIPMT | \PhpOffice\PhpSpreadsheet\Calculation\Financial::CUMIPMT +CUMPRINC | \PhpOffice\PhpSpreadsheet\Calculation\Financial::CUMPRINC +DB | \PhpOffice\PhpSpreadsheet\Calculation\Financial::DB +DDB | \PhpOffice\PhpSpreadsheet\Calculation\Financial::DDB +DISC | \PhpOffice\PhpSpreadsheet\Calculation\Financial::DISC +DOLLARDE | \PhpOffice\PhpSpreadsheet\Calculation\Financial::DOLLARDE +DOLLARFR | \PhpOffice\PhpSpreadsheet\Calculation\Financial::DOLLARFR +DURATION | **Not yet Implemented** +EFFECT | \PhpOffice\PhpSpreadsheet\Calculation\Financial::EFFECT +FV | \PhpOffice\PhpSpreadsheet\Calculation\Financial::FV +FVSCHEDULE | \PhpOffice\PhpSpreadsheet\Calculation\Financial::FVSCHEDULE +INTRATE | \PhpOffice\PhpSpreadsheet\Calculation\Financial::INTRATE +IPMT | \PhpOffice\PhpSpreadsheet\Calculation\Financial::IPMT +IRR | \PhpOffice\PhpSpreadsheet\Calculation\Financial::IRR +ISPMT | \PhpOffice\PhpSpreadsheet\Calculation\Financial::ISPMT +MDURATION | **Not yet Implemented** +MIRR | \PhpOffice\PhpSpreadsheet\Calculation\Financial::MIRR +NOMINAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial::NOMINAL +NPER | \PhpOffice\PhpSpreadsheet\Calculation\Financial::NPER +NPV | \PhpOffice\PhpSpreadsheet\Calculation\Financial::NPV +ODDFPRICE | **Not yet Implemented** +ODDFYIELD | **Not yet Implemented** +ODDLPRICE | **Not yet Implemented** +ODDLYIELD | **Not yet Implemented** +PDURATION | \PhpOffice\PhpSpreadsheet\Calculation\Financial::PDURATION +PMT | \PhpOffice\PhpSpreadsheet\Calculation\Financial::PMT +PPMT | \PhpOffice\PhpSpreadsheet\Calculation\Financial::PPMT +PRICE | \PhpOffice\PhpSpreadsheet\Calculation\Financial::PRICE +PRICEDISC | \PhpOffice\PhpSpreadsheet\Calculation\Financial::PRICEDISC +PRICEMAT | \PhpOffice\PhpSpreadsheet\Calculation\Financial::PRICEMAT +PV | \PhpOffice\PhpSpreadsheet\Calculation\Financial::PV +RATE | \PhpOffice\PhpSpreadsheet\Calculation\Financial::RATE +RECEIVED | \PhpOffice\PhpSpreadsheet\Calculation\Financial::RECEIVED +RRI | \PhpOffice\PhpSpreadsheet\Calculation\Financial::RRI +SLN | \PhpOffice\PhpSpreadsheet\Calculation\Financial::SLN +SYD | \PhpOffice\PhpSpreadsheet\Calculation\Financial::SYD +TBILLEQ | \PhpOffice\PhpSpreadsheet\Calculation\Financial::TBILLEQ +TBILLPRICE | \PhpOffice\PhpSpreadsheet\Calculation\Financial::TBILLPRICE +TBILLYIELD | \PhpOffice\PhpSpreadsheet\Calculation\Financial::TBILLYIELD +USDOLLAR | **Not yet Implemented** +VDB | **Not yet Implemented** +XIRR | \PhpOffice\PhpSpreadsheet\Calculation\Financial::XIRR +XNPV | \PhpOffice\PhpSpreadsheet\Calculation\Financial::XNPV +YIELD | **Not yet Implemented** +YIELDDISC | \PhpOffice\PhpSpreadsheet\Calculation\Financial::YIELDDISC +YIELDMAT | \PhpOffice\PhpSpreadsheet\Calculation\Financial::YIELDMAT + +## CATEGORY_INFORMATION + +Excel Function | PhpSpreadsheet Function +--------------------|------------------------------------------- +CELL | **Not yet Implemented** +ERROR.TYPE | \PhpOffice\PhpSpreadsheet\Calculation\Functions::errorType +INFO | **Not yet Implemented** +ISBLANK | \PhpOffice\PhpSpreadsheet\Calculation\Functions::isBlank +ISERR | \PhpOffice\PhpSpreadsheet\Calculation\Functions::isErr +ISERROR | \PhpOffice\PhpSpreadsheet\Calculation\Functions::isError +ISEVEN | \PhpOffice\PhpSpreadsheet\Calculation\Functions::isEven +ISFORMULA | \PhpOffice\PhpSpreadsheet\Calculation\Functions::isFormula +ISLOGICAL | \PhpOffice\PhpSpreadsheet\Calculation\Functions::isLogical +ISNA | \PhpOffice\PhpSpreadsheet\Calculation\Functions::isNa +ISNONTEXT | \PhpOffice\PhpSpreadsheet\Calculation\Functions::isNonText +ISNUMBER | \PhpOffice\PhpSpreadsheet\Calculation\Functions::isNumber +ISODD | \PhpOffice\PhpSpreadsheet\Calculation\Functions::isOdd +ISREF | **Not yet Implemented** +ISTEXT | \PhpOffice\PhpSpreadsheet\Calculation\Functions::isText +N | \PhpOffice\PhpSpreadsheet\Calculation\Functions::n +NA | \PhpOffice\PhpSpreadsheet\Calculation\Functions::NA +TYPE | \PhpOffice\PhpSpreadsheet\Calculation\Functions::TYPE + +## CATEGORY_LOGICAL + +Excel Function | PhpSpreadsheet Function +--------------------|------------------------------------------- +AND | \PhpOffice\PhpSpreadsheet\Calculation\Logical::logicalAnd +FALSE | \PhpOffice\PhpSpreadsheet\Calculation\Logical::FALSE +IF | \PhpOffice\PhpSpreadsheet\Calculation\Logical::statementIf +IFERROR | \PhpOffice\PhpSpreadsheet\Calculation\Logical::IFERROR +IFNA | \PhpOffice\PhpSpreadsheet\Calculation\Logical::IFNA +IFS | **Not yet Implemented** +NOT | \PhpOffice\PhpSpreadsheet\Calculation\Logical::NOT +OR | \PhpOffice\PhpSpreadsheet\Calculation\Logical::logicalOr +SWITCH | \PhpOffice\PhpSpreadsheet\Calculation\Logical::statementSwitch +TRUE | \PhpOffice\PhpSpreadsheet\Calculation\Logical::TRUE +XOR | \PhpOffice\PhpSpreadsheet\Calculation\Logical::logicalXor + +## CATEGORY_LOOKUP_AND_REFERENCE + +Excel Function | PhpSpreadsheet Function +--------------------|------------------------------------------- +ADDRESS | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef::cellAddress +AREAS | **Not yet Implemented** +CHOOSE | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef::CHOOSE +COLUMN | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef::COLUMN +COLUMNS | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef::COLUMNS +FORMULATEXT | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef::FORMULATEXT +GETPIVOTDATA | **Not yet Implemented** +HLOOKUP | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef::HLOOKUP +HYPERLINK | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef::HYPERLINK +INDEX | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef::INDEX +INDIRECT | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef::INDIRECT +LOOKUP | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef::LOOKUP +MATCH | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef::MATCH +OFFSET | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef::OFFSET +ROW | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef::ROW +ROWS | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef::ROWS +RTD | **Not yet Implemented** +TRANSPOSE | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef::TRANSPOSE +VLOOKUP | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef::VLOOKUP + +## CATEGORY_MATH_AND_TRIG + +Excel Function | PhpSpreadsheet Function +--------------------|------------------------------------------- +ABS | abs +ACOS | acos +ACOSH | acosh +ACOT | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::ACOT +ACOTH | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::ACOTH +ARABIC | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::ARABIC +ASIN | asin +ASINH | asinh +ATAN | atan +ATAN2 | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::ATAN2 +ATANH | atanh +BASE | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::BASE +CEILING | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::CEILING +COMBIN | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::COMBIN +COS | cos +COSH | cosh +COT | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::COT +COTH | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::COTH +CSC | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::CSC +CSCH | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::CSCH +DEGREES | rad2deg +EVEN | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::EVEN +EXP | exp +FACT | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::FACT +FACTDOUBLE | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::FACTDOUBLE +FLOOR | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::FLOOR +GCD | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::GCD +INT | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::INT +LCM | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::LCM +LN | log +LOG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::logBase +LOG10 | log10 +MDETERM | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::MDETERM +MINVERSE | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::MINVERSE +MMULT | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::MMULT +MOD | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::MOD +MROUND | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::MROUND +MULTINOMIAL | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::MULTINOMIAL +ODD | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::ODD +PI | pi +POWER | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::POWER +PRODUCT | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::PRODUCT +QUOTIENT | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::QUOTIENT +RADIANS | deg2rad +RAND | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::RAND +RANDBETWEEN | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::RAND +ROMAN | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::ROMAN +ROUND | round +ROUNDDOWN | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::ROUNDDOWN +ROUNDUP | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::ROUNDUP +SEC | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::SEC +SECH | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::SECH +SERIESSUM | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::SERIESSUM +SIGN | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::SIGN +SIN | sin +SINH | sinh +SQRT | sqrt +SQRTPI | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::SQRTPI +SUBTOTAL | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::SUBTOTAL +SUM | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::SUM +SUMIF | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::SUMIF +SUMIFS | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::SUMIFS +SUMPRODUCT | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::SUMPRODUCT +SUMSQ | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::SUMSQ +SUMX2MY2 | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::SUMX2MY2 +SUMX2PY2 | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::SUMX2PY2 +SUMXMY2 | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::SUMXMY2 +TAN | tan +TANH | tanh +TRUNC | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::TRUNC + +## CATEGORY_STATISTICAL + +Excel Function | PhpSpreadsheet Function +--------------------|------------------------------------------- +AVEDEV | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::AVEDEV +AVERAGE | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::AVERAGE +AVERAGEA | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::AVERAGEA +AVERAGEIF | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::AVERAGEIF +AVERAGEIFS | **Not yet Implemented** +BETADIST | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::BETADIST +BETAINV | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::BETAINV +BINOMDIST | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::BINOMDIST +CHIDIST | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::CHIDIST +CHIINV | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::CHIINV +CHITEST | **Not yet Implemented** +CONFIDENCE | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::CONFIDENCE +CORREL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::CORREL +COUNT | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::COUNT +COUNTA | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::COUNTA +COUNTBLANK | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::COUNTBLANK +COUNTIF | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::COUNTIF +COUNTIFS | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::COUNTIFS +COVAR | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::COVAR +CRITBINOM | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::CRITBINOM +DEVSQ | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::DEVSQ +EXPONDIST | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::EXPONDIST +FDIST | **Not yet Implemented** +FINV | **Not yet Implemented** +FISHER | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::FISHER +FISHERINV | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::FISHERINV +FORECAST | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::FORECAST +FREQUENCY | **Not yet Implemented** +FTEST | **Not yet Implemented** +GAMMADIST | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::GAMMADIST +GAMMAINV | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::GAMMAINV +GAMMALN | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::GAMMALN +GEOMEAN | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::GEOMEAN +GROWTH | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::GROWTH +HARMEAN | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::HARMEAN +HYPGEOMDIST | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::HYPGEOMDIST +INTERCEPT | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::INTERCEPT +KURT | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::KURT +LARGE | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::LARGE +LINEST | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::LINEST +LOGEST | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::LOGEST +LOGINV | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::LOGINV +LOGNORMDIST | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::LOGNORMDIST +MAX | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::MAX +MAXA | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::MAXA +MAXIFS | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::MAXIFS +MEDIAN | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::MEDIAN +MEDIANIF | **Not yet Implemented** +MIN | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::MIN +MINA | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::MINA +MINIFS | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::MINIFS +MODE | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::MODE +MODE.SNGL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::MODE +NEGBINOMDIST | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::NEGBINOMDIST +NORMDIST | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::NORMDIST +NORMINV | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::NORMINV +NORMSDIST | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::NORMSDIST +NORMSINV | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::NORMSINV +PEARSON | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::CORREL +PERCENTILE | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::PERCENTILE +PERCENTRANK | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::PERCENTRANK +PERMUT | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::PERMUT +POISSON | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::POISSON +PROB | **Not yet Implemented** +QUARTILE | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::QUARTILE +RANK | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::RANK +RSQ | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::RSQ +SKEW | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::SKEW +SLOPE | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::SLOPE +SMALL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::SMALL +STANDARDIZE | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::STANDARDIZE +STDEV | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::STDEV +STDEV.P | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::STDEVP +STDEV.S | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::STDEV +STDEVA | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::STDEVA +STDEVP | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::STDEVP +STDEVPA | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::STDEVPA +STEYX | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::STEYX +TDIST | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::TDIST +TINV | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::TINV +TREND | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::TREND +TRIMMEAN | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::TRIMMEAN +TTEST | **Not yet Implemented** +VAR | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::VARFunc +VAR.P | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::VARP +VAR.S | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::VARFunc +VARA | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::VARA +VARP | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::VARP +VARPA | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::VARPA +WEIBULL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::WEIBULL +ZTEST | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::ZTEST + +## CATEGORY_TEXT_AND_DATA + +Excel Function | PhpSpreadsheet Function +--------------------|------------------------------------------- +ASC | **Not yet Implemented** +BAHTTEXT | **Not yet Implemented** +CHAR | \PhpOffice\PhpSpreadsheet\Calculation\TextData::CHARACTER +CLEAN | \PhpOffice\PhpSpreadsheet\Calculation\TextData::TRIMNONPRINTABLE +CODE | \PhpOffice\PhpSpreadsheet\Calculation\TextData::ASCIICODE +CONCAT | \PhpOffice\PhpSpreadsheet\Calculation\TextData::CONCATENATE +CONCATENATE | \PhpOffice\PhpSpreadsheet\Calculation\TextData::CONCATENATE +DOLLAR | \PhpOffice\PhpSpreadsheet\Calculation\TextData::DOLLAR +EXACT | \PhpOffice\PhpSpreadsheet\Calculation\TextData::EXACT +FIND | \PhpOffice\PhpSpreadsheet\Calculation\TextData::SEARCHSENSITIVE +FINDB | \PhpOffice\PhpSpreadsheet\Calculation\TextData::SEARCHSENSITIVE +FIXED | \PhpOffice\PhpSpreadsheet\Calculation\TextData::FIXEDFORMAT +JIS | **Not yet Implemented** +LEFT | \PhpOffice\PhpSpreadsheet\Calculation\TextData::LEFT +LEFTB | \PhpOffice\PhpSpreadsheet\Calculation\TextData::LEFT +LEN | \PhpOffice\PhpSpreadsheet\Calculation\TextData::STRINGLENGTH +LENB | \PhpOffice\PhpSpreadsheet\Calculation\TextData::STRINGLENGTH +LOWER | \PhpOffice\PhpSpreadsheet\Calculation\TextData::LOWERCASE +MID | \PhpOffice\PhpSpreadsheet\Calculation\TextData::MID +MIDB | \PhpOffice\PhpSpreadsheet\Calculation\TextData::MID +NUMBERVALUE | \PhpOffice\PhpSpreadsheet\Calculation\TextData::NUMBERVALUE +PHONETIC | **Not yet Implemented** +PROPER | \PhpOffice\PhpSpreadsheet\Calculation\TextData::PROPERCASE +REPLACE | \PhpOffice\PhpSpreadsheet\Calculation\TextData::REPLACE +REPLACEB | \PhpOffice\PhpSpreadsheet\Calculation\TextData::REPLACE +REPT | str_repeat +RIGHT | \PhpOffice\PhpSpreadsheet\Calculation\TextData::RIGHT +RIGHTB | \PhpOffice\PhpSpreadsheet\Calculation\TextData::RIGHT +SEARCH | \PhpOffice\PhpSpreadsheet\Calculation\TextData::SEARCHINSENSITIVE +SEARCHB | \PhpOffice\PhpSpreadsheet\Calculation\TextData::SEARCHINSENSITIVE +SUBSTITUTE | \PhpOffice\PhpSpreadsheet\Calculation\TextData::SUBSTITUTE +T | \PhpOffice\PhpSpreadsheet\Calculation\TextData::RETURNSTRING +TEXT | \PhpOffice\PhpSpreadsheet\Calculation\TextData::TEXTFORMAT +TEXTJOIN | \PhpOffice\PhpSpreadsheet\Calculation\TextData::TEXTJOIN +TRIM | \PhpOffice\PhpSpreadsheet\Calculation\TextData::TRIMSPACES +UNICHAR | \PhpOffice\PhpSpreadsheet\Calculation\TextData::CHARACTER +UNICODE | \PhpOffice\PhpSpreadsheet\Calculation\TextData::ASCIICODE +UPPER | \PhpOffice\PhpSpreadsheet\Calculation\TextData::UPPERCASE +VALUE | \PhpOffice\PhpSpreadsheet\Calculation\TextData::VALUE diff --git a/vendor/phpoffice/phpspreadsheet/docs/references/function-list-by-name.md b/vendor/phpoffice/phpspreadsheet/docs/references/function-list-by-name.md new file mode 100644 index 0000000..709b4b1 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/docs/references/function-list-by-name.md @@ -0,0 +1,533 @@ +# Function list by name + +## A + +Excel Function | Category | PhpSpreadsheet Function +--------------------|--------------------------------|------------------------------------------- +ABS | CATEGORY_MATH_AND_TRIG | abs +ACCRINT | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial::ACCRINT +ACCRINTM | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial::ACCRINTM +ACOS | CATEGORY_MATH_AND_TRIG | acos +ACOSH | CATEGORY_MATH_AND_TRIG | acosh +ACOT | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::ACOT +ACOTH | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::ACOTH +ADDRESS | CATEGORY_LOOKUP_AND_REFERENCE | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef::cellAddress +AMORDEGRC | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial::AMORDEGRC +AMORLINC | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial::AMORLINC +AND | CATEGORY_LOGICAL | \PhpOffice\PhpSpreadsheet\Calculation\Logical::logicalAnd +ARABIC | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::ARABIC +AREAS | CATEGORY_LOOKUP_AND_REFERENCE | **Not yet Implemented** +ASC | CATEGORY_TEXT_AND_DATA | **Not yet Implemented** +ASIN | CATEGORY_MATH_AND_TRIG | asin +ASINH | CATEGORY_MATH_AND_TRIG | asinh +ATAN | CATEGORY_MATH_AND_TRIG | atan +ATAN2 | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::ATAN2 +ATANH | CATEGORY_MATH_AND_TRIG | atanh +AVEDEV | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::AVEDEV +AVERAGE | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::AVERAGE +AVERAGEA | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::AVERAGEA +AVERAGEIF | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::AVERAGEIF +AVERAGEIFS | CATEGORY_STATISTICAL | **Not yet Implemented** + +## B + +Excel Function | Category | PhpSpreadsheet Function +--------------------|--------------------------------|------------------------------------------- +BAHTTEXT | CATEGORY_TEXT_AND_DATA | **Not yet Implemented** +BASE | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::BASE +BESSELI | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::BESSELI +BESSELJ | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::BESSELJ +BESSELK | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::BESSELK +BESSELY | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::BESSELY +BETADIST | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::BETADIST +BETAINV | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::BETAINV +BIN2DEC | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::BINTODEC +BIN2HEX | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::BINTOHEX +BIN2OCT | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::BINTOOCT +BINOMDIST | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::BINOMDIST +BITAND | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::BITAND +BITLSHIFT | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::BITLSHIFT +BITOR | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::BITOR +BITRSHIFT | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::BITRSHIFT +BITXOR | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::BITOR + +## C + +Excel Function | Category | PhpSpreadsheet Function +--------------------|--------------------------------|------------------------------------------- +CEILING | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::CEILING +CELL | CATEGORY_INFORMATION | **Not yet Implemented** +CHAR | CATEGORY_TEXT_AND_DATA | \PhpOffice\PhpSpreadsheet\Calculation\TextData::CHARACTER +CHIDIST | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::CHIDIST +CHIINV | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::CHIINV +CHITEST | CATEGORY_STATISTICAL | **Not yet Implemented** +CHOOSE | CATEGORY_LOOKUP_AND_REFERENCE | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef::CHOOSE +CLEAN | CATEGORY_TEXT_AND_DATA | \PhpOffice\PhpSpreadsheet\Calculation\TextData::TRIMNONPRINTABLE +CODE | CATEGORY_TEXT_AND_DATA | \PhpOffice\PhpSpreadsheet\Calculation\TextData::ASCIICODE +COLUMN | CATEGORY_LOOKUP_AND_REFERENCE | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef::COLUMN +COLUMNS | CATEGORY_LOOKUP_AND_REFERENCE | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef::COLUMNS +COMBIN | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::COMBIN +COMPLEX | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::COMPLEX +CONCAT | CATEGORY_TEXT_AND_DATA | \PhpOffice\PhpSpreadsheet\Calculation\TextData::CONCATENATE +CONCATENATE | CATEGORY_TEXT_AND_DATA | \PhpOffice\PhpSpreadsheet\Calculation\TextData::CONCATENATE +CONFIDENCE | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::CONFIDENCE +CONVERT | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::CONVERTUOM +CORREL | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::CORREL +COS | CATEGORY_MATH_AND_TRIG | cos +COSH | CATEGORY_MATH_AND_TRIG | cosh +COT | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::COT +COTH | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::COTH +COUNT | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::COUNT +COUNTA | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::COUNTA +COUNTBLANK | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::COUNTBLANK +COUNTIF | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::COUNTIF +COUNTIFS | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::COUNTIFS +COUPDAYBS | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial::COUPDAYBS +COUPDAYS | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial::COUPDAYS +COUPDAYSNC | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial::COUPDAYSNC +COUPNCD | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial::COUPNCD +COUPNUM | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial::COUPNUM +COUPPCD | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial::COUPPCD +COVAR | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::COVAR +CRITBINOM | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::CRITBINOM +CSC | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::CSC +CSCH | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::CSCH +CUBEKPIMEMBER | CATEGORY_CUBE | **Not yet Implemented** +CUBEMEMBER | CATEGORY_CUBE | **Not yet Implemented** +CUBEMEMBERPROPERTY | CATEGORY_CUBE | **Not yet Implemented** +CUBERANKEDMEMBER | CATEGORY_CUBE | **Not yet Implemented** +CUBESET | CATEGORY_CUBE | **Not yet Implemented** +CUBESETCOUNT | CATEGORY_CUBE | **Not yet Implemented** +CUBEVALUE | CATEGORY_CUBE | **Not yet Implemented** +CUMIPMT | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial::CUMIPMT +CUMPRINC | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial::CUMPRINC + +## D + +Excel Function | Category | PhpSpreadsheet Function +--------------------|--------------------------------|------------------------------------------- +DATE | CATEGORY_DATE_AND_TIME | \PhpOffice\PhpSpreadsheet\Calculation\DateTime::DATE +DATEDIF | CATEGORY_DATE_AND_TIME | \PhpOffice\PhpSpreadsheet\Calculation\DateTime::DATEDIF +DATEVALUE | CATEGORY_DATE_AND_TIME | \PhpOffice\PhpSpreadsheet\Calculation\DateTime::DATEVALUE +DAVERAGE | CATEGORY_DATABASE | \PhpOffice\PhpSpreadsheet\Calculation\Database::DAVERAGE +DAY | CATEGORY_DATE_AND_TIME | \PhpOffice\PhpSpreadsheet\Calculation\DateTime::DAYOFMONTH +DAYS | CATEGORY_DATE_AND_TIME | \PhpOffice\PhpSpreadsheet\Calculation\DateTime::DAYS +DAYS360 | CATEGORY_DATE_AND_TIME | \PhpOffice\PhpSpreadsheet\Calculation\DateTime::DAYS360 +DB | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial::DB +DCOUNT | CATEGORY_DATABASE | \PhpOffice\PhpSpreadsheet\Calculation\Database::DCOUNT +DCOUNTA | CATEGORY_DATABASE | \PhpOffice\PhpSpreadsheet\Calculation\Database::DCOUNTA +DDB | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial::DDB +DEC2BIN | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::DECTOBIN +DEC2HEX | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::DECTOHEX +DEC2OCT | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::DECTOOCT +DEGREES | CATEGORY_MATH_AND_TRIG | rad2deg +DELTA | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::DELTA +DEVSQ | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::DEVSQ +DGET | CATEGORY_DATABASE | \PhpOffice\PhpSpreadsheet\Calculation\Database::DGET +DISC | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial::DISC +DMAX | CATEGORY_DATABASE | \PhpOffice\PhpSpreadsheet\Calculation\Database::DMAX +DMIN | CATEGORY_DATABASE | \PhpOffice\PhpSpreadsheet\Calculation\Database::DMIN +DOLLAR | CATEGORY_TEXT_AND_DATA | \PhpOffice\PhpSpreadsheet\Calculation\TextData::DOLLAR +DOLLARDE | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial::DOLLARDE +DOLLARFR | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial::DOLLARFR +DPRODUCT | CATEGORY_DATABASE | \PhpOffice\PhpSpreadsheet\Calculation\Database::DPRODUCT +DSTDEV | CATEGORY_DATABASE | \PhpOffice\PhpSpreadsheet\Calculation\Database::DSTDEV +DSTDEVP | CATEGORY_DATABASE | \PhpOffice\PhpSpreadsheet\Calculation\Database::DSTDEVP +DSUM | CATEGORY_DATABASE | \PhpOffice\PhpSpreadsheet\Calculation\Database::DSUM +DURATION | CATEGORY_FINANCIAL | **Not yet Implemented** +DVAR | CATEGORY_DATABASE | \PhpOffice\PhpSpreadsheet\Calculation\Database::DVAR +DVARP | CATEGORY_DATABASE | \PhpOffice\PhpSpreadsheet\Calculation\Database::DVARP + +## E + +Excel Function | Category | PhpSpreadsheet Function +--------------------|--------------------------------|------------------------------------------- +EDATE | CATEGORY_DATE_AND_TIME | \PhpOffice\PhpSpreadsheet\Calculation\DateTime::EDATE +EFFECT | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial::EFFECT +EOMONTH | CATEGORY_DATE_AND_TIME | \PhpOffice\PhpSpreadsheet\Calculation\DateTime::EOMONTH +ERF | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::ERF +ERF.PRECISE | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::ERFPRECISE +ERFC | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::ERFC +ERFC.PRECISE | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::ERFC +ERROR.TYPE | CATEGORY_INFORMATION | \PhpOffice\PhpSpreadsheet\Calculation\Functions::errorType +EVEN | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::EVEN +EXACT | CATEGORY_TEXT_AND_DATA | \PhpOffice\PhpSpreadsheet\Calculation\TextData::EXACT +EXP | CATEGORY_MATH_AND_TRIG | exp +EXPONDIST | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::EXPONDIST + +## F + +Excel Function | Category | PhpSpreadsheet Function +--------------------|--------------------------------|------------------------------------------- +FACT | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::FACT +FACTDOUBLE | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::FACTDOUBLE +FALSE | CATEGORY_LOGICAL | \PhpOffice\PhpSpreadsheet\Calculation\Logical::FALSE +FDIST | CATEGORY_STATISTICAL | **Not yet Implemented** +FIND | CATEGORY_TEXT_AND_DATA | \PhpOffice\PhpSpreadsheet\Calculation\TextData::SEARCHSENSITIVE +FINDB | CATEGORY_TEXT_AND_DATA | \PhpOffice\PhpSpreadsheet\Calculation\TextData::SEARCHSENSITIVE +FINV | CATEGORY_STATISTICAL | **Not yet Implemented** +FISHER | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::FISHER +FISHERINV | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::FISHERINV +FIXED | CATEGORY_TEXT_AND_DATA | \PhpOffice\PhpSpreadsheet\Calculation\TextData::FIXEDFORMAT +FLOOR | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::FLOOR +FORECAST | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::FORECAST +FORMULATEXT | CATEGORY_LOOKUP_AND_REFERENCE | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef::FORMULATEXT +FREQUENCY | CATEGORY_STATISTICAL | **Not yet Implemented** +FTEST | CATEGORY_STATISTICAL | **Not yet Implemented** +FV | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial::FV +FVSCHEDULE | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial::FVSCHEDULE + +## G + +Excel Function | Category | PhpSpreadsheet Function +--------------------|--------------------------------|------------------------------------------- +GAMMADIST | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::GAMMADIST +GAMMAINV | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::GAMMAINV +GAMMALN | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::GAMMALN +GCD | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::GCD +GEOMEAN | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::GEOMEAN +GESTEP | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::GESTEP +GETPIVOTDATA | CATEGORY_LOOKUP_AND_REFERENCE | **Not yet Implemented** +GROWTH | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::GROWTH + +## H + +Excel Function | Category | PhpSpreadsheet Function +--------------------|--------------------------------|------------------------------------------- +HARMEAN | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::HARMEAN +HEX2BIN | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::HEXTOBIN +HEX2DEC | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::HEXTODEC +HEX2OCT | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::HEXTOOCT +HLOOKUP | CATEGORY_LOOKUP_AND_REFERENCE | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef::HLOOKUP +HOUR | CATEGORY_DATE_AND_TIME | \PhpOffice\PhpSpreadsheet\Calculation\DateTime::HOUROFDAY +HYPERLINK | CATEGORY_LOOKUP_AND_REFERENCE | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef::HYPERLINK +HYPGEOMDIST | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::HYPGEOMDIST + +## I + +Excel Function | Category | PhpSpreadsheet Function +--------------------|--------------------------------|------------------------------------------- +IF | CATEGORY_LOGICAL | \PhpOffice\PhpSpreadsheet\Calculation\Logical::statementIf +IFERROR | CATEGORY_LOGICAL | \PhpOffice\PhpSpreadsheet\Calculation\Logical::IFERROR +IFNA | CATEGORY_LOGICAL | \PhpOffice\PhpSpreadsheet\Calculation\Logical::IFNA +IFS | CATEGORY_LOGICAL | **Not yet Implemented** +IMABS | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMABS +IMAGINARY | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMAGINARY +IMARGUMENT | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMARGUMENT +IMCONJUGATE | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMCONJUGATE +IMCOS | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMCOS +IMCOSH | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMCOSH +IMCOT | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMCOT +IMCSC | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMCSC +IMCSCH | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMCSCH +IMDIV | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMDIV +IMEXP | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMEXP +IMLN | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMLN +IMLOG10 | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMLOG10 +IMLOG2 | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMLOG2 +IMPOWER | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMPOWER +IMPRODUCT | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMPRODUCT +IMREAL | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMREAL +IMSEC | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMSEC +IMSECH | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMSECH +IMSIN | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMSIN +IMSINH | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMSINH +IMSQRT | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMSQRT +IMSUB | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMSUB +IMSUM | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMSUM +IMTAN | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMTAN +INDEX | CATEGORY_LOOKUP_AND_REFERENCE | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef::INDEX +INDIRECT | CATEGORY_LOOKUP_AND_REFERENCE | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef::INDIRECT +INFO | CATEGORY_INFORMATION | **Not yet Implemented** +INT | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::INT +INTERCEPT | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::INTERCEPT +INTRATE | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial::INTRATE +IPMT | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial::IPMT +IRR | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial::IRR +ISBLANK | CATEGORY_INFORMATION | \PhpOffice\PhpSpreadsheet\Calculation\Functions::isBlank +ISERR | CATEGORY_INFORMATION | \PhpOffice\PhpSpreadsheet\Calculation\Functions::isErr +ISERROR | CATEGORY_INFORMATION | \PhpOffice\PhpSpreadsheet\Calculation\Functions::isError +ISEVEN | CATEGORY_INFORMATION | \PhpOffice\PhpSpreadsheet\Calculation\Functions::isEven +ISFORMULA | CATEGORY_INFORMATION | \PhpOffice\PhpSpreadsheet\Calculation\Functions::isFormula +ISLOGICAL | CATEGORY_INFORMATION | \PhpOffice\PhpSpreadsheet\Calculation\Functions::isLogical +ISNA | CATEGORY_INFORMATION | \PhpOffice\PhpSpreadsheet\Calculation\Functions::isNa +ISNONTEXT | CATEGORY_INFORMATION | \PhpOffice\PhpSpreadsheet\Calculation\Functions::isNonText +ISNUMBER | CATEGORY_INFORMATION | \PhpOffice\PhpSpreadsheet\Calculation\Functions::isNumber +ISODD | CATEGORY_INFORMATION | \PhpOffice\PhpSpreadsheet\Calculation\Functions::isOdd +ISOWEEKNUM | CATEGORY_DATE_AND_TIME | \PhpOffice\PhpSpreadsheet\Calculation\DateTime::ISOWEEKNUM +ISPMT | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial::ISPMT +ISREF | CATEGORY_INFORMATION | **Not yet Implemented** +ISTEXT | CATEGORY_INFORMATION | \PhpOffice\PhpSpreadsheet\Calculation\Functions::isText + +## J + +Excel Function | Category | PhpSpreadsheet Function +--------------------|--------------------------------|------------------------------------------- +JIS | CATEGORY_TEXT_AND_DATA | **Not yet Implemented** + +## K + +Excel Function | Category | PhpSpreadsheet Function +--------------------|--------------------------------|------------------------------------------- +KURT | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::KURT + +## L + +Excel Function | Category | PhpSpreadsheet Function +--------------------|--------------------------------|------------------------------------------- +LARGE | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::LARGE +LCM | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::LCM +LEFT | CATEGORY_TEXT_AND_DATA | \PhpOffice\PhpSpreadsheet\Calculation\TextData::LEFT +LEFTB | CATEGORY_TEXT_AND_DATA | \PhpOffice\PhpSpreadsheet\Calculation\TextData::LEFT +LEN | CATEGORY_TEXT_AND_DATA | \PhpOffice\PhpSpreadsheet\Calculation\TextData::STRINGLENGTH +LENB | CATEGORY_TEXT_AND_DATA | \PhpOffice\PhpSpreadsheet\Calculation\TextData::STRINGLENGTH +LINEST | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::LINEST +LN | CATEGORY_MATH_AND_TRIG | log +LOG | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::logBase +LOG10 | CATEGORY_MATH_AND_TRIG | log10 +LOGEST | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::LOGEST +LOGINV | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::LOGINV +LOGNORMDIST | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::LOGNORMDIST +LOOKUP | CATEGORY_LOOKUP_AND_REFERENCE | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef::LOOKUP +LOWER | CATEGORY_TEXT_AND_DATA | \PhpOffice\PhpSpreadsheet\Calculation\TextData::LOWERCASE + +## M + +Excel Function | Category | PhpSpreadsheet Function +--------------------|--------------------------------|------------------------------------------- +MATCH | CATEGORY_LOOKUP_AND_REFERENCE | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef::MATCH +MAX | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::MAX +MAXA | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::MAXA +MAXIFS | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::MAXIFS +MDETERM | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::MDETERM +MDURATION | CATEGORY_FINANCIAL | **Not yet Implemented** +MEDIAN | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::MEDIAN +MEDIANIF | CATEGORY_STATISTICAL | **Not yet Implemented** +MID | CATEGORY_TEXT_AND_DATA | \PhpOffice\PhpSpreadsheet\Calculation\TextData::MID +MIDB | CATEGORY_TEXT_AND_DATA | \PhpOffice\PhpSpreadsheet\Calculation\TextData::MID +MIN | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::MIN +MINA | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::MINA +MINIFS | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::MINIFS +MINUTE | CATEGORY_DATE_AND_TIME | \PhpOffice\PhpSpreadsheet\Calculation\DateTime::MINUTE +MINVERSE | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::MINVERSE +MIRR | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial::MIRR +MMULT | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::MMULT +MOD | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::MOD +MODE | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::MODE +MODE.SNGL | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::MODE +MONTH | CATEGORY_DATE_AND_TIME | \PhpOffice\PhpSpreadsheet\Calculation\DateTime::MONTHOFYEAR +MROUND | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::MROUND +MULTINOMIAL | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::MULTINOMIAL + +## N + +Excel Function | Category | PhpSpreadsheet Function +--------------------|--------------------------------|------------------------------------------- +N | CATEGORY_INFORMATION | \PhpOffice\PhpSpreadsheet\Calculation\Functions::n +NA | CATEGORY_INFORMATION | \PhpOffice\PhpSpreadsheet\Calculation\Functions::NA +NEGBINOMDIST | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::NEGBINOMDIST +NETWORKDAYS | CATEGORY_DATE_AND_TIME | \PhpOffice\PhpSpreadsheet\Calculation\DateTime::NETWORKDAYS +NETWORKDAYS.INTL | CATEGORY_DATE_AND_TIME | **Not yet Implemented** +NOMINAL | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial::NOMINAL +NORMDIST | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::NORMDIST +NORMINV | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::NORMINV +NORMSDIST | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::NORMSDIST +NORMSINV | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::NORMSINV +NOT | CATEGORY_LOGICAL | \PhpOffice\PhpSpreadsheet\Calculation\Logical::NOT +NOW | CATEGORY_DATE_AND_TIME | \PhpOffice\PhpSpreadsheet\Calculation\DateTime::DATETIMENOW +NPER | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial::NPER +NPV | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial::NPV +NUMBERVALUE | CATEGORY_TEXT_AND_DATA | \PhpOffice\PhpSpreadsheet\Calculation\TextData::NUMBERVALUE + +## O + +Excel Function | Category | PhpSpreadsheet Function +--------------------|--------------------------------|------------------------------------------- +OCT2BIN | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::OCTTOBIN +OCT2DEC | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::OCTTODEC +OCT2HEX | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::OCTTOHEX +ODD | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::ODD +ODDFPRICE | CATEGORY_FINANCIAL | **Not yet Implemented** +ODDFYIELD | CATEGORY_FINANCIAL | **Not yet Implemented** +ODDLPRICE | CATEGORY_FINANCIAL | **Not yet Implemented** +ODDLYIELD | CATEGORY_FINANCIAL | **Not yet Implemented** +OFFSET | CATEGORY_LOOKUP_AND_REFERENCE | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef::OFFSET +OR | CATEGORY_LOGICAL | \PhpOffice\PhpSpreadsheet\Calculation\Logical::logicalOr + +## P + +Excel Function | Category | PhpSpreadsheet Function +--------------------|--------------------------------|------------------------------------------- +PDURATION | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial::PDURATION +PEARSON | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::CORREL +PERCENTILE | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::PERCENTILE +PERCENTRANK | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::PERCENTRANK +PERMUT | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::PERMUT +PHONETIC | CATEGORY_TEXT_AND_DATA | **Not yet Implemented** +PI | CATEGORY_MATH_AND_TRIG | pi +PMT | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial::PMT +POISSON | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::POISSON +POWER | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::POWER +PPMT | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial::PPMT +PRICE | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial::PRICE +PRICEDISC | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial::PRICEDISC +PRICEMAT | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial::PRICEMAT +PROB | CATEGORY_STATISTICAL | **Not yet Implemented** +PRODUCT | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::PRODUCT +PROPER | CATEGORY_TEXT_AND_DATA | \PhpOffice\PhpSpreadsheet\Calculation\TextData::PROPERCASE +PV | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial::PV + +## Q + +Excel Function | Category | PhpSpreadsheet Function +--------------------|--------------------------------|------------------------------------------- +QUARTILE | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::QUARTILE +QUOTIENT | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::QUOTIENT + +## R + +Excel Function | Category | PhpSpreadsheet Function +--------------------|--------------------------------|------------------------------------------- +RADIANS | CATEGORY_MATH_AND_TRIG | deg2rad +RAND | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::RAND +RANDBETWEEN | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::RAND +RANK | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::RANK +RATE | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial::RATE +RECEIVED | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial::RECEIVED +REPLACE | CATEGORY_TEXT_AND_DATA | \PhpOffice\PhpSpreadsheet\Calculation\TextData::REPLACE +REPLACEB | CATEGORY_TEXT_AND_DATA | \PhpOffice\PhpSpreadsheet\Calculation\TextData::REPLACE +REPT | CATEGORY_TEXT_AND_DATA | str_repeat +RIGHT | CATEGORY_TEXT_AND_DATA | \PhpOffice\PhpSpreadsheet\Calculation\TextData::RIGHT +RIGHTB | CATEGORY_TEXT_AND_DATA | \PhpOffice\PhpSpreadsheet\Calculation\TextData::RIGHT +ROMAN | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::ROMAN +ROUND | CATEGORY_MATH_AND_TRIG | round +ROUNDDOWN | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::ROUNDDOWN +ROUNDUP | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::ROUNDUP +ROW | CATEGORY_LOOKUP_AND_REFERENCE | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef::ROW +ROWS | CATEGORY_LOOKUP_AND_REFERENCE | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef::ROWS +RRI | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial::RRI +RSQ | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::RSQ +RTD | CATEGORY_LOOKUP_AND_REFERENCE | **Not yet Implemented** + +## S + +Excel Function | Category | PhpSpreadsheet Function +--------------------|--------------------------------|------------------------------------------- +SEARCH | CATEGORY_TEXT_AND_DATA | \PhpOffice\PhpSpreadsheet\Calculation\TextData::SEARCHINSENSITIVE +SEARCHB | CATEGORY_TEXT_AND_DATA | \PhpOffice\PhpSpreadsheet\Calculation\TextData::SEARCHINSENSITIVE +SEC | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::SEC +SECH | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::SECH +SECOND | CATEGORY_DATE_AND_TIME | \PhpOffice\PhpSpreadsheet\Calculation\DateTime::SECOND +SERIESSUM | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::SERIESSUM +SIGN | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::SIGN +SIN | CATEGORY_MATH_AND_TRIG | sin +SINH | CATEGORY_MATH_AND_TRIG | sinh +SKEW | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::SKEW +SLN | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial::SLN +SLOPE | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::SLOPE +SMALL | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::SMALL +SQRT | CATEGORY_MATH_AND_TRIG | sqrt +SQRTPI | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::SQRTPI +STANDARDIZE | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::STANDARDIZE +STDEV | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::STDEV +STDEV.P | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::STDEVP +STDEV.S | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::STDEV +STDEVA | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::STDEVA +STDEVP | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::STDEVP +STDEVPA | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::STDEVPA +STEYX | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::STEYX +SUBSTITUTE | CATEGORY_TEXT_AND_DATA | \PhpOffice\PhpSpreadsheet\Calculation\TextData::SUBSTITUTE +SUBTOTAL | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::SUBTOTAL +SUM | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::SUM +SUMIF | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::SUMIF +SUMIFS | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::SUMIFS +SUMPRODUCT | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::SUMPRODUCT +SUMSQ | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::SUMSQ +SUMX2MY2 | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::SUMX2MY2 +SUMX2PY2 | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::SUMX2PY2 +SUMXMY2 | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::SUMXMY2 +SWITCH | CATEGORY_LOGICAL | \PhpOffice\PhpSpreadsheet\Calculation\Logical::statementSwitch +SYD | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial::SYD + +## T + +Excel Function | Category | PhpSpreadsheet Function +--------------------|--------------------------------|------------------------------------------- +T | CATEGORY_TEXT_AND_DATA | \PhpOffice\PhpSpreadsheet\Calculation\TextData::RETURNSTRING +TAN | CATEGORY_MATH_AND_TRIG | tan +TANH | CATEGORY_MATH_AND_TRIG | tanh +TBILLEQ | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial::TBILLEQ +TBILLPRICE | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial::TBILLPRICE +TBILLYIELD | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial::TBILLYIELD +TDIST | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::TDIST +TEXT | CATEGORY_TEXT_AND_DATA | \PhpOffice\PhpSpreadsheet\Calculation\TextData::TEXTFORMAT +TEXTJOIN | CATEGORY_TEXT_AND_DATA | \PhpOffice\PhpSpreadsheet\Calculation\TextData::TEXTJOIN +TIME | CATEGORY_DATE_AND_TIME | \PhpOffice\PhpSpreadsheet\Calculation\DateTime::TIME +TIMEVALUE | CATEGORY_DATE_AND_TIME | \PhpOffice\PhpSpreadsheet\Calculation\DateTime::TIMEVALUE +TINV | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::TINV +TODAY | CATEGORY_DATE_AND_TIME | \PhpOffice\PhpSpreadsheet\Calculation\DateTime::DATENOW +TRANSPOSE | CATEGORY_LOOKUP_AND_REFERENCE | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef::TRANSPOSE +TREND | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::TREND +TRIM | CATEGORY_TEXT_AND_DATA | \PhpOffice\PhpSpreadsheet\Calculation\TextData::TRIMSPACES +TRIMMEAN | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::TRIMMEAN +TRUE | CATEGORY_LOGICAL | \PhpOffice\PhpSpreadsheet\Calculation\Logical::TRUE +TRUNC | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::TRUNC +TTEST | CATEGORY_STATISTICAL | **Not yet Implemented** +TYPE | CATEGORY_INFORMATION | \PhpOffice\PhpSpreadsheet\Calculation\Functions::TYPE + +## U + +Excel Function | Category | PhpSpreadsheet Function +--------------------|--------------------------------|------------------------------------------- +UNICHAR | CATEGORY_TEXT_AND_DATA | \PhpOffice\PhpSpreadsheet\Calculation\TextData::CHARACTER +UNICODE | CATEGORY_TEXT_AND_DATA | \PhpOffice\PhpSpreadsheet\Calculation\TextData::ASCIICODE +UPPER | CATEGORY_TEXT_AND_DATA | \PhpOffice\PhpSpreadsheet\Calculation\TextData::UPPERCASE +USDOLLAR | CATEGORY_FINANCIAL | **Not yet Implemented** + +## V + +Excel Function | Category | PhpSpreadsheet Function +--------------------|--------------------------------|------------------------------------------- +VALUE | CATEGORY_TEXT_AND_DATA | \PhpOffice\PhpSpreadsheet\Calculation\TextData::VALUE +VAR | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::VARFunc +VAR.P | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::VARP +VAR.S | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::VARFunc +VARA | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::VARA +VARP | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::VARP +VARPA | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::VARPA +VDB | CATEGORY_FINANCIAL | **Not yet Implemented** +VLOOKUP | CATEGORY_LOOKUP_AND_REFERENCE | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef::VLOOKUP + +## W + +Excel Function | Category | PhpSpreadsheet Function +--------------------|--------------------------------|------------------------------------------- +WEEKDAY | CATEGORY_DATE_AND_TIME | \PhpOffice\PhpSpreadsheet\Calculation\DateTime::WEEKDAY +WEEKNUM | CATEGORY_DATE_AND_TIME | \PhpOffice\PhpSpreadsheet\Calculation\DateTime::WEEKNUM +WEIBULL | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::WEIBULL +WORKDAY | CATEGORY_DATE_AND_TIME | \PhpOffice\PhpSpreadsheet\Calculation\DateTime::WORKDAY +WORKDAY.INTL | CATEGORY_DATE_AND_TIME | **Not yet Implemented** + +## X + +Excel Function | Category | PhpSpreadsheet Function +--------------------|--------------------------------|------------------------------------------- +XIRR | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial::XIRR +XNPV | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial::XNPV +XOR | CATEGORY_LOGICAL | \PhpOffice\PhpSpreadsheet\Calculation\Logical::logicalXor + +## Y + +Excel Function | Category | PhpSpreadsheet Function +--------------------|--------------------------------|------------------------------------------- +YEAR | CATEGORY_DATE_AND_TIME | \PhpOffice\PhpSpreadsheet\Calculation\DateTime::YEAR +YEARFRAC | CATEGORY_DATE_AND_TIME | \PhpOffice\PhpSpreadsheet\Calculation\DateTime::YEARFRAC +YIELD | CATEGORY_FINANCIAL | **Not yet Implemented** +YIELDDISC | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial::YIELDDISC +YIELDMAT | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial::YIELDMAT + +## Z + +Excel Function | Category | PhpSpreadsheet Function +--------------------|--------------------------------|------------------------------------------- +ZTEST | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical::ZTEST diff --git a/vendor/phpoffice/phpspreadsheet/docs/topics/accessing-cells.md b/vendor/phpoffice/phpspreadsheet/docs/topics/accessing-cells.md new file mode 100644 index 0000000..4770d72 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/docs/topics/accessing-cells.md @@ -0,0 +1,556 @@ +# Accessing cells + +Accessing cells in a Spreadsheet should be pretty straightforward. This +topic lists some of the options to access a cell. + +## Setting a cell value by coordinate + +Setting a cell value by coordinate can be done using the worksheet's +`setCellValue()` method. + +``` php +// Set cell A1 with a string value +$spreadsheet->getActiveSheet()->setCellValue('A1', 'PhpSpreadsheet'); + +// Set cell A2 with a numeric value +$spreadsheet->getActiveSheet()->setCellValue('A2', 12345.6789); + +// Set cell A3 with a boolean value +$spreadsheet->getActiveSheet()->setCellValue('A3', TRUE); + +// Set cell A4 with a formula +$spreadsheet->getActiveSheet()->setCellValue( + 'A4', + '=IF(A3, CONCATENATE(A1, " ", A2), CONCATENATE(A2, " ", A1))' +); +``` + +Alternatively, you can retrieve the cell object, and then call the +cell’s `setValue()` method: + +``` php +$spreadsheet->getActiveSheet() + ->getCell('B8') + ->setValue('Some value'); +``` + +### Creating a new Cell + +If you make a call to `getCell()`, and the cell doesn't already exist, then +PhpSpreadsheet will (by default) create the cell for you. If you don't want +to create a new cell, then you can pass a second argument of false, and then +`getCell()` will return a null if the cell doesn't exist. + +### BEWARE: Cells assigned to variables as a Detached Reference + +As an "in-memory" model, PHPSpreadsheet can be very demanding of memory, +particularly when working with large spreadsheets. One technique used to +reduce this memory overhead is cell caching, so cells are actually +maintained in a collection that may or may not be held in memory while you +are working with the spreadsheet. Because of this, a call to `getCell()` +(or any similar method) returns the cell data, and a pointer to the collection. +While this is not normally an issue, it can become significant +if you assign the result of a call to `getCell()` to a variable. Any +subsequent calls to retrieve other cells will unset that pointer, although +the cell object will still retain its data values. + +What does this mean? Consider the following code: + +``` +$spreadSheet = new Spreadsheet(); +$workSheet = $spreadSheet->getActiveSheet(); + +// Set details for the formula that we want to evaluate, together with any data on which it depends +$workSheet->fromArray( + [1, 2, 3], + null, + 'A1' +); + +$cellC1 = $workSheet->getCell('C1'); +echo 'Value: ', $cellC1->getValue(), '; Address: ', $cellC1->getCoordinate(), PHP_EOL; + +$cellA1 = $workSheet->getCell('A1'); +echo 'Value: ', $cellA1->getValue(), '; Address: ', $cellA1->getCoordinate(), PHP_EOL; + +echo 'Value: ', $cellC1->getValue(), '; Address: ', $cellC1->getCoordinate(), PHP_EOL; +``` + +The call to `getCell('C1')` returns the cell at `C1` containing its value (`3`), +together with its link to the collection (used to identify its +address/coordinate `C1`). The subsequent call to access cell `A1` +modifies the value of `$cellC1`, detaching its link to the collection. + +So when we try to display the value and address a second time, we can display +its value, but trying to display its address/coordinate will throw an +exception because that link has been set to null. + +__Note:__ There are some internal methods that will fetch other cells from the +collection, and this too will detach the link to the collection from any cell +that you might have assigned to a variable. + +## Excel DataTypes + +MS Excel supports 7 basic datatypes: + +- string +- number +- boolean +- null +- formula +- error +- Inline (or rich text) string + +By default, when you call the worksheet's `setCellValue()` method or the +cell's `setValue()` method, PhpSpreadsheet will use the appropriate +datatype for PHP nulls, booleans, floats or integers; or cast any string +data value that you pass to the method into the most appropriate +datatype, so numeric strings will be cast to numbers, while string +values beginning with `=` will be converted to a formula. Strings that +aren't numeric, or that don't begin with a leading `=` will be treated +as genuine string values. + +This "conversion" is handled by a cell "value binder", and you can write +custom "value binders" to change the behaviour of these "conversions". +The standard PhpSpreadsheet package also provides an "advanced value +binder" that handles a number of more complex conversions, such as +converting strings with a fractional format like "3/4" to a number value +(0.75 in this case) and setting an appropriate "fraction" number format +mask. Similarly, strings like "5%" will be converted to a value of 0.05, +and a percentage number format mask applied, and strings containing +values that look like dates will be converted to Excel serialized +datetimestamp values, and a corresponding mask applied. This is +particularly useful when loading data from csv files, or setting cell +values from a database. + +Formats handled by the advanced value binder include: + +- TRUE or FALSE (dependent on locale settings) are converted to booleans. +- Numeric strings identified as scientific (exponential) format are + converted to numbers. +- Fractions and vulgar fractions are converted to numbers, and + an appropriate number format mask applied. +- Percentages are converted + to numbers, divided by 100, and an appropriate number format mask + applied. +- Dates and times are converted to Excel timestamp values + (numbers), and an appropriate number format mask applied. +- When strings contain a newline character (`\n`), then the cell styling is + set to wrap. + +You can read more about value binders later in this section of the +documentation. + +### Setting a formula in a Cell + +As stated above, if you store a string value with the first character an `=` +in a cell. PHPSpreadsheet will treat that value as a formula, and then you +can evaluate that formula by calling `getCalculatedValue()` against the cell. + +There may be times though, when you wish to store a value beginning with `=` +as a string, and that you don't want PHPSpreadsheet to evaluate as though it +was a formula. + +To do this, you need to "escape" the value by setting it as "quoted text". + +``` +// Set cell A4 with a formula +$spreadsheet->getActiveSheet()->setCellValue( + 'A4', + '=IF(A3, CONCATENATE(A1, " ", A2), CONCATENATE(A2, " ", A1))' +); +$spreadsheet->getActiveSheet()->getCell('A4') + ->getStyle()->setQuotePrefix(true); +``` + +Then, even if you ask PHPSpreadsheet to return the calculated value for cell +`A4`, it will return `=IF(A3, CONCATENATE(A1, " ", A2), CONCATENATE(A2, " ", A1))` +as a string, and not try to evaluate the formula. + + +### Setting a date and/or time value in a cell + +Date or time values are held as timestamp in Excel (a simple floating +point value), and a number format mask is used to show how that value +should be formatted; so if we want to store a date in a cell, we need to +calculate the correct Excel timestamp, and set a number format mask. + +``` php +// Get the current date/time and convert to an Excel date/time +$dateTimeNow = time(); +$excelDateValue = \PhpOffice\PhpSpreadsheet\Shared\Date::PHPToExcel( $dateTimeNow ); +// Set cell A6 with the Excel date/time value +$spreadsheet->getActiveSheet()->setCellValue( + 'A6', + $excelDateValue +); +// Set the number format mask so that the excel timestamp will be displayed as a human-readable date/time +$spreadsheet->getActiveSheet()->getStyle('A6') + ->getNumberFormat() + ->setFormatCode( + \PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_DATE_DATETIME + ); +``` + +### Setting a number with leading zeroes + +By default, PhpSpreadsheet will automatically detect the value type and +set it to the appropriate Excel numeric datatype. This type conversion +is handled by a value binder, as described in the section of this +document entitled "Using value binders to facilitate data entry". + +Numbers don't have leading zeroes, so if you try to set a numeric value +that does have leading zeroes (such as a telephone number) then these +will be normally be lost as the value is cast to a number, so +"01513789642" will be displayed as 1513789642. + +There are two ways you can force PhpSpreadsheet to override this +behaviour. + +Firstly, you can set the datatype explicitly as a string so that it is +not converted to a number. + +``` php +// Set cell A8 with a numeric value, but tell PhpSpreadsheet it should be treated as a string +$spreadsheet->getActiveSheet()->setCellValueExplicit( + 'A8', + "01513789642", + \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING +); +``` + +Alternatively, you can use a number format mask to display the value +with leading zeroes. + +``` php +// Set cell A9 with a numeric value +$spreadsheet->getActiveSheet()->setCellValue('A9', 1513789642); +// Set a number format mask to display the value as 11 digits with leading zeroes +$spreadsheet->getActiveSheet()->getStyle('A9') + ->getNumberFormat() + ->setFormatCode( + '00000000000' + ); +``` + +With number format masking, you can even break up the digits into groups +to make the value more easily readable. + +``` php +// Set cell A10 with a numeric value +$spreadsheet->getActiveSheet()->setCellValue('A10', 1513789642); +// Set a number format mask to display the value as 11 digits with leading zeroes +$spreadsheet->getActiveSheet()->getStyle('A10') + ->getNumberFormat() + ->setFormatCode( + '0000-000-0000' + ); +``` + +![07-simple-example-1.png](./images/07-simple-example-1.png) + +**Note:** that not all complex format masks such as this one will work +when retrieving a formatted value to display "on screen", or for certain +writers such as HTML or PDF, but it will work with the true spreadsheet +writers (Xlsx and Xls). + +## Setting a range of cells from an array + +It is also possible to set a range of cell values in a single call by +passing an array of values to the `fromArray()` method. + +``` php +$arrayData = [ + [NULL, 2010, 2011, 2012], + ['Q1', 12, 15, 21], + ['Q2', 56, 73, 86], + ['Q3', 52, 61, 69], + ['Q4', 30, 32, 0], +]; +$spreadsheet->getActiveSheet() + ->fromArray( + $arrayData, // The data to set + NULL, // Array values with this value will not be set + 'C3' // Top left coordinate of the worksheet range where + // we want to set these values (default is A1) + ); +``` + +![07-simple-example-2.png](./images/07-simple-example-2.png) + +If you pass a 2-d array, then this will be treated as a series of rows +and columns. A 1-d array will be treated as a single row, which is +particularly useful if you're fetching an array of data from a database. + +``` php +$rowArray = ['Value1', 'Value2', 'Value3', 'Value4']; +$spreadsheet->getActiveSheet() + ->fromArray( + $rowArray, // The data to set + NULL, // Array values with this value will not be set + 'C3' // Top left coordinate of the worksheet range where + // we want to set these values (default is A1) + ); +``` + +![07-simple-example-3.png](./images/07-simple-example-3.png) + +If you have a simple 1-d array, and want to write it as a column, then +the following will convert it into an appropriately structured 2-d array +that can be fed to the `fromArray()` method: + +``` php +$rowArray = ['Value1', 'Value2', 'Value3', 'Value4']; +$columnArray = array_chunk($rowArray, 1); +$spreadsheet->getActiveSheet() + ->fromArray( + $columnArray, // The data to set + NULL, // Array values with this value will not be set + 'C3' // Top left coordinate of the worksheet range where + // we want to set these values (default is A1) + ); +``` + +![07-simple-example-4.png](./images/07-simple-example-4.png) + +## Retrieving a cell value by coordinate + +To retrieve the value of a cell, the cell should first be retrieved from +the worksheet using the `getCell()` method. A cell's value can be read +using the `getValue()` method. + +``` php +// Get the value from cell A1 +$cellValue = $spreadsheet->getActiveSheet()->getCell('A1')->getValue(); +``` + +This will retrieve the raw, unformatted value contained in the cell. + +If a cell contains a formula, and you need to retrieve the calculated +value rather than the formula itself, then use the cell's +`getCalculatedValue()` method. This is further explained in +[the calculation engine](./calculation-engine.md). + +``` php +// Get the value from cell A4 +$cellValue = $spreadsheet->getActiveSheet()->getCell('A4')->getCalculatedValue(); +``` + +Alternatively, if you want to see the value with any cell formatting +applied (e.g. for a human-readable date or time value), then you can use +the cell's `getFormattedValue()` method. + +``` php +// Get the value from cell A6 +$cellValue = $spreadsheet->getActiveSheet()->getCell('A6')->getFormattedValue(); +``` + +## Setting a cell value by column and row + +Setting a cell value by coordinate can be done using the worksheet's +`setCellValueByColumnAndRow()` method. + +``` php +// Set cell A5 with a string value +$spreadsheet->getActiveSheet()->setCellValueByColumnAndRow(1, 5, 'PhpSpreadsheet'); +``` + +**Note:** that column references start with `1` for column `A`. + +## Retrieving a cell value by column and row + +To retrieve the value of a cell, the cell should first be retrieved from +the worksheet using the `getCellByColumnAndRow()` method. A cell’s value can +be read again using the following line of code: + +``` php +// Get the value from cell B5 +$cellValue = $spreadsheet->getActiveSheet()->getCellByColumnAndRow(2, 5)->getValue(); +``` + +If you need the calculated value of a cell, use the following code. This +is further explained in [the calculation engine](./calculation-engine.md). + +``` php +// Get the value from cell A4 +$cellValue = $spreadsheet->getActiveSheet()->getCellByColumnAndRow(1, 4)->getCalculatedValue(); +``` + +## Retrieving a range of cell values to an array + +It is also possible to retrieve a range of cell values to an array in a +single call using the `toArray()`, `rangeToArray()` or +`namedRangeToArray()` methods. + +``` php +$dataArray = $spreadsheet->getActiveSheet() + ->rangeToArray( + 'C3:E5', // The worksheet range that we want to retrieve + NULL, // Value that should be returned for empty cells + TRUE, // Should formulas be calculated (the equivalent of getCalculatedValue() for each cell) + TRUE, // Should values be formatted (the equivalent of getFormattedValue() for each cell) + TRUE // Should the array be indexed by cell row and cell column + ); +``` + +These methods will all return a 2-d array of rows and columns. The +`toArray()` method will return the whole worksheet; `rangeToArray()` +will return a specified range or cells; while `namedRangeToArray()` will +return the cells within a defined `named range`. + +## Looping through cells + +### Looping through cells using iterators + +The easiest way to loop cells is by using iterators. Using iterators, +one can use foreach to loop worksheets, rows within a worksheet, and +cells within a row. + +Below is an example where we read all the values in a worksheet and +display them in a table. + +``` php +$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader('Xlsx'); +$reader->setReadDataOnly(TRUE); +$spreadsheet = $reader->load("test.xlsx"); + +$worksheet = $spreadsheet->getActiveSheet(); + +echo '' . PHP_EOL; +foreach ($worksheet->getRowIterator() as $row) { + echo '' . PHP_EOL; + $cellIterator = $row->getCellIterator(); + $cellIterator->setIterateOnlyExistingCells(FALSE); // This loops through all cells, + // even if a cell value is not set. + // By default, only cells that have a value + // set will be iterated. + foreach ($cellIterator as $cell) { + echo '' . PHP_EOL; + } + echo '' . PHP_EOL; +} +echo '
    ' . + $cell->getValue() . + '
    ' . PHP_EOL; +``` + +Note that we have set the cell iterator's +`setIterateOnlyExistingCells()` to FALSE. This makes the iterator loop +all cells within the worksheet range, even if they have not been set. + +The cell iterator will return a `null` as the cell value if it is not +set in the worksheet. Setting the cell iterator's +`setIterateOnlyExistingCells()` to `false` will loop all cells in the +worksheet that can be available at that moment. This will create new +cells if required and increase memory usage! Only use it if it is +intended to loop all cells that are possibly available. + +### Looping through cells using indexes + +One can use the possibility to access cell values by column and row +index like `[1, 1]` instead of `'A1'` for reading and writing cell values in +loops. + +**Note:** In PhpSpreadsheet column index and row index are 1-based. That means `'A1'` ~ `[1, 1]` + +Below is an example where we read all the values in a worksheet and +display them in a table. + +``` php +$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader('Xlsx'); +$reader->setReadDataOnly(TRUE); +$spreadsheet = $reader->load("test.xlsx"); + +$worksheet = $spreadsheet->getActiveSheet(); +// Get the highest row and column numbers referenced in the worksheet +$highestRow = $worksheet->getHighestRow(); // e.g. 10 +$highestColumn = $worksheet->getHighestColumn(); // e.g 'F' +$highestColumnIndex = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::columnIndexFromString($highestColumn); // e.g. 5 + +echo '' . "\n"; +for ($row = 1; $row <= $highestRow; ++$row) { + echo '' . PHP_EOL; + for ($col = 1; $col <= $highestColumnIndex; ++$col) { + $value = $worksheet->getCellByColumnAndRow($col, $row)->getValue(); + echo '' . PHP_EOL; + } + echo '' . PHP_EOL; +} +echo '
    ' . $value . '
    ' . PHP_EOL; +``` + +Alternatively, you can take advantage of PHP's "Perl-style" character +incrementors to loop through the cells by coordinate: + +``` php +$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader('Xlsx'); +$reader->setReadDataOnly(TRUE); +$spreadsheet = $reader->load("test.xlsx"); + +$worksheet = $spreadsheet->getActiveSheet(); +// Get the highest row number and column letter referenced in the worksheet +$highestRow = $worksheet->getHighestRow(); // e.g. 10 +$highestColumn = $worksheet->getHighestColumn(); // e.g 'F' +// Increment the highest column letter +$highestColumn++; + +echo '' . "\n"; +for ($row = 1; $row <= $highestRow; ++$row) { + echo '' . PHP_EOL; + for ($col = 'A'; $col != $highestColumn; ++$col) { + echo '' . PHP_EOL; + } + echo '' . PHP_EOL; +} +echo '
    ' . + $worksheet->getCell($col . $row) + ->getValue() . + '
    ' . PHP_EOL; +``` + +Note that we can't use a `<=` comparison here, because `'AA'` would match +as `<= 'B'`, so we increment the highest column letter and then loop +while `$col !=` the incremented highest column. + +## Using value binders to facilitate data entry + +Internally, PhpSpreadsheet uses a default +`\PhpOffice\PhpSpreadsheet\Cell\IValueBinder` implementation +(\PhpOffice\PhpSpreadsheet\Cell\DefaultValueBinder) to determine data +types of entered data using a cell's `setValue()` method (the +`setValueExplicit()` method bypasses this check). + +Optionally, the default behaviour of PhpSpreadsheet can be modified, +allowing easier data entry. For example, a +`\PhpOffice\PhpSpreadsheet\Cell\AdvancedValueBinder` class is available. +It automatically converts percentages, number in scientific format, and +dates entered as strings to the correct format, also setting the cell's +style information. The following example demonstrates how to set the +value binder in PhpSpreadsheet: + +``` php +/** PhpSpreadsheet */ +require_once 'src/Boostrap.php'; + +// Set value binder +\PhpOffice\PhpSpreadsheet\Cell\Cell::setValueBinder( new \PhpOffice\PhpSpreadsheet\Cell\AdvancedValueBinder() ); + +// Create new Spreadsheet object +$spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet(); + +// ... +// Add some data, resembling some different data types +$spreadsheet->getActiveSheet()->setCellValue('A4', 'Percentage value:'); +// Converts the string value to 0.1 and sets percentage cell style +$spreadsheet->getActiveSheet()->setCellValue('B4', '10%'); + +$spreadsheet->getActiveSheet()->setCellValue('A5', 'Date/time value:'); +// Converts the string value to an Excel datestamp and sets the date format cell style +$spreadsheet->getActiveSheet()->setCellValue('B5', '21 December 1983'); +``` + +**Creating your own value binder is easy.** When advanced value binding +is required, you can implement the +`\PhpOffice\PhpSpreadsheet\Cell\IValueBinder` interface or extend the +`\PhpOffice\PhpSpreadsheet\Cell\DefaultValueBinder` or +`\PhpOffice\PhpSpreadsheet\Cell\AdvancedValueBinder` classes. diff --git a/vendor/phpoffice/phpspreadsheet/docs/topics/architecture.md b/vendor/phpoffice/phpspreadsheet/docs/topics/architecture.md new file mode 100644 index 0000000..0295d67 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/docs/topics/architecture.md @@ -0,0 +1,75 @@ +# Architecture + +## Schematical + +![01-schematic.png](./images/01-schematic.png "Basic Architecture Schematic") + +## AutoLoader + +PhpSpreadsheet relies on Composer autoloader. So before working with +PhpSpreadsheet in standalone, be sure to run `composer install`. Or add it to a +pre-existing project with `composer require phpoffice/phpspreadsheet`. + +## Spreadsheet in memory + +PhpSpreadsheet's architecture is built in a way that it can serve as an +in-memory spreadsheet. This means that, if one would want to create a +web based view of a spreadsheet which communicates with PhpSpreadsheet's +object model, he would only have to write the front-end code. + +Just like desktop spreadsheet software, PhpSpreadsheet represents a +spreadsheet containing one or more worksheets, which contain cells with +data, formulas, images, ... + +## Readers and writers + +On its own, the `Spreadsheet` class does not provide the functionality +to read from or write to a persisted spreadsheet (on disk or in a +database). To provide that functionality, readers and writers can be +used. + +By default, the PhpSpreadsheet package provides some readers and +writers, including one for the Open XML spreadsheet format (a.k.a. Excel +2007 file format). You are not limited to the default readers and +writers, as you are free to implement the +`\PhpOffice\PhpSpreadsheet\Reader\IReader` and +`\PhpOffice\PhpSpreadsheet\Writer\IWriter` interface in a custom class. + +![02-readers-writers.png](./images/02-readers-writers.png "Readers/Writers") + +## Fluent interfaces + +PhpSpreadsheet supports fluent interfaces in most locations. This means +that you can easily "chain" calls to specific methods without requiring +a new PHP statement. For example, take the following code: + +``` php +$spreadsheet->getProperties()->setCreator("Maarten Balliauw"); +$spreadsheet->getProperties()->setLastModifiedBy("Maarten Balliauw"); +$spreadsheet->getProperties()->setTitle("Office 2007 XLSX Test Document"); +$spreadsheet->getProperties()->setSubject("Office 2007 XLSX Test Document"); +$spreadsheet->getProperties()->setDescription("Test document for Office 2007 XLSX, generated using PHP classes."); +$spreadsheet->getProperties()->setKeywords("office 2007 openxml php"); +$spreadsheet->getProperties()->setCategory("Test result file"); +``` + +This can be rewritten as: + +``` php +$spreadsheet->getProperties() + ->setCreator("Maarten Balliauw") + ->setLastModifiedBy("Maarten Balliauw") + ->setTitle("Office 2007 XLSX Test Document") + ->setSubject("Office 2007 XLSX Test Document") + ->setDescription("Test document for Office 2007 XLSX, generated using PHP classes.") + ->setKeywords("office 2007 openxml php") + ->setCategory("Test result file"); +``` + +> **Using fluent interfaces is not required** Fluent interfaces have +> been implemented to provide a convenient programming API. Use of them +> is not required, but can make your code easier to read and maintain. +> It can also improve performance, as you are reducing the overall +> number of calls to PhpSpreadsheet methods: in the above example, the +> `getProperties()` method is being called only once rather than 7 times +> in the non-fluent version. diff --git a/vendor/phpoffice/phpspreadsheet/docs/topics/autofilters.md b/vendor/phpoffice/phpspreadsheet/docs/topics/autofilters.md new file mode 100644 index 0000000..66321ee --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/docs/topics/autofilters.md @@ -0,0 +1,530 @@ +# AutoFilter Reference + +## Introduction + +Each worksheet in an Excel Workbook can contain a single autoFilter +range. Filtered data displays only the rows that meet criteria that you +specify and hides rows that you do not want displayed. You can filter by +more than one column: filters are additive, which means that each +additional filter is based on the current filter and further reduces the +subset of data. + +![01-01-autofilter.png](./images/01-01-autofilter.png) + +When an AutoFilter is applied to a range of cells, the first row in an +autofilter range will be the heading row, which displays the autoFilter +dropdown icons. It is not part of the actual autoFiltered data. All +subsequent rows are the autoFiltered data. So an AutoFilter range should +always contain the heading row and one or more data rows (one data row +is pretty meaningless), but PhpSpreadsheet won't actually stop you +specifying a meaningless range: it's up to you as a developer to avoid +such errors. + +To determine if a filter is applied, note the icon in the column +heading. A drop-down arrow +(![01-03-filter-icon-1.png](./images/01-03-filter-icon-1.png)) means +that filtering is enabled but not applied. In MS Excel, when you hover +over the heading of a column with filtering enabled but not applied, a +screen tip displays the cell text for the first row in that column, and +the message "(Showing All)". + +![01-02-autofilter.png](./images/01-02-autofilter.png) + +A Filter button +(![01-03-filter-icon-2.png](./images/01-03-filter-icon-2.png)) means +that a filter is applied. When you hover over the heading of a filtered +column, a screen tip displays the filter that has been applied to that +column, such as "Equals a red cell color" or "Larger than 150". + +![01-04-autofilter.png](./images/01-04-autofilter.png) + +## Setting an AutoFilter area on a worksheet + +To set an autoFilter on a range of cells. + +``` php +$spreadsheet->getActiveSheet()->setAutoFilter('A1:E20'); +``` + +The first row in an autofilter range will be the heading row, which +displays the autoFilter dropdown icons. It is not part of the actual +autoFiltered data. All subsequent rows are the autoFiltered data. So an +AutoFilter range should always contain the heading row and one or more +data rows (one data row is pretty meaningless, but PhpSpreadsheet won't +actually stop you specifying a meaningless range: it's up to you as a +developer to avoid such errors. + +If you want to set the whole worksheet as an autofilter region + +``` php +$spreadsheet->getActiveSheet()->setAutoFilter( + $spreadsheet->getActiveSheet() + ->calculateWorksheetDimension() +); +``` + +This enables filtering, but does not actually apply any filters. + +## Autofilter Expressions + +PHPEXcel 1.7.8 introduced the ability to actually create, read and write +filter expressions; initially only for Xlsx files, but later releases +will extend this to other formats. + +To apply a filter expression to an autoFilter range, you first need to +identify which column you're going to be applying this filter to. + +``` php +$autoFilter = $spreadsheet->getActiveSheet()->getAutoFilter(); +$columnFilter = $autoFilter->getColumn('C'); +``` + +This returns an autoFilter column object, and you can then apply filter +expressions to that column. + +There are a number of different types of autofilter expressions. The +most commonly used are: + +- Simple Filters +- DateGroup Filters +- Custom filters +- Dynamic Filters +- Top Ten Filters + +These different types are mutually exclusive within any single column. +You should not mix the different types of filter in the same column. +PhpSpreadsheet will not actively prevent you from doing this, but the +results are unpredictable. + +Other filter expression types (such as cell colour filters) are not yet +supported. + +### Simple filters + +In MS Excel, Simple Filters are a dropdown list of all values used in +that column, and the user can select which ones they want to display and +which ones they want to hide by ticking and unticking the checkboxes +alongside each option. When the filter is applied, rows containing the +checked entries will be displayed, rows that don't contain those values +will be hidden. + +![04-01-simple-autofilter.png](./images/04-01-simple-autofilter.png) + +To create a filter expression, we need to start by identifying the +filter type. In this case, we're just going to specify that this filter +is a standard filter. + +``` php +$columnFilter->setFilterType( + \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column::AUTOFILTER_FILTERTYPE_FILTER +); +``` + +Now we've identified the filter type, we can create a filter rule and +set the filter values: + +When creating a simple filter in PhpSpreadsheet, you only need to +specify the values for "checked" columns: you do this by creating a +filter rule for each value. + +``` php +$columnFilter->createRule() + ->setRule( + \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_EQUAL, + 'France' + ); + +$columnFilter->createRule() + ->setRule( + \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_EQUAL, + 'Germany' + ); +``` + +This creates two filter rules: the column will be filtered by values +that match "France" OR "Germany". For Simple Filters, you can create as +many rules as you want + +Simple filters are always a comparison match of EQUALS, and multiple +standard filters are always treated as being joined by an OR condition. + +#### Matching Blanks + +If you want to create a filter to select blank cells, you would use: + +``` php +$columnFilter->createRule() + ->setRule( + \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_EQUAL, + '' + ); +``` + +### DateGroup Filters + +In MS Excel, DateGroup filters provide a series of dropdown filter +selectors for date values, so you can specify entire years, or months +within a year, or individual days within each month. + +![04-02-dategroup-autofilter.png](./images/04-02-dategroup-autofilter.png) + +DateGroup filters are still applied as a Standard Filter type. + +``` php +$columnFilter->setFilterType( + \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column::AUTOFILTER_FILTERTYPE_FILTER +); +``` + +Creating a dateGroup filter in PhpSpreadsheet, you specify the values +for "checked" columns as an associative array of year. month, day, hour +minute and second. To select a year and month, you need to create a +DateGroup rule identifying the selected year and month: + +``` php +$columnFilter->createRule() + ->setRule( + \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_EQUAL, + [ + 'year' => 2012, + 'month' => 1 + ] + ) + ->setRuleType( + \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP + ); +``` + +The key values for the associative array are: + +- year +- month +- day +- hour +- minute +- second + +Like Standard filters, DateGroup filters are always a match of EQUALS, +and multiple standard filters are always treated as being joined by an +OR condition. + +Note that we alse specify a ruleType: to differentiate this from a +standard filter, we explicitly set the Rule's Type to +AUTOFILTER\_RULETYPE\_DATEGROUP. As with standard filters, we can create +any number of DateGroup Filters. + +### Custom filters + +In MS Excel, Custom filters allow us to select more complex conditions +using an operator as well as a value. Typical examples might be values +that fall within a range (e.g. between -20 and +20), or text values with +wildcards (e.g. beginning with the letter U). To handle this, they + +![04-03-custom-autofilter-1.png](./images/04-03-custom-autofilter-1.png) + +![04-03-custom-autofilter-2.png](./images/04-03-custom-autofilter-2.png) + +Custom filters are limited to 2 rules, and these can be joined using +either an AND or an OR. + +We start by specifying a Filter type, this time a CUSTOMFILTER. + +``` php +$columnFilter->setFilterType( + \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column::AUTOFILTER_FILTERTYPE_CUSTOMFILTER +); +``` + +And then define our rules. + +The following shows a simple wildcard filter to show all column entries +beginning with the letter `U`. + +``` php +$columnFilter->createRule() + ->setRule( + \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_EQUAL, + 'U*' + ) + ->setRuleType( + \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_CUSTOMFILTER + ); +``` + +MS Excel uses \* as a wildcard to match any number of characters, and ? +as a wildcard to match a single character. 'U\*' equates to "begins with +a 'U'"; '\*U' equates to "ends with a 'U'"; and '\*U\*' equates to +"contains a 'U'" + +If you want to match explicitly against a \* or a ? character, you can +escape it with a tilde (\~), so ?\~\*\* would explicitly match for a \* +character as the second character in the cell value, followed by any +number of other characters. The only other character that needs escaping +is the \~ itself. + +To create a "between" condition, we need to define two rules: + +``` php +$columnFilter->createRule() + ->setRule( + \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL, + -20 + ) + ->setRuleType( + \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_CUSTOMFILTER + ); +$columnFilter->createRule() + ->setRule( + \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_LESSTHANOREQUAL, + 20 + ) + ->setRuleType( + \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_CUSTOMFILTER + ); +``` + +We also set the rule type to CUSTOMFILTER. + +This defined two rules, filtering numbers that are `>= -20` OR `<= +20`, so we also need to modify the join condition to reflect AND rather +than OR. + +``` php +$columnFilter->setAndOr( + \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column::AUTOFILTER_COLUMN_ANDOR_AND +); +``` + +The valid set of operators for Custom Filters are defined in the +`\PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule` class, and +comprise: + +Operator Constant | Value +------------------------------------------|---------------------- +AUTOFILTER_COLUMN_RULE_EQUAL | 'equal' +AUTOFILTER_COLUMN_RULE_NOTEQUAL | 'notEqual' +AUTOFILTER_COLUMN_RULE_GREATERTHAN | 'greaterThan' +AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL | 'greaterThanOrEqual' +AUTOFILTER_COLUMN_RULE_LESSTHAN | 'lessThan' +AUTOFILTER_COLUMN_RULE_LESSTHANOREQUAL | 'lessThanOrEqual' + +### Dynamic Filters + +Dynamic Filters are based on a dynamic comparison condition, where the +value we're comparing against the cell values is variable, such as +'today'; or when we're testing against an aggregate of the cell data +(e.g. 'aboveAverage'). Only a single dynamic filter can be applied to a +column at a time. + +![04-04-dynamic-autofilter.png](./images/04-04-dynamic-autofilter.png) + +Again, we start by specifying a Filter type, this time a DYNAMICFILTER. + +``` php +$columnFilter->setFilterType( + \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column::AUTOFILTER_FILTERTYPE_DYNAMICFILTER +); +``` + +When defining the rule for a dynamic filter, we don't define a value (we +can simply set that to NULL) but we do specify the dynamic filter +category. + +``` php +$columnFilter->createRule() + ->setRule( + \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_EQUAL, + NULL, + \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_YEARTODATE + ) + ->setRuleType( + \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMICFILTER + ); +``` + +We also set the rule type to DYNAMICFILTER. + +The valid set of dynamic filter categories is defined in the +`\PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule` class, and +comprises: + +Operator Constant | Value +-----------------------------------------|---------------- +AUTOFILTER_RULETYPE_DYNAMIC_YESTERDAY | 'yesterday' +AUTOFILTER_RULETYPE_DYNAMIC_TODAY | 'today' +AUTOFILTER_RULETYPE_DYNAMIC_TOMORROW | 'tomorrow' +AUTOFILTER_RULETYPE_DYNAMIC_YEARTODATE | 'yearToDate' +AUTOFILTER_RULETYPE_DYNAMIC_THISYEAR | 'thisYear' +AUTOFILTER_RULETYPE_DYNAMIC_THISQUARTER | 'thisQuarter' +AUTOFILTER_RULETYPE_DYNAMIC_THISMONTH | 'thisMonth' +AUTOFILTER_RULETYPE_DYNAMIC_THISWEEK | 'thisWeek' +AUTOFILTER_RULETYPE_DYNAMIC_LASTYEAR | 'lastYear' +AUTOFILTER_RULETYPE_DYNAMIC_LASTQUARTER | 'lastQuarter' +AUTOFILTER_RULETYPE_DYNAMIC_LASTMONTH | 'lastMonth' +AUTOFILTER_RULETYPE_DYNAMIC_LASTWEEK | 'lastWeek' +AUTOFILTER_RULETYPE_DYNAMIC_NEXTYEAR | 'nextYear' +AUTOFILTER_RULETYPE_DYNAMIC_NEXTQUARTER | 'nextQuarter' +AUTOFILTER_RULETYPE_DYNAMIC_NEXTMONTH | 'nextMonth' +AUTOFILTER_RULETYPE_DYNAMIC_NEXTWEEK | 'nextWeek' +AUTOFILTER_RULETYPE_DYNAMIC_MONTH_1 | 'M1' +AUTOFILTER_RULETYPE_DYNAMIC_JANUARY | 'M1' +AUTOFILTER_RULETYPE_DYNAMIC_MONTH_2 | 'M2' +AUTOFILTER_RULETYPE_DYNAMIC_FEBRUARY | 'M2' +AUTOFILTER_RULETYPE_DYNAMIC_MONTH_3 | 'M3' +AUTOFILTER_RULETYPE_DYNAMIC_MARCH | 'M3' +AUTOFILTER_RULETYPE_DYNAMIC_MONTH_4 | 'M4' +AUTOFILTER_RULETYPE_DYNAMIC_APRIL | 'M4' +AUTOFILTER_RULETYPE_DYNAMIC_MONTH_5 | 'M5' +AUTOFILTER_RULETYPE_DYNAMIC_MAY | 'M5' +AUTOFILTER_RULETYPE_DYNAMIC_MONTH_6 | 'M6' +AUTOFILTER_RULETYPE_DYNAMIC_JUNE | 'M6' +AUTOFILTER_RULETYPE_DYNAMIC_MONTH_7 | 'M7' +AUTOFILTER_RULETYPE_DYNAMIC_JULY | 'M7' +AUTOFILTER_RULETYPE_DYNAMIC_MONTH_8 | 'M8' +AUTOFILTER_RULETYPE_DYNAMIC_AUGUST | 'M8' +AUTOFILTER_RULETYPE_DYNAMIC_MONTH_9 | 'M9' +AUTOFILTER_RULETYPE_DYNAMIC_SEPTEMBER | 'M9' +AUTOFILTER_RULETYPE_DYNAMIC_MONTH_10 | 'M10' +AUTOFILTER_RULETYPE_DYNAMIC_OCTOBER | 'M10' +AUTOFILTER_RULETYPE_DYNAMIC_MONTH_11 | 'M11' +AUTOFILTER_RULETYPE_DYNAMIC_NOVEMBER | 'M11' +AUTOFILTER_RULETYPE_DYNAMIC_MONTH_12 | 'M12' +AUTOFILTER_RULETYPE_DYNAMIC_DECEMBER | 'M12' +AUTOFILTER_RULETYPE_DYNAMIC_QUARTER_1 | 'Q1' +AUTOFILTER_RULETYPE_DYNAMIC_QUARTER_2 | 'Q2' +AUTOFILTER_RULETYPE_DYNAMIC_QUARTER_3 | 'Q3' +AUTOFILTER_RULETYPE_DYNAMIC_QUARTER_4 | 'Q4' +AUTOFILTER_RULETYPE_DYNAMIC_ABOVEAVERAGE | 'aboveAverage' +AUTOFILTER_RULETYPE_DYNAMIC_BELOWAVERAGE | 'belowAverage' + +We can only apply a single Dynamic Filter rule to a column at a time. + +### Top Ten Filters + +Top Ten Filters are similar to Dynamic Filters in that they are based on +a summarisation of the actual data values in the cells. However, unlike +Dynamic Filters where you can only select a single option, Top Ten +Filters allow you to select based on a number of criteria: + +![04-05-custom-topten-1.png](./images/04-05-topten-autofilter-1.png) + +![04-05-custom-topten-2.png](./images/04-05-topten-autofilter-2.png) + +You can identify whether you want the top (highest) or bottom (lowest) +values.You can identify how many values you wish to select in the +filterYou can identify whether this should be a percentage or a number +of items. + +Like Dynamic Filters, only a single Top Ten filter can be applied to a +column at a time. + +We start by specifying a Filter type, this time a DYNAMICFILTER. + +``` php +$columnFilter->setFilterType( + \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column::AUTOFILTER_FILTERTYPE_TOPTENFILTER +); +``` + +Then we create the rule: + +``` php +$columnFilter->createRule() + ->setRule( + \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_PERCENT, + 5, + \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP + ) + ->setRuleType( + \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_TOPTENFILTER + ); +``` + +This will filter the Top 5 percent of values in the column. + +To specify the lowest (bottom 2 values), we would specify a rule of: + +``` php +$columnFilter->createRule() + ->setRule( + \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_BY_VALUE, + 5, + \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_BOTTOM + ) + ->setRuleType( + \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_TOPTENFILTER + ); +``` + +The option values for TopTen Filters top/bottom value/percent are all +defined in the +`\PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule` class, and +comprise: + +Operator Constant | Value +---------------------------------------|------------- +AUTOFILTER_COLUMN_RULE_TOPTEN_BY_VALUE | 'byValue' +AUTOFILTER_COLUMN_RULE_TOPTEN_PERCENT | 'byPercent' + +and + +Operator Constant | Value +-------------------------------------|---------- +AUTOFILTER_COLUMN_RULE_TOPTEN_TOP | 'top' +AUTOFILTER_COLUMN_RULE_TOPTEN_BOTTOM | 'bottom' + +## Executing an AutoFilter + +When an autofilter is applied in MS Excel, it sets the row +hidden/visible flags for each row of the autofilter area based on the +selected criteria, so that only those rows that match the filter +criteria are displayed. + +PhpSpreadsheet will not execute the equivalent function automatically +when you set or change a filter expression, but only when the file is +saved. + +### Applying the Filter + +If you wish to execute your filter from within a script, you need to do +this manually. You can do this using the autofilters `showHideRows()` +method. + +``` php +$autoFilter = $spreadsheet->getActiveSheet()->getAutoFilter(); +$autoFilter->showHideRows(); +``` + +This will set all rows that match the filter criteria to visible, while +hiding all other rows within the autofilter area. + +### Displaying Filtered Rows + +Simply looping through the rows in an autofilter area will still access +ever row, whether it matches the filter criteria or not. To selectively +access only the filtered rows, you need to test each row’s visibility +settings. + +``` php +foreach ($spreadsheet->getActiveSheet()->getRowIterator() as $row) { + if ($spreadsheet->getActiveSheet() + ->getRowDimension($row->getRowIndex())->getVisible()) { + echo ' Row number - ' , $row->getRowIndex() , ' '; + echo $spreadsheet->getActiveSheet() + ->getCell( + 'C'.$row->getRowIndex() + ) + ->getValue(), ' '; + echo $spreadsheet->getActiveSheet() + ->getCell( + 'D'.$row->getRowIndex() + )->getFormattedValue(), ' '; + echo PHP_EOL; + } +} +``` + +## AutoFilter Sorting + +In MS Excel, Autofiltering also allows the rows to be sorted. This +feature is ***not*** supported by PhpSpreadsheet. diff --git a/vendor/phpoffice/phpspreadsheet/docs/topics/calculation-engine.md b/vendor/phpoffice/phpspreadsheet/docs/topics/calculation-engine.md new file mode 100644 index 0000000..779d73e --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/docs/topics/calculation-engine.md @@ -0,0 +1,2098 @@ +# Calculation Engine + +## Using the PhpSpreadsheet calculation engine + +### Performing formula calculations + +As PhpSpreadsheet represents an in-memory spreadsheet, it also offers +formula calculation capabilities. A cell can be of a value type +(containing a number or text), or a formula type (containing a formula +which can be evaluated). For example, the formula `=SUM(A1:A10)` +evaluates to the sum of values in A1, A2, ..., A10. + +To calculate a formula, you can call the cell containing the formula’s +method `getCalculatedValue()`, for example: + +``` php +$spreadsheet->getActiveSheet()->getCell('E11')->getCalculatedValue(); +``` + +If you write the following line of code in the invoice demo included +with PhpSpreadsheet, it evaluates to the value "64": + +![09-command-line-calculation.png](./images/09-command-line-calculation.png) + +Another nice feature of PhpSpreadsheet's formula parser, is that it can +automatically adjust a formula when inserting/removing rows/columns. +Here's an example: + +![09-formula-in-cell-1.png](./images/09-formula-in-cell-1.png) + +You see that the formula contained in cell E11 is "SUM(E4:E9)". Now, +when I write the following line of code, two new product lines are +added: + +``` php +$spreadsheet->getActiveSheet()->insertNewRowBefore(7, 2); +``` + +![09-formula-in-cell-2.png](./images/09-formula-in-cell-2.png) + +Did you notice? The formula in the former cell E11 (now E13, as I +inserted 2 new rows), changed to "SUM(E4:E11)". Also, the inserted cells +duplicate style information of the previous cell, just like Excel's +behaviour. Note that you can both insert rows and columns. + +## Calculation Cache + +Once the Calculation engine has evaluated the formula in a cell, the result +will be cached, so if you call `getCalculatedValue()` a second time for the +same cell, the result will be returned from the cache rather than evaluating +the formula a second time. This helps boost performance, because evaluating +a formula is an expensive operation in terms of performance and speed. + +However, there may be times when you don't want this, perhaps you've changed +the underlying data and need to re-evaluate the same formula with that new +data. + +``` +Calculation::getInstance($spreadsheet)->disableCalculationCache(); +``` + +Will disable calculation caching, and flush the current calculation cache. + +If you want only to flush the cache, then you can call + +``` +Calculation::getInstance($spreadsheet)->clearCalculationCache(); +``` + +## Known limitations + +There are some known limitations to the PhpSpreadsheet calculation +engine. Most of them are due to the fact that an Excel formula is +converted into PHP code before being executed. This means that Excel +formula calculation is subject to PHP's language characteristics. + +### Function that are not Supported in Xls + +Not all functions are supported, for a comprehensive list, read the +[function list by name](../references/function-list-by-name.md). + +#### Operator precedence + +In Excel `+` wins over `&`, just like `*` wins over `+` in ordinary +algebra. The former rule is not what one finds using the calculation +engine shipped with PhpSpreadsheet. + +- [Reference for Excel](https://support.office.com/en-us/article/Calculation-operators-and-precedence-in-Excel-48be406d-4975-4d31-b2b8-7af9e0e2878a) +- [Reference for PHP](https://php.net/manual/en/language.operators.php) + +#### Formulas involving numbers and text + +Formulas involving numbers and text may produce unexpected results or +even unreadable file contents. For example, the formula `=3+"Hello "` is +expected to produce an error in Excel (\#VALUE!). Due to the fact that +PHP converts `"Hello "` to a numeric value (zero), the result of this +formula is evaluated as 3 instead of evaluating as an error. This also +causes the Excel document being generated as containing unreadable +content. + +- [Reference for this behaviour in PHP](https://php.net/manual/en/language.types.string.php#language.types.string.conversion) + +#### Formulas don’t seem to be calculated in Excel2003 using compatibility pack? + +This is normal behaviour of the compatibility pack, Xlsx displays this +correctly. Use `\PhpOffice\PhpSpreadsheet\Writer\Xls` if you really need +calculated values, or force recalculation in Excel2003. + +## Handling Date and Time Values + +### Excel functions that return a Date and Time value + +Any of the Date and Time functions that return a date value in Excel can +return either an Excel timestamp or a PHP timestamp or `DateTime` object. + +It is possible for scripts to change the data type used for returning +date values by calling the +`\PhpOffice\PhpSpreadsheet\Calculation\Functions::setReturnDateType()` +method: + +``` php +\PhpOffice\PhpSpreadsheet\Calculation\Functions::setReturnDateType($returnDateType); +``` + +where the following constants can be used for `$returnDateType`: + +- `\PhpOffice\PhpSpreadsheet\Calculation\Functions::RETURNDATE_PHP_NUMERIC` +- `\PhpOffice\PhpSpreadsheet\Calculation\Functions::RETURNDATE_PHP_OBJECT` +- `\PhpOffice\PhpSpreadsheet\Calculation\Functions::RETURNDATE_EXCEL` + +The method will return a Boolean True on success, False on failure (e.g. +if an invalid value is passed in for the return date type). + +The `\PhpOffice\PhpSpreadsheet\Calculation\Functions::getReturnDateType()` +method can be used to determine the current value of this setting: + +``` php +$returnDateType = \PhpOffice\PhpSpreadsheet\Calculation\Functions::getReturnDateType(); +``` + +The default is `RETURNDATE_PHP_NUMERIC`. + +#### PHP Timestamps + +If `RETURNDATE_PHP_NUMERIC` is set for the Return Date Type, then any +date value returned to the calling script by any access to the Date and +Time functions in Excel will be an integer value that represents the +number of seconds from the PHP/Unix base date. The PHP/Unix base date +(0) is 00:00 UST on 1st January 1970. This value can be positive or +negative: so a value of -3600 would be 23:00 hrs on 31st December 1969; +while a value of +3600 would be 01:00 hrs on 1st January 1970. This +gives PHP a date range of between 14th December 1901 and 19th January +2038. + +#### PHP `DateTime` Objects + +If the Return Date Type is set for `RETURNDATE_PHP_OBJECT`, then any +date value returned to the calling script by any access to the Date and +Time functions in Excel will be a PHP `DateTime` object. + +#### Excel Timestamps + +If `RETURNDATE_EXCEL` is set for the Return Date Type, then the returned +date value by any access to the Date and Time functions in Excel will be +a floating point value that represents a number of days from the Excel +base date. The Excel base date is determined by which calendar Excel +uses: the Windows 1900 or the Mac 1904 calendar. 1st January 1900 is the +base date for the Windows 1900 calendar while 1st January 1904 is the +base date for the Mac 1904 calendar. + +It is possible for scripts to change the calendar used for calculating +Excel date values by calling the +`\PhpOffice\PhpSpreadsheet\Shared\Date::setExcelCalendar()` method: + +``` php +\PhpOffice\PhpSpreadsheet\Shared\Date::setExcelCalendar($baseDate); +``` + +where the following constants can be used for `$baseDate`: + +- `\PhpOffice\PhpSpreadsheet\Shared\Date::CALENDAR_WINDOWS_1900` +- `\PhpOffice\PhpSpreadsheet\Shared\Date::CALENDAR_MAC_1904` + +The method will return a Boolean True on success, False on failure (e.g. +if an invalid value is passed in). + +The `\PhpOffice\PhpSpreadsheet\Shared\Date::getExcelCalendar()` method can +be used to determine the current value of this setting: + +``` php +$baseDate = \PhpOffice\PhpSpreadsheet\Shared\Date::getExcelCalendar(); +``` + +The default is `CALENDAR_WINDOWS_1900`. + +#### Functions that return a Date/Time Value + +- DATE +- DATEVALUE +- EDATE +- EOMONTH +- NOW +- TIME +- TIMEVALUE +- TODAY + +### Excel functions that accept Date and Time values as parameters + +Date values passed in as parameters to a function can be an Excel +timestamp or a PHP timestamp; or `DateTime` object; or a string containing a +date value (e.g. '1-Jan-2009'). PhpSpreadsheet will attempt to identify +their type based on the PHP datatype: + +An integer numeric value will be treated as a PHP/Unix timestamp. A real +(floating point) numeric value will be treated as an Excel +date/timestamp. Any PHP `DateTime` object will be treated as a `DateTime` +object. Any string value (even one containing straight numeric data) +will be converted to a `DateTime` object for validation as a date value +based on the server locale settings, so passing through an ambiguous +value of '07/08/2008' will be treated as 7th August 2008 if your server +settings are UK, but as 8th July 2008 if your server settings are US. +However, if you pass through a value such as '31/12/2008' that would be +considered an error by a US-based server, but which is not ambiguous, +then PhpSpreadsheet will attempt to correct this to 31st December 2008. +If the content of the string doesn’t match any of the formats recognised +by the php `DateTime` object implementation of `strtotime()` (which can +handle a wider range of formats than the normal `strtotime()` function), +then the function will return a `#VALUE` error. However, Excel +recommends that you should always use date/timestamps for your date +functions, and the recommendation for PhpSpreadsheet is the same: avoid +strings because the result is not predictable. + +The same principle applies when data is being written to Excel. Cells +containing date actual values (rather than Excel functions that return a +date value) are always written as Excel dates, converting where +necessary. If a cell formatted as a date contains an integer or +`DateTime` object value, then it is converted to an Excel value for +writing: if a cell formatted as a date contains a real value, then no +conversion is required. Note that string values are written as strings +rather than converted to Excel date timestamp values. + +#### Functions that expect a Date/Time Value + +- DATEDIF +- DAY +- DAYS360 +- EDATE +- EOMONTH +- HOUR +- MINUTE +- MONTH +- NETWORKDAYS +- SECOND +- WEEKDAY +- WEEKNUM +- WORKDAY +- YEAR +- YEARFRAC + +### Helper Methods + +In addition to the `setExcelCalendar()` and `getExcelCalendar()` methods, a +number of other methods are available in the +`\PhpOffice\PhpSpreadsheet\Shared\Date` class that can help when working +with dates: + +#### \PhpOffice\PhpSpreadsheet\Shared\Date::excelToTimestamp($excelDate) + +Converts a date/time from an Excel date timestamp to return a PHP +serialized date/timestamp. + +Note that this method does not trap for Excel dates that fall outside of +the valid range for a PHP date timestamp. + +#### \PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject($excelDate) + +Converts a date from an Excel date/timestamp to return a PHP `DateTime` +object. + +#### \PhpOffice\PhpSpreadsheet\Shared\Date::PHPToExcel($PHPDate) + +Converts a PHP serialized date/timestamp or a PHP `DateTime` object to +return an Excel date timestamp. + +#### \PhpOffice\PhpSpreadsheet\Shared\Date::formattedPHPToExcel($year, $month, $day, $hours=0, $minutes=0, $seconds=0) + +Takes year, month and day values (and optional hour, minute and second +values) and returns an Excel date timestamp value. + +### Timezone support for Excel date timestamp conversions + +The default timezone for the date functions in PhpSpreadsheet is UST (Universal Standard Time). +If a different timezone needs to be used, these methods are available: + +#### \PhpOffice\PhpSpreadsheet\Shared\Date::getDefaultTimezone() + +Returns the current timezone value PhpSpeadsheet is using to handle dates and times. + +#### \PhpOffice\PhpSpreadsheet\Shared\Date::setDefaultTimezone($timeZone) + +Sets the timezone for Excel date timestamp conversions to $timeZone, +which must be a valid PHP DateTimeZone value. +The return value is a Boolean, where true is success, +and false is failure (e.g. an invalid DateTimeZone value was passed.) + +#### \PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject($excelDate, $timeZone) +#### \PhpOffice\PhpSpreadsheet\Shared\Date::excelToTimeStamp($excelDate, $timeZone) + +These functions support a timezone as an optional second parameter. +This applies a specific timezone to that function call without affecting the default PhpSpreadsheet Timezone. + +## Function Reference + +### Database Functions + +#### DAVERAGE + +The DAVERAGE function returns the average value of the cells in a column +of a list or database that match conditions you specify. + +##### Syntax + + DAVERAGE (database, field, criteria) + +##### Parameters + +**database** The range of cells that makes up the list or database. + +A database is a list of related data in which rows of related +information are records, and columns of data are fields. The first row +of the list contains labels for each column. + +**field** Indicates which column of the database is used in the +function. + +Enter the column label as a string (enclosed between double quotation +marks), such as "Age" or "Yield," or as a number (without quotation +marks) that represents the position of the column within the list: 1 for +the first column, 2 for the second column, and so on. + +**criteria** The range of cells that contains the conditions you +specify. + +You can use any range for the criteria argument, as long as it includes +at least one column label and at least one cell below the column label +in which you specify a condition for the column. + +##### Return Value + +**float** The average value of the matching cells. + +This is the statistical mean. + +##### Examples + +``` php +$database = [ + [ 'Tree', 'Height', 'Age', 'Yield', 'Profit' ], + [ 'Apple', 18, 20, 14, 105.00 ], + [ 'Pear', 12, 12, 10, 96.00 ], + [ 'Cherry', 13, 14, 9, 105.00 ], + [ 'Apple', 14, 15, 10, 75.00 ], + [ 'Pear', 9, 8, 8, 76.80 ], + [ 'Apple', 8, 9, 6, 45.00 ], +]; + +$criteria = [ + [ 'Tree', 'Height', 'Age', 'Yield', 'Profit', 'Height' ], + [ '="=Apple"', '>10', NULL, NULL, NULL, '<16' ], + [ '="=Pear"', NULL, NULL, NULL, NULL, NULL ], +]; + +$worksheet->fromArray( $criteria, NULL, 'A1' ) + ->fromArray( $database, NULL, 'A4' ); + +$worksheet->setCellValue('A12', '=DAVERAGE(A4:E10,"Yield",A1:B2)'); + +$retVal = $worksheet->getCell('A12')->getCalculatedValue(); +// $retVal = 12 +``` + +##### Notes + +There are no additional notes on this function + +#### DCOUNT + +The DCOUNT function returns the count of cells that contain a number in +a column of a list or database matching conditions that you specify. + +##### Syntax + + DCOUNT(database, [field], criteria) + +##### Parameters + +**database** The range of cells that makes up the list or database. + +A database is a list of related data in which rows of related +information are records, and columns of data are fields. The first row +of the list contains labels for each column. + +**field** Indicates which column of the database is used in the +function. + +Enter the column label as a string (enclosed between double quotation +marks), such as "Age" or "Yield," or as a number (without quotation +marks) that represents the position of the column within the list: 1 for +the first column, 2 for the second column, and so on. + +**criteria** The range of cells that contains the conditions you +specify. + +You can use any range for the criteria argument, as long as it includes +at least one column label and at least one cell below the column label +in which you specify a condition for the column. + +##### Return Value + +**float** The count of the matching cells. + +##### Examples + +``` php +$database = [ + [ 'Tree', 'Height', 'Age', 'Yield', 'Profit' ], + [ 'Apple', 18, 20, 14, 105.00 ], + [ 'Pear', 12, 12, 10, 96.00 ], + [ 'Cherry', 13, 14, 9, 105.00 ], + [ 'Apple', 14, 15, 10, 75.00 ], + [ 'Pear', 9, 8, 8, 76.80 ], + [ 'Apple', 8, 9, 6, 45.00 ], +]; + +$criteria = [ + [ 'Tree', 'Height', 'Age', 'Yield', 'Profit', 'Height' ], + [ '="=Apple"', '>10', NULL, NULL, NULL, '<16' ], + [ '="=Pear"', NULL, NULL, NULL, NULL, NULL ], +]; + +$worksheet->fromArray( $criteria, NULL, 'A1' ) + ->fromArray( $database, NULL, 'A4' ); + +$worksheet->setCellValue('A12', '=DCOUNT(A4:E10,"Height",A1:B3)'); + +$retVal = $worksheet->getCell('A12')->getCalculatedValue(); + +// $retVal = 3 +``` + +##### Notes + +In MS Excel, The field argument is optional. If field is omitted, DCOUNT +counts all records in the database that match the criteria. This logic +has not yet been implemented in PhpSpreadsheet. + +#### DCOUNTA + +The DCOUNT function returns the count of cells that aren’t blank in a +column of a list or database and that match conditions that you specify. + +##### Syntax + + DCOUNTA(database, [field], criteria) + +##### Parameters + +**database** The range of cells that makes up the list or database. + +A database is a list of related data in which rows of related +information are records, and columns of data are fields. The first row +of the list contains labels for each column. + +**field** Indicates which column of the database is used in the +function. + +Enter the column label as a string (enclosed between double quotation +marks), such as "Age" or "Yield," or as a number (without quotation +marks) that represents the position of the column within the list: 1 for +the first column, 2 for the second column, and so on. + +**criteria** The range of cells that contains the conditions you +specify. + +You can use any range for the criteria argument, as long as it includes +at least one column label and at least one cell below the column label +in which you specify a condition for the column. + +##### Return Value + +**float** The count of the matching cells. + +##### Examples + +``` php +$database = [ + [ 'Tree', 'Height', 'Age', 'Yield', 'Profit' ], + [ 'Apple', 18, 20, 14, 105.00 ], + [ 'Pear', 12, 12, 10, 96.00 ], + [ 'Cherry', 13, 14, 9, 105.00 ], + [ 'Apple', 14, 15, 10, 75.00 ], + [ 'Pear', 9, 8, 8, 76.80 ], + [ 'Apple', 8, 9, 6, 45.00 ], +]; + +$criteria = [ + [ 'Tree', 'Height', 'Age', 'Yield', 'Profit', 'Height' ], + [ '="=Apple"', '>10', NULL, NULL, NULL, '<16' ], + [ '="=Pear"', NULL, NULL, NULL, NULL, NULL ], +]; + +$worksheet->fromArray( $criteria, NULL, 'A1' ) + ->fromArray( $database, NULL, 'A4' ); + +$worksheet->setCellValue('A12', '=DCOUNTA(A4:E10,"Yield",A1:A3)'); + +$retVal = $worksheet->getCell('A12')->getCalculatedValue(); + +// $retVal = 5 +``` + +##### Notes + +In MS Excel, The field argument is optional. If field is omitted, +DCOUNTA counts all records in the database that match the criteria. This +logic has not yet been implemented in PhpSpreadsheet. + +#### DGET + +The DGET function extracts a single value from a column of a list or +database that matches conditions that you specify. + +##### Syntax + + DGET(database, field, criteria) + +##### Parameters + +**database** The range of cells that makes up the list or database. + +A database is a list of related data in which rows of related +information are records, and columns of data are fields. The first row +of the list contains labels for each column. + +**field** Indicates which column of the database is used in the +function. + +Enter the column label as a string (enclosed between double quotation +marks), such as "Age" or "Yield," or as a number (without quotation +marks) that represents the position of the column within the list: 1 for +the first column, 2 for the second column, and so on. + +**criteria** The range of cells that contains the conditions you +specify. + +You can use any range for the criteria argument, as long as it includes +at least one column label and at least one cell below the column label +in which you specify a condition for the column. + +##### Return Value + +**mixed** The value from the selected column of the matching row. + +#### Examples + +``` php +$database = [ + [ 'Tree', 'Height', 'Age', 'Yield', 'Profit' ], + [ 'Apple', 18, 20, 14, 105.00 ], + [ 'Pear', 12, 12, 10, 96.00 ], + [ 'Cherry', 13, 14, 9, 105.00 ], + [ 'Apple', 14, 15, 10, 75.00 ], + [ 'Pear', 9, 8, 8, 76.80 ], + [ 'Apple', 8, 9, 6, 45.00 ], +]; + +$criteria = [ + [ 'Tree', 'Height', 'Age', 'Yield', 'Profit', 'Height' ], + [ '="=Apple"', '>10', NULL, NULL, NULL, '<16' ], + [ '="=Pear"', NULL, NULL, NULL, NULL, NULL ], +]; + +$worksheet->fromArray( $criteria, NULL, 'A1' ) + ->fromArray( $database, NULL, 'A4' ); + +$worksheet->setCellValue('A12', '=GET(A4:E10,"Age",A1:F2)'); + +$retVal = $worksheet->getCell('A12')->getCalculatedValue(); +// $retVal = 14 +``` + +##### Notes + +There are no additional notes on this function + +#### DMAX + +The DMAX function returns the largest number in a column of a list or +database that matches conditions you specify. + +##### Syntax + + DMAX(database, field, criteria) + +##### Parameters + +**database** The range of cells that makes up the list or database. + +A database is a list of related data in which rows of related +information are records, and columns of data are fields. The first row +of the list contains labels for each column. + +**field** Indicates which column of the database is used in the +function. + +Enter the column label as a string (enclosed between double quotation +marks), such as "Age" or "Yield," or as a number (without quotation +marks) that represents the position of the column within the list: 1 for +the first column, 2 for the second column, and so on. + +**criteria** The range of cells that contains the conditions you +specify. + +You can use any range for the criteria argument, as long as it includes +at least one column label and at least one cell below the column label +in which you specify a condition for the column. + +##### Return Value + +**float** The maximum value of the matching cells. + +##### Examples + +``` php +$database = [ + [ 'Tree', 'Height', 'Age', 'Yield', 'Profit' ], + [ 'Apple', 18, 20, 14, 105.00 ], + [ 'Pear', 12, 12, 10, 96.00 ], + [ 'Cherry', 13, 14, 9, 105.00 ], + [ 'Apple', 14, 15, 10, 75.00 ], + [ 'Pear', 9, 8, 8, 76.80 ], + [ 'Apple', 8, 9, 6, 45.00 ], +]; + +$criteria = [ + [ 'Tree', 'Height', 'Age', 'Yield', 'Profit', 'Height' ], + [ '="=Apple"', '>10', NULL, NULL, NULL, '<16' ], + [ '="=Pear"', NULL, NULL, NULL, NULL, NULL ], +]; + +$worksheet->fromArray( $criteria, NULL, 'A1' ) + ->fromArray( $database, NULL, 'A4' ); + +$worksheet->setCellValue('A12', '=DMAX(A4:E10,"Profit",A1:B2)'); + +$retVal = $worksheet->getCell('A12')->getCalculatedValue(); +// $retVal = 105 +``` + +##### Notes + +There are no additional notes on this function + +#### DMIN + +The DMIN function returns the smallest number in a column of a list or +database that matches conditions you specify. + +##### Syntax + + DMIN(database, field, criteria) + +##### Parameters + +**database** The range of cells that makes up the list or database. + +A database is a list of related data in which rows of related +information are records, and columns of data are fields. The first row +of the list contains labels for each column. + +**field** Indicates which column of the database is used in the +function. + +Enter the column label as a string (enclosed between double quotation +marks), such as "Age" or "Yield," or as a number (without quotation +marks) that represents the position of the column within the list: 1 for +the first column, 2 for the second column, and so on. + +**criteria** The range of cells that contains the conditions you +specify. + +You can use any range for the criteria argument, as long as it includes +at least one column label and at least one cell below the column label +in which you specify a condition for the column. + +##### Return Value + +**float** The minimum value of the matching cells. + +##### Examples + +``` php +$database = [ + [ 'Tree', 'Height', 'Age', 'Yield', 'Profit' ], + [ 'Apple', 18, 20, 14, 105.00 ], + [ 'Pear', 12, 12, 10, 96.00 ], + [ 'Cherry', 13, 14, 9, 105.00 ], + [ 'Apple', 14, 15, 10, 75.00 ], + [ 'Pear', 9, 8, 8, 76.80 ], + [ 'Apple', 8, 9, 6, 45.00 ], +]; + +$criteria = [ + [ 'Tree', 'Height', 'Age', 'Yield', 'Profit', 'Height' ], + [ '="=Apple"', '>10', NULL, NULL, NULL, '<16' ], + [ '="=Pear"', NULL, NULL, NULL, NULL, NULL ], +]; + +$worksheet->fromArray( $criteria, NULL, 'A1' ) + ->fromArray( $database, NULL, 'A4' ); + +$worksheet->setCellValue('A12', '=DMIN(A4:E10,"Yield",A1:A3)'); + +$retVal = $worksheet->getCell('A12')->getCalculatedValue(); +// $retVal = 6 +``` + +##### Notes + +There are no additional notes on this function + +#### DPRODUCT + +The DPRODUCT function multiplies the values in a column of a list or +database that match conditions that you specify. + +##### Syntax + + DPRODUCT(database, field, criteria) + +##### Parameters + +**database** The range of cells that makes up the list or database. + +A database is a list of related data in which rows of related +information are records, and columns of data are fields. The first row +of the list contains labels for each column. + +**field** Indicates which column of the database is used in the +function. + +Enter the column label as a string (enclosed between double quotation +marks), such as "Age" or "Yield," or as a number (without quotation +marks) that represents the position of the column within the list: 1 for +the first column, 2 for the second column, and so on. + +**criteria** The range of cells that contains the conditions you +specify. + +You can use any range for the criteria argument, as long as it includes +at least one column label and at least one cell below the column label +in which you specify a condition for the column. + +##### Return Value + +**float** The product of the matching cells. + +##### Examples + +``` php +$database = [ + [ 'Tree', 'Height', 'Age', 'Yield', 'Profit' ], + [ 'Apple', 18, 20, 14, 105.00 ], + [ 'Pear', 12, 12, 10, 96.00 ], + [ 'Cherry', 13, 14, 9, 105.00 ], + [ 'Apple', 14, 15, 10, 75.00 ], + [ 'Pear', 9, 8, 8, 76.80 ], + [ 'Apple', 8, 9, 6, 45.00 ], +]; + +$criteria = [ + [ 'Tree', 'Height', 'Age', 'Yield', 'Profit', 'Height' ], + [ '="=Apple"', '>10', NULL, NULL, NULL, '<16' ], + [ '="=Pear"', NULL, NULL, NULL, NULL, NULL ], +]; + +$worksheet->fromArray( $criteria, NULL, 'A1' ) + ->fromArray( $database, NULL, 'A4' ); + +$worksheet->setCellValue('A12', '=DPRODUCT(A4:E10,"Yield",A1:B2)'); + +$retVal = $worksheet->getCell('A12')->getCalculatedValue(); +// $retVal = 140 +``` + +##### Notes + +There are no additional notes on this function + +#### DSTDEV + +The DSTDEV function estimates the standard deviation of a population +based on a sample by using the numbers in a column of a list or database +that match conditions that you specify. + +##### Syntax + + DSTDEV(database, field, criteria) + +##### Parameters + +**database** The range of cells that makes up the list or database. + +A database is a list of related data in which rows of related +information are records, and columns of data are fields. The first row +of the list contains labels for each column. + +**field** Indicates which column of the database is used in the +function. + +Enter the column label as a string (enclosed between double quotation +marks), such as "Age" or "Yield," or as a number (without quotation +marks) that represents the position of the column within the list: 1 for +the first column, 2 for the second column, and so on. + +**criteria** The range of cells that contains the conditions you +specify. + +You can use any range for the criteria argument, as long as it includes +at least one column label and at least one cell below the column label +in which you specify a condition for the column. + +##### Return Value + +**float** The estimated standard deviation of the matching cells. + +##### Examples + +``` php +$database = [ + [ 'Tree', 'Height', 'Age', 'Yield', 'Profit' ], + [ 'Apple', 18, 20, 14, 105.00 ], + [ 'Pear', 12, 12, 10, 96.00 ], + [ 'Cherry', 13, 14, 9, 105.00 ], + [ 'Apple', 14, 15, 10, 75.00 ], + [ 'Pear', 9, 8, 8, 76.80 ], + [ 'Apple', 8, 9, 6, 45.00 ], +]; + +$criteria = [ + [ 'Tree', 'Height', 'Age', 'Yield', 'Profit', 'Height' ], + [ '="=Apple"', '>10', NULL, NULL, NULL, '<16' ], + [ '="=Pear"', NULL, NULL, NULL, NULL, NULL ], +]; + +$worksheet->fromArray( $criteria, NULL, 'A1' ) + ->fromArray( $database, NULL, 'A4' ); + +$worksheet->setCellValue('A12', '=DSTDEV(A4:E10,"Yield",A1:A3)'); + +$retVal = $worksheet->getCell('A12')->getCalculatedValue(); +// $retVal = 2.97 +``` + +##### Notes + +There are no additional notes on this function + +#### DSTDEVP + +The DSTDEVP function calculates the standard deviation of a population +based on the entire population by using the numbers in a column of a +list or database that match conditions that you specify. + +##### Syntax + + DSTDEVP(database, field, criteria) + +##### Parameters + +**database** The range of cells that makes up the list or database. + +A database is a list of related data in which rows of related +information are records, and columns of data are fields. The first row +of the list contains labels for each column. + +**field** Indicates which column of the database is used in the +function. + +Enter the column label as a string (enclosed between double quotation +marks), such as "Age" or "Yield," or as a number (without quotation +marks) that represents the position of the column within the list: 1 for +the first column, 2 for the second column, and so on. + +**criteria** The range of cells that contains the conditions you +specify. + +You can use any range for the criteria argument, as long as it includes +at least one column label and at least one cell below the column label +in which you specify a condition for the column. + +##### Return Value + +**float** The estimated standard deviation of the matching cells. + +##### Examples + +``` php +$database = [ + [ 'Tree', 'Height', 'Age', 'Yield', 'Profit' ], + [ 'Apple', 18, 20, 14, 105.00 ], + [ 'Pear', 12, 12, 10, 96.00 ], + [ 'Cherry', 13, 14, 9, 105.00 ], + [ 'Apple', 14, 15, 10, 75.00 ], + [ 'Pear', 9, 8, 8, 76.80 ], + [ 'Apple', 8, 9, 6, 45.00 ], +]; + +$criteria = [ + [ 'Tree', 'Height', 'Age', 'Yield', 'Profit', 'Height' ], + [ '="=Apple"', '>10', NULL, NULL, NULL, '<16' ], + [ '="=Pear"', NULL, NULL, NULL, NULL, NULL ], +]; + +$worksheet->fromArray( $criteria, NULL, 'A1' ) + ->fromArray( $database, NULL, 'A4' ); + +$worksheet->setCellValue('A12', '=DSTDEVP(A4:E10,"Yield",A1:A3)'); + +$retVal = $worksheet->getCell('A12')->getCalculatedValue(); +// $retVal = 2.65 +``` + +##### Notes + +There are no additional notes on this function + +#### DSUM + +The DSUM function adds the numbers in a column of a list or database +that matches conditions you specify. + +##### Syntax + + DSUM(database, field, criteria) + +##### Parameters + +**database** The range of cells that makes up the list or database. + +A database is a list of related data in which rows of related +information are records, and columns of data are fields. The first row +of the list contains labels for each column. + +**field** Indicates which column of the database is used in the +function. + +Enter the column label as a string (enclosed between double quotation +marks), such as "Age" or "Yield," or as a number (without quotation +marks) that represents the position of the column within the list: 1 for +the first column, 2 for the second column, and so on. + +**criteria** The range of cells that contains the conditions you +specify. + +You can use any range for the criteria argument, as long as it includes +at least one column label and at least one cell below the column label +in which you specify a condition for the column. + +##### Return Value + +**float** The total value of the matching cells. + +##### Examples + +``` php +$database = [ + [ 'Tree', 'Height', 'Age', 'Yield', 'Profit' ], + [ 'Apple', 18, 20, 14, 105.00 ], + [ 'Pear', 12, 12, 10, 96.00 ], + [ 'Cherry', 13, 14, 9, 105.00 ], + [ 'Apple', 14, 15, 10, 75.00 ], + [ 'Pear', 9, 8, 8, 76.80 ], + [ 'Apple', 8, 9, 6, 45.00 ], +]; + +$criteria = [ + [ 'Tree', 'Height', 'Age', 'Yield', 'Profit', 'Height' ], + [ '="=Apple"', '>10', NULL, NULL, NULL, '<16' ], + [ '="=Pear"', NULL, NULL, NULL, NULL, NULL ], +]; + +$worksheet->fromArray( $criteria, NULL, 'A1' ) + ->fromArray( $database, NULL, 'A4' ); + +$worksheet->setCellValue('A12', '=DMIN(A4:E10,"Profit",A1:A2)'); + +$retVal = $worksheet->getCell('A12')->getCalculatedValue(); +// $retVal = 225 +``` + +##### Notes + +There are no additional notes on this function + +#### DVAR + +Not yet documented. + +#### DVARP + +Not yet documented. + +### Date and Time Functions + +Excel provides a number of functions for the manipulation of dates and +times, and calculations based on date/time values. it is worth spending +some time reading the section titled "Date and Time Values" on passing +date parameters and returning date values to understand how +PhpSpreadsheet reconciles the differences between dates and times in +Excel and in PHP. + +#### DATE + +The DATE function returns an Excel timestamp or a PHP timestamp or `DateTime` +object representing the date that is referenced by the parameters. + +##### Syntax + + DATE(year, month, day) + +##### Parameters + +**year** The year number. + +If this value is between 0 (zero) and 1899 inclusive (for the Windows +1900 calendar), or between 4 and 1903 inclusive (for the Mac 1904), then +PhpSpreadsheet adds it to the Calendar base year, so a value of 108 will +interpret the year as 2008 when using the Windows 1900 calendar, or 2012 +when using the Mac 1904 calendar. + +**month** The month number. + +If this value is greater than 12, the DATE function adds that number of +months to the first month in the year specified. For example, +DATE(2008,14,2) returns a value representing February 2, 2009. + +If the value of **month** is less than 1, then that value will be +adjusted by -1, and that will then be subtracted from the first month of +the year specified. For example, DATE(2008,0,2) returns a value +representing December 2, 2007; while DATE(2008,-1,2) returns a value +representing November 2, 2007. + +**day** The day number. + +If this value is greater than the number of days in the month (and year) +specified, the DATE function adds that number of days to the first day +in the month. For example, DATE(2008,1,35) returns a value representing +February 4, 2008. + +If the value of **day** is less than 1, then that value will be adjusted +by -1, and that will then be subtracted from the first month of the year +specified. For example, DATE(2008,3,0) returns a value representing +February 29, 2008; while DATE(2008,3,-2) returns a value representing +February 27, 2008. + +##### Return Value + +**mixed** A date/time stamp that corresponds to the given date. + +This could be a PHP timestamp value (integer), a PHP `DateTime` object, +or an Excel timestamp value (real), depending on the value of +`\PhpOffice\PhpSpreadsheet\Calculation\Functions::getReturnDateType()`. + +##### Examples + +``` php +$worksheet->setCellValue('A1', 'Year') + ->setCellValue('A2', 'Month') + ->setCellValue('A3', 'Day'); + +$worksheet->setCellValue('B1', 2008) + ->setCellValue('B2', 12) + ->setCellValue('B3', 31); + +$worksheet->setCellValue('D1', '=DATE(B1,B2,B3)'); + +$retVal = $worksheet->getCell('D1')->getCalculatedValue(); +// $retVal = 1230681600 +``` + +``` php +// We're going to be calling the same cell calculation multiple times, +// and expecting different return values, so disable calculation cacheing +\PhpOffice\PhpSpreadsheet\Calculation\Calculation::getInstance()->setCalculationCacheEnabled(FALSE); + +$saveFormat = \PhpOffice\PhpSpreadsheet\Calculation\Functions::getReturnDateType(); + +\PhpOffice\PhpSpreadsheet\Calculation\Functions::setReturnDateType( + \PhpOffice\PhpSpreadsheet\Calculation\Functions::RETURNDATE_EXCEL +); + +$retVal = call_user_func_array( + ['\PhpOffice\PhpSpreadsheet\Calculation\Functions', 'DATE'], + [2008, 12, 31] +); +// $retVal = 39813.0 + +\PhpOffice\PhpSpreadsheet\Calculation\Functions::setReturnDateType( + \PhpOffice\PhpSpreadsheet\Calculation\Functions::RETURNDATE_PHP_NUMERIC +); + +$retVal = call_user_func_array( + ['\PhpOffice\PhpSpreadsheet\Calculation\Functions', 'DATE'], + [2008, 12, 31] +); +// $retVal = 1230681600 + +\PhpOffice\PhpSpreadsheet\Calculation\Functions::setReturnDateType($saveFormat); +``` + +##### Notes + +There are no additional notes on this function + +#### DATEDIF + +The DATEDIF function computes the difference between two dates in a +variety of different intervals, such number of years, months, or days. + +##### Syntax + + DATEDIF(date1, date2 [, unit]) + +##### Parameters + +**date1** First Date. + +An Excel date value, PHP date timestamp, PHP `DateTime` object, or a date +represented as a string. + +**date2** Second Date. + +An Excel date value, PHP date timestamp, PHP `DateTime` object, or a date +represented as a string. + +**unit** The interval type to use for the calculation + +This is a string, comprising one of the values listed below: + +Unit | Meaning | Description +-----|---------------------------------|-------------------------------- +m | Months | Complete calendar months between the dates. +d | Days | Number of days between the dates. +y | Years | Complete calendar years between the dates. +ym | Months Excluding Years | Complete calendar months between the dates as if they were of the same year. +yd | Days Excluding Years | Complete calendar days between the dates as if they were of the same year. +md | Days Excluding Years And Months | Complete calendar days between the dates as if they were of the same month and same year. + +The unit value is not case sensitive, and defaults to `d`. + +##### Return Value + +**integer** An integer value that reflects the difference between the +two dates. + +This could be the number of full days, months or years between the two +dates, depending on the interval unit value passed into the function as +the third parameter. + +##### Examples + +``` php +$worksheet->setCellValue('A1', 'Year') + ->setCellValue('A2', 'Month') + ->setCellValue('A3', 'Day'); + +$worksheet->setCellValue('B1', 2001) + ->setCellValue('C1', 2009) + ->setCellValue('B2', 7) + ->setCellValue('C2', 12) + ->setCellValue('B3', 1) + ->setCellValue('C3', 31); + +$worksheet->setCellValue('D1', '=DATEDIF(DATE(B1,B2,B3),DATE(C1,C2,C3),"d")') + ->setCellValue('D2', '=DATEDIF(DATE(B1,B2,B3),DATE(C1,C2,C3),"m")') + ->setCellValue('D3', '=DATEDIF(DATE(B1,B2,B3),DATE(C1,C2,C3),"y")') + ->setCellValue('D4', '=DATEDIF(DATE(B1,B2,B3),DATE(C1,C2,C3),"ym")') + ->setCellValue('D5', '=DATEDIF(DATE(B1,B2,B3),DATE(C1,C2,C3),"yd")') + ->setCellValue('D6', '=DATEDIF(DATE(B1,B2,B3),DATE(C1,C2,C3),"md")'); + +$retVal = $worksheet->getCell('D1')->getCalculatedValue(); +// $retVal = 3105 + +$retVal = $worksheet->getCell('D2')->getCalculatedValue(); +// $retVal = 101 + +$retVal = $worksheet->getCell('D3')->getCalculatedValue(); +// $retVal = 8 + +$retVal = $worksheet->getCell('D4')->getCalculatedValue(); +// $retVal = 5 + +$retVal = $worksheet->getCell('D5')->getCalculatedValue(); +// $retVal = 183 + +$retVal = $worksheet->getCell('D6')->getCalculatedValue(); +// $retVal = 30 +``` + +``` php +$date1 = 1193317015; // PHP timestamp for 25-Oct-2007 +$date2 = 1449579415; // PHP timestamp for 8-Dec-2015 + +$retVal = call_user_func_array( + ['\PhpOffice\PhpSpreadsheet\Calculation\Functions', 'DATEDIF'], + [$date1, $date2, 'd'] +); +// $retVal = 2966 + +$retVal = call_user_func_array( + ['\PhpOffice\PhpSpreadsheet\Calculation\Functions', 'DATEDIF'], + [$date1, $date2, 'm'] +); +// $retVal = 97 + +$retVal = call_user_func_array( + ['\PhpOffice\PhpSpreadsheet\Calculation\Functions', 'DATEDIF'], + [$date1, $date2, 'y'] +); +// $retVal = 8 + +$retVal = call_user_func_array( + ['\PhpOffice\PhpSpreadsheet\Calculation\Functions', 'DATEDIF'], + [$date1, $date2, 'ym'] +); +// $retVal = 1 + +$retVal = call_user_func_array( + ['\PhpOffice\PhpSpreadsheet\Calculation\Functions', 'DATEDIF'], + [$date1, $date2, 'yd'] +); +// $retVal = 44 + +$retVal = call_user_func_array( + ['\PhpOffice\PhpSpreadsheet\Calculation\Functions', 'DATEDIF'], + [$date1, $date2, 'md'] +); +// $retVal = 13 +``` + +##### Notes + +If Date1 is later than Date2, DATEDIF will return a \#NUM! error. + +#### DATEVALUE + +The DATEVALUE function returns the date represented by a date formatted +as a text string. Use DATEVALUE to convert a date represented by text to +a serial number. + +##### Syntax + + DATEVALUE(dateString) + +##### Parameters + +**date** Date String. + +A string, representing a date value. + +##### Return Value + +**mixed** A date/time stamp that corresponds to the given date. + +This could be a PHP timestamp value (integer), a PHP `DateTime` object, +or an Excel timestamp value (real), depending on the value of +`\PhpOffice\PhpSpreadsheet\Calculation\Functions::getReturnDateType()`. + +##### Examples + +``` php +$worksheet->setCellValue('A1', 'Date String'); + ->setCellValue('A2', '31-Dec-2008') + ->setCellValue('A3', '31/12/2008') + ->setCellValue('A4', '12-31-2008'); + +$worksheet->setCellValue('B2', '=DATEVALUE(A2)') + ->setCellValue('B3', '=DATEVALUE(A3)') + ->setCellValue('B4', '=DATEVALUE(A4)'); + +\PhpOffice\PhpSpreadsheet\Calculation\Functions::setReturnDateType( + \PhpOffice\PhpSpreadsheet\Calculation\Functions::RETURNDATE_EXCEL +); + +$retVal = $worksheet->getCell('B2')->getCalculatedValue(); + +$retVal = $worksheet->getCell('B3')->getCalculatedValue(); + +$retVal = $worksheet->getCell('B4')->getCalculatedValue(); +// $retVal = 39813.0 for all cases +``` + +``` php +// We're going to be calling the same cell calculation multiple times, +// and expecting different return values, so disable calculation cacheing +\PhpOffice\PhpSpreadsheet\Calculation\Calculation::getInstance()->setCalculationCacheEnabled(FALSE); + +$saveFormat = \PhpOffice\PhpSpreadsheet\Calculation\Functions::getReturnDateType(); + +\PhpOffice\PhpSpreadsheet\Calculation\Functions::setReturnDateType( + \PhpOffice\PhpSpreadsheet\Calculation\Functions::RETURNDATE_EXCEL +); + +$retVal = call_user_func_array( + ['\PhpOffice\PhpSpreadsheet\Calculation\Functions', 'DATEVALUE'], + ['31-Dec-2008'] +); +// $retVal = 39813.0 + +\PhpOffice\PhpSpreadsheet\Calculation\Functions::setReturnDateType( + \PhpOffice\PhpSpreadsheet\Calculation\Functions::RETURNDATE_PHP_NUMERIC +); + +$retVal = call_user_func_array( + ['\PhpOffice\PhpSpreadsheet\Calculation\Functions', 'DATEVALUE'], + ['31-Dec-2008'] +); +// $retVal = 1230681600 + +\PhpOffice\PhpSpreadsheet\Calculation\Functions::setReturnDateType($saveFormat); +``` + +##### Notes + +DATEVALUE uses the php `DateTime` object implementation of `strtotime()` +(which can handle a wider range of formats than the normal `strtotime()` +function), and it is also called for any date parameter passed to other +date functions (such as DATEDIF) when the parameter value is a string. + +**WARNING:-** PhpSpreadsheet accepts a wider range of date formats than +MS Excel, so it is entirely possible that Excel will return a \#VALUE! +error when passed a date string that it can’t interpret, while +PhpSpreadsheet is able to translate that same string into a correct date +value. + +Care should be taken in workbooks that use string formatted dates in +calculations when writing to Xls or Xlsx. + +#### DAY + +The DAY function returns the day of a date. The day is given as an +integer ranging from 1 to 31. + +##### Syntax + + DAY(datetime) + +##### Parameters + +**datetime** Date. + +An Excel date value, PHP date timestamp, PHP `DateTime` object, or a date +represented as a string. + +##### Return Value + +**integer** An integer value that reflects the day of the month. + +This is an integer ranging from 1 to 31. + +##### Examples + +``` php +$worksheet->setCellValue('A1', 'Date String') + ->setCellValue('A2', '31-Dec-2008') + ->setCellValue('A3', '14-Feb-2008'); + +$worksheet->setCellValue('B2', '=DAY(A2)') + ->setCellValue('B3', '=DAY(A3)'); + +$retVal = $worksheet->getCell('B2')->getCalculatedValue(); +// $retVal = 31 + +$retVal = $worksheet->getCell('B3')->getCalculatedValue(); +// $retVal = 14 +``` + +``` php +$retVal = call_user_func_array( + ['\PhpOffice\PhpSpreadsheet\Calculation\Functions', 'DAYOFMONTH'], + ['25-Dec-2008'] +); +// $retVal = 25 +``` + +##### Notes + +Note that the PhpSpreadsheet function is +`\PhpOffice\PhpSpreadsheet\Calculation\Functions::DAYOFMONTH()` when the +method is called statically. + +#### DAYS360 + +The DAYS360 function computes the difference between two dates based on +a 360 day year (12 equal periods of 30 days each) used by some +accounting systems. + +##### Syntax + + DAYS360(date1, date2 [, method]) + +#### Parameters + +**date1** First Date. + +An Excel date value, PHP date timestamp, PHP `DateTime` object, or a date +represented as a string. + +**date2** Second Date. + +An Excel date value, PHP date timestamp, PHP `DateTime` object, or a date +represented as a string. + +**method** A boolean flag (TRUE or FALSE) + +This is a flag that determines which method to use in the calculation, +based on the values listed below: + +method | Description +-------|------------ +FALSE | U.S. (NASD) method. If the starting date is the last day of a month, it becomes equal to the 30th of the same month. If the ending date is the last day of a month and the starting date is earlier than the 30th of a month, the ending date becomes equal to the 1st of the next month; otherwise the ending date becomes equal to the 30th of the same month. +TRUE | European method. Starting dates and ending dates that occur on the 31st of a month become equal to the 30th of the same month. + +The method value defaults to FALSE. + +##### Return Value + +**integer** An integer value that reflects the difference between the +two dates. + +This is the number of full days between the two dates, based on a 360 +day year. + +##### Examples + +``` php +$worksheet->setCellValue('B1', 'Start Date') + ->setCellValue('C1', 'End Date') + ->setCellValue('A2', 'Year') + ->setCellValue('A3', 'Month') + ->setCellValue('A4', 'Day'); + +$worksheet->setCellValue('B2', 2003) + ->setCellValue('B3', 2) + ->setCellValue('B4', 3); + +$worksheet->setCellValue('C2', 2007) + ->setCellValue('C3', 5) + ->setCellValue('C4', 31); + +$worksheet->setCellValue('E2', '=DAYS360(DATE(B2,B3,B4),DATE(C2,C3,C4))') + ->setCellValue('E4', '=DAYS360(DATE(B2,B3,B4),DATE(C2,C3,C4),FALSE)'); + +$retVal = $worksheet->getCell('E2')->getCalculatedValue(); +// $retVal = 1558 + +$retVal = $worksheet->getCell('E4')->getCalculatedValue(); +// $retVal = 1557 +``` + +``` php +$date1 = 37655.0; // Excel timestamp for 25-Oct-2007 +$date2 = 39233.0; // Excel timestamp for 8-Dec-2015 + +$retVal = call_user_func_array( + ['\PhpOffice\PhpSpreadsheet\Calculation\Functions', 'DAYS360'], + [$date1, $date2] +); +// $retVal = 1558 + +$retVal = call_user_func_array( + ['\PhpOffice\PhpSpreadsheet\Calculation\Functions', 'DAYS360'], + [$date1, $date2, TRUE] +); +// $retVal = 1557 +``` + +##### Notes + +**WARNING:-** This function does not currently work with the Xls Writer +when a PHP Boolean is used for the third (optional) parameter (as shown +in the example above), and the writer will generate and error. It will +work if a numeric 0 or 1 is used for the method parameter; or if the +Excel `TRUE()` and `FALSE()` functions are used instead. + +#### EDATE + +The EDATE function returns an Excel timestamp or a PHP timestamp or `DateTime` +object representing the date that is the indicated number of months +before or after a specified date (the start\_date). Use EDATE to +calculate maturity dates or due dates that fall on the same day of the +month as the date of issue. + +##### Syntax + + EDATE(baseDate, months) + +##### Parameters + +**baseDate** Start Date. + +An Excel date value, PHP date timestamp, PHP `DateTime` object, or a date +represented as a string. + +**months** Number of months to add. + +An integer value indicating the number of months before or after +baseDate. A positive value for months yields a future date; a negative +value yields a past date. + +##### Return Value + +**mixed** A date/time stamp that corresponds to the basedate + months. + +This could be a PHP timestamp value (integer), a PHP `DateTime` object, +or an Excel timestamp value (real), depending on the value of +`\PhpOffice\PhpSpreadsheet\Calculation\Functions::getReturnDateType()`. + +##### Examples + +``` php +$worksheet->setCellValue('A1', 'Date String') + ->setCellValue('A2', '1-Jan-2008') + ->setCellValue('A3', '29-Feb-2008'); + +$worksheet->setCellValue('B2', '=EDATE(A2,5)') + ->setCellValue('B3', '=EDATE(A3,-12)'); + +\PhpOffice\PhpSpreadsheet\Calculation\Functions::setReturnDateType( + \PhpOffice\PhpSpreadsheet\Calculation\Functions::RETURNDATE_EXCEL +); + +$retVal = $worksheet->getCell('B2')->getCalculatedValue(); +// $retVal = 39600.0 (1-Jun-2008) + +$retVal = $worksheet->getCell('B3')->getCalculatedValue(); +// $retVal = 39141.0 (28-Feb-2007) +``` + +``` php +\PhpOffice\PhpSpreadsheet\Calculation\Functions::setReturnDateType( + \PhpOffice\PhpSpreadsheet\Calculation\Functions::RETURNDATE_EXCEL +); + +$retVal = call_user_func_array( + ['\PhpOffice\PhpSpreadsheet\Calculation\Functions', 'EDATE'], + ['31-Oct-2008', 25] +); +// $retVal = 40512.0 (30-Nov-2010) +``` + +###### Notes + +**WARNING:-** This function is currently not supported by the Xls Writer +because it is not a standard function within Excel 5, but an add-in from +the Analysis ToolPak. + +#### EOMONTH + +The EOMONTH function returns an Excel timestamp or a PHP timestamp or +`DateTime` object representing the date of the last day of the month that is +the indicated number of months before or after a specified date (the +start\_date). Use EOMONTH to calculate maturity dates or due dates that +fall on the last day of the month. + +##### Syntax + + EOMONTH(baseDate, months) + +##### Parameters + +**baseDate** Start Date. + +An Excel date value, PHP date timestamp, PHP `DateTime` object, or a date +represented as a string. + +**months** Number of months to add. + +An integer value indicating the number of months before or after +baseDate. A positive value for months yields a future date; a negative +value yields a past date. + +##### Return Value + +**mixed** A date/time stamp that corresponds to the last day of basedate ++ months. + +This could be a PHP timestamp value (integer), a PHP `DateTime` object, +or an Excel timestamp value (real), depending on the value of +`\PhpOffice\PhpSpreadsheet\Calculation\Functions::getReturnDateType()`. + +##### Examples + +``` php +$worksheet->setCellValue('A1', 'Date String') + ->setCellValue('A2', '1-Jan-2000') + ->setCellValue('A3', '14-Feb-2009'); + +$worksheet->setCellValue('B2', '=EOMONTH(A2,5)') + ->setCellValue('B3', '=EOMONTH(A3,-12)'); + +\PhpOffice\PhpSpreadsheet\Calculation\Functions::setReturnDateType(\PhpOffice\PhpSpreadsheet\Calculation\Functions::RETURNDATE_EXCEL); + +$retVal = $worksheet->getCell('B2')->getCalculatedValue(); +// $retVal = 39629.0 (30-Jun-2008) + +$retVal = $worksheet->getCell('B3')->getCalculatedValue(); +// $retVal = 39507.0 (29-Feb-2008) +``` + +``` php +\PhpOffice\PhpSpreadsheet\Calculation\Functions::setReturnDateType( + \PhpOffice\PhpSpreadsheet\Calculation\Functions::RETURNDATE_EXCEL +); + +$retVal = call_user_func_array( + ['\PhpOffice\PhpSpreadsheet\Calculation\Functions', 'EOMONTH'], + ['31-Oct-2008', 13] +); +// $retVal = 40147.0 (30-Nov-2010) +``` + +##### Notes + +**WARNING:-** This function is currently not supported by the Xls Writer +because it is not a standard function within Excel 5, but an add-in from +the Analysis ToolPak. + +#### HOUR + +The HOUR function returns the hour of a time value. The hour is given as +an integer, ranging from 0 (12:00 A.M.) to 23 (11:00 P.M.). + +##### Syntax + + HOUR(datetime) + +##### Parameters + +**datetime** Time. + +An Excel date/time value, PHP date timestamp, PHP `DateTime` object, or a +date/time represented as a string. + +##### Return Value + +**integer** An integer value that reflects the hour of the day. + +This is an integer ranging from 0 to 23. + +##### Examples + +``` php +$worksheet->setCellValue('A1', 'Time String') + ->setCellValue('A2', '31-Dec-2008 17:30') + ->setCellValue('A3', '14-Feb-2008 4:20 AM') + ->setCellValue('A4', '14-Feb-2008 4:20 PM'); + +$worksheet->setCellValue('B2', '=HOUR(A2)') + ->setCellValue('B3', '=HOUR(A3)') + ->setCellValue('B4', '=HOUR(A4)'); + +$retVal = $worksheet->getCell('B2')->getCalculatedValue(); +// $retVal = 17 + +$retVal = $worksheet->getCell('B3')->getCalculatedValue(); +// $retVal = 4 + +$retVal = $worksheet->getCell('B4')->getCalculatedValue(); +// $retVal = 16 +``` + +``` php +$retVal = call_user_func_array( + ['\PhpOffice\PhpSpreadsheet\Calculation\Functions', 'HOUROFDAY'], + ['09:30'] +); +// $retVal = 9 +``` + +##### Notes + +Note that the PhpSpreadsheet function is +`\PhpOffice\PhpSpreadsheet\Calculation\Functions::HOUROFDAY()` when the +method is called statically. + +#### MINUTE + +The MINUTE function returns the minutes of a time value. The minute is +given as an integer, ranging from 0 to 59. + +##### Syntax + + MINUTE(datetime) + +##### Parameters + +**datetime** Time. + +An Excel date/time value, PHP date timestamp, PHP `DateTime` object, or a +date/time represented as a string. + +##### Return Value + +**integer** An integer value that reflects the minutes within the hour. + +This is an integer ranging from 0 to 59. + +##### Examples + +``` php +$worksheet->setCellValue('A1', 'Time String') + ->setCellValue('A2', '31-Dec-2008 17:30') + ->setCellValue('A3', '14-Feb-2008 4:20 AM') + ->setCellValue('A4', '14-Feb-2008 4:45 PM'); + +$worksheet->setCellValue('B2', '=MINUTE(A2)') + ->setCellValue('B3', '=MINUTE(A3)') + ->setCellValue('B4', '=MINUTE(A4)'); + +$retVal = $worksheet->getCell('B2')->getCalculatedValue(); +// $retVal = 30 + +$retVal = $worksheet->getCell('B3')->getCalculatedValue(); +// $retVal = 20 + +$retVal = $worksheet->getCell('B4')->getCalculatedValue(); +// $retVal = 45 +``` + +``` php +$retVal = call_user_func_array( + ['\PhpOffice\PhpSpreadsheet\Calculation\Functions', 'MINUTE'], + ['09:30'] +); +// $retVal = 30 +``` + +##### Notes + +Note that the PhpSpreadsheet function is +`\PhpOffice\PhpSpreadsheet\Calculation\Functions::MINUTE()` when the +method is called statically. + +#### MONTH + +The MONTH function returns the month of a date. The month is given as an +integer ranging from 1 to 12. + +##### Syntax + + MONTH(datetime) + +##### Parameters + +**datetime** Date. + +An Excel date value, PHP date timestamp, PHP `DateTime` object, or a date +represented as a string. + +##### Return Value + +**integer** An integer value that reflects the month of the year. + +This is an integer ranging from 1 to 12. + +##### Examples + +``` php +$worksheet->setCellValue('A1', 'Date String'); +$worksheet->setCellValue('A2', '31-Dec-2008'); +$worksheet->setCellValue('A3', '14-Feb-2008'); + +$worksheet->setCellValue('B2', '=MONTH(A2)'); +$worksheet->setCellValue('B3', '=MONTH(A3)'); + +$retVal = $worksheet->getCell('B2')->getCalculatedValue(); +// $retVal = 12 + +$retVal = $worksheet->getCell('B3')->getCalculatedValue(); +// $retVal = 2 +``` + +``` php +$retVal = call_user_func_array( + ['\PhpOffice\PhpSpreadsheet\Calculation\Functions', 'MONTHOFYEAR'], + ['14-July-2008'] +); +// $retVal = 7 +``` + +#### Notes + +Note that the PhpSpreadsheet function is +`\PhpOffice\PhpSpreadsheet\Calculation\Functions::MONTHOFYEAR()` when the +method is called statically. + +#### NETWORKDAYS + +The NETWORKDAYS function returns the number of whole working days +between a *start date* and an *end date*. Working days exclude weekends +and any dates identified in *holidays*. Use NETWORKDAYS to calculate +employee benefits that accrue based on the number of days worked during +a specific term. + +##### Syntax + + NETWORKDAYS(startDate, endDate [, holidays]) + +##### Parameters + +**startDate** Start Date of the period. + +An Excel date value, PHP date timestamp, PHP `DateTime` object, or a date +represented as a string. + +**endDate** End Date of the period. + +An Excel date value, PHP date timestamp, PHP `DateTime` object, or a date +represented as a string. + +**holidays** Optional array of Holiday dates. + +An optional range of one or more dates to exclude from the working +calendar, such as state and federal holidays and floating holidays. + +The list can be either a range of cells that contains the dates or an +array constant of Excel date values, PHP date timestamps, PHP date +objects, or dates represented as strings. + +##### Return Value + +**integer** Number of working days. + +The number of working days between startDate and endDate. + +##### Examples + +``` php +``` + +``` php +``` + +##### Notes + +There are no additional notes on this function + +#### NOW + +The NOW function returns the current date and time. + +##### Syntax + + NOW() + +##### Parameters + +There are no parameters for the `NOW()` function. + +##### Return Value + +**mixed** A date/time stamp that corresponds to the current date and +time. + +This could be a PHP timestamp value (integer), a PHP `DateTime` object, +or an Excel timestamp value (real), depending on the value of +`\PhpOffice\PhpSpreadsheet\Calculation\Functions::getReturnDateType()`. + +##### Examples + +``` php +``` + +``` php +``` + +##### Notes + +Note that the PhpSpreadsheet function is +`\PhpOffice\PhpSpreadsheet\Calculation\Functions::DATETIMENOW()` when the +method is called statically. + +#### SECOND + +The SECOND function returns the seconds of a time value. The second is +given as an integer, ranging from 0 to 59. + +##### Syntax + + SECOND(datetime) + +##### Parameters + +**datetime** Time. + +An Excel date/time value, PHP date timestamp, PHP `DateTime` object, or a +date/time represented as a string. + +##### Return Value + +**integer** An integer value that reflects the seconds within the +minute. + +This is an integer ranging from 0 to 59. + +##### Examples + +``` php +$worksheet->setCellValue('A1', 'Time String') + ->setCellValue('A2', '31-Dec-2008 17:30:20') + ->setCellValue('A3', '14-Feb-2008 4:20 AM') + ->setCellValue('A4', '14-Feb-2008 4:45:59 PM'); + +$worksheet->setCellValue('B2', '=SECOND(A2)') + ->setCellValue('B3', '=SECOND(A3)'); + ->setCellValue('B4', '=SECOND(A4)'); + +$retVal = $worksheet->getCell('B2')->getCalculatedValue(); +// $retVal = 20 + +$retVal = $worksheet->getCell('B3')->getCalculatedValue(); +// $retVal = 0 + +$retVal = $worksheet->getCell('B4')->getCalculatedValue(); +// $retVal = 59 +``` + +``` php +$retVal = call_user_func_array( + ['\PhpOffice\PhpSpreadsheet\Calculation\Functions', 'SECOND'], + ['09:30:17'] +); +// $retVal = 17 +``` + +##### Notes + +Note that the PhpSpreadsheet function is +`\PhpOffice\PhpSpreadsheet\Calculation\Functions::SECOND()` when the +method is called statically. + +#### TIME + +Not yet documented. + +#### TIMEVALUE + +Not yet documented. + +#### TODAY + +Not yet documented. + +#### WEEKDAY + +The WEEKDAY function returns the day of the week for a given date. The +day is given as an integer ranging from 1 to 7, although this can be +modified to return a value between 0 and 6. + +##### Syntax + + WEEKDAY(datetime [, method]) + +##### Parameters + +**datetime** Date. + +An Excel date value, PHP date timestamp, PHP `DateTime` object, or a date +represented as a string. + +**method** An integer flag (values 0, 1 or 2) + +This is a flag that determines which method to use in the calculation, +based on the values listed below: + + method | Description + :-----:|------------------------------------------ + 0 | Returns 1 (Sunday) through 7 (Saturday). + 1 | Returns 1 (Monday) through 7 (Sunday). + 2 | Returns 0 (Monday) through 6 (Sunday). + +The method value defaults to 1. + +##### Return Value + +**integer** An integer value that reflects the day of the week. + +This is an integer ranging from 1 to 7, or 0 to 6, depending on the +value of method. + +##### Examples + +``` php +$worksheet->setCellValue('A1', 'Date String') + ->setCellValue('A2', '31-Dec-2008') + ->setCellValue('A3', '14-Feb-2008'); + +$worksheet->setCellValue('B2', '=WEEKDAY(A2)') + ->setCellValue('B3', '=WEEKDAY(A3,0)') + ->setCellValue('B4', '=WEEKDAY(A3,2)'); + +$retVal = $worksheet->getCell('B2')->getCalculatedValue(); +// $retVal = 12 + +$retVal = $worksheet->getCell('B3')->getCalculatedValue(); +// $retVal = 2 + +$retVal = $worksheet->getCell('B4')->getCalculatedValue(); +// $retVal = 2 +``` + +``` php +$retVal = call_user_func_array( + ['\PhpOffice\PhpSpreadsheet\Calculation\Functions', 'WEEKDAY'], + ['14-July-2008'] +); +// $retVal = 7 +``` + +##### Notes + +Note that the PhpSpreadsheet function is +`\PhpOffice\PhpSpreadsheet\Calculation\Functions::WEEKDAY()` when the +method is called statically. + +#### WEEKNUM + +Not yet documented. + +#### WORKDAY + +Not yet documented. + +#### YEAR + +The YEAR function returns the year of a date. + +##### Syntax + + YEAR(datetime) + +##### Parameters + +**datetime** Date. + +An Excel date value, PHP date timestamp, PHP `DateTime` object, or a date +represented as a string. + +##### Return Value + +**integer** An integer value that reflects the month of the year. + +This is an integer year value. + +##### Examples + +``` php +$worksheet->setCellValue('A1', 'Date String') + ->setCellValue('A2', '17-Jul-1982') + ->setCellValue('A3', '16-Apr-2009'); + +$worksheet->setCellValue('B2', '=YEAR(A2)') + ->setCellValue('B3', '=YEAR(A3)'); + +$retVal = $worksheet->getCell('B2')->getCalculatedValue(); +// $retVal = 1982 + +$retVal = $worksheet->getCell('B3')->getCalculatedValue(); +// $retVal = 2009 +``` + +``` php +$retVal = call_user_func_array( + ['\PhpOffice\PhpSpreadsheet\Calculation\Functions', 'YEAR'], + ['14-July-2001'] +); +// $retVal = 2001 +``` + +##### Notes + +There are no additional notes on this function + +### YEARFRAC + +Not yet documented. diff --git a/vendor/phpoffice/phpspreadsheet/docs/topics/creating-spreadsheet.md b/vendor/phpoffice/phpspreadsheet/docs/topics/creating-spreadsheet.md new file mode 100644 index 0000000..dceafe4 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/docs/topics/creating-spreadsheet.md @@ -0,0 +1,59 @@ +# Creating a spreadsheet + +## The `Spreadsheet` class + +The `Spreadsheet` class is the core of PhpSpreadsheet. It contains +references to the contained worksheets, document security settings and +document meta data. + +To simplify the PhpSpreadsheet concept: the `Spreadsheet` class +represents your workbook. + +Typically, you will create a workbook in one of two ways, either by +loading it from a spreadsheet file, or creating it manually. A third +option, though less commonly used, is cloning an existing workbook that +has been created using one of the previous two methods. + +### Loading a Workbook from a file + +Details of the different spreadsheet formats supported, and the options +available to read them into a Spreadsheet object are described fully in +the [Reading Files](./reading-files.md) document. + +``` php +$inputFileName = './sampleData/example1.xls'; + +/** Load $inputFileName to a Spreadsheet object **/ +$spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($inputFileName); +``` + +### Creating a new workbook + +If you want to create a new workbook, rather than load one from file, +then you simply need to instantiate it as a new Spreadsheet object. + +``` php +/** Create a new Spreadsheet Object **/ +$spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet(); +``` + +A new workbook will always be created with a single worksheet. + +## Clearing a Workbook from memory + +The PhpSpreadsheet object contains cyclic references (e.g. the workbook +is linked to the worksheets, and the worksheets are linked to their +parent workbook) which cause problems when PHP tries to clear the +objects from memory when they are `unset()`, or at the end of a function +when they are in local scope. The result of this is "memory leaks", +which can easily use a large amount of PHP's limited memory. + +This can only be resolved manually: if you need to unset a workbook, +then you also need to "break" these cyclic references before doing so. +PhpSpreadsheet provides the `disconnectWorksheets()` method for this +purpose. + +``` php +$spreadsheet->disconnectWorksheets(); +unset($spreadsheet); +``` diff --git a/vendor/phpoffice/phpspreadsheet/docs/topics/file-formats.md b/vendor/phpoffice/phpspreadsheet/docs/topics/file-formats.md new file mode 100644 index 0000000..7f4e6b7 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/docs/topics/file-formats.md @@ -0,0 +1,121 @@ +# File Formats + +PhpSpreadsheet can read a number of different spreadsheet and file +formats, although not all features are supported by all of the readers. +Check the [features cross +reference](../references/features-cross-reference.md) for a list that +identifies which features are supported by which readers. + +Currently, PhpSpreadsheet supports the following File Types for Reading: + +### Xls + +The Microsoft Excel™ Binary file format (BIFF5 and BIFF8) is a binary +file format that was used by Microsoft Excel™ between versions 95 and 2003. +The format is supported (to various extents) by most spreadsheet +programs. BIFF files normally have an extension of .xls. Documentation +describing the format can be [read online](https://msdn.microsoft.com/en-us/library/cc313154(v=office.12).aspx) +or [downloaded as PDF](https://download.microsoft.com/download/2/4/8/24862317-78F0-4C4B-B355-C7B2C1D997DB/%5BMS-XLS%5D.pdf). + +### Xml + +Microsoft Excel™ 2003 included options for a file format called +SpreadsheetML. This file is a zipped XML document. It is not very +common, but its core features are supported. Documentation for the +format can be [read online](https://msdn.microsoft.com/en-us/library/aa140066(office.10).aspx) +though it’s sadly rather sparse in its detail. + +### Xlsx + +Microsoft Excel™ 2007 shipped with a new file format, namely Microsoft +Office Open XML SpreadsheetML, and Excel 2010 extended this still +further with its new features such as sparklines. These files typically +have an extension of .xlsx. This format is based around a zipped +collection of eXtensible Markup Language (XML) files. Microsoft Office +Open XML SpreadsheetML is mostly standardized in [ECMA 376](https://www.ecma-international.org/news/TC45_current_work/TC45_available_docs.htm) +and ISO 29500. + +### Ods + +aka Open Document Format (ODF) or OASIS, this is the OpenOffice.org XML +file format for spreadsheets. It comprises a zip archive including +several components all of which are text files, most of these with +markup in the eXtensible Markup Language (XML). It is the standard file +format for OpenOffice.org Calc and StarCalc, and files typically have an +extension of .ods. The published specification for the file format is +available from [the OASIS Open Office XML Format Technical Committee web +page](https://www.oasis-open.org/committees/tc_home.php?wg_abbrev=office). +Other information is available from [the OpenOffice.org XML File Format +web page](https://www.openoffice.org/xml/), part of the +OpenOffice.org project. + +### Slk + +This is the Microsoft Multiplan Symbolic Link Interchange (SYLK) file +format. Multiplan was a predecessor to Microsoft Excel™. Files normally +have an extension of .slk. While not common, there are still a few +applications that generate SYLK files as a cross-platform option, +because (despite being limited to a single worksheet) it is a simple +format to implement, and supports some basic data and cell formatting +options (unlike CSV files). + +### Gnumeric + +The [Gnumeric file format](https://help.gnome.org/users/gnumeric/stable/sect-file-formats.html.en#file-format-gnumeric) +is used by the Gnome Gnumeric spreadsheet +application, and typically files have an extension of `.gnumeric`. The +file contents are stored using eXtensible Markup Language (XML) markup, +and the file is then compressed using the GNU project's gzip compression +library. + +### Csv + +Comma Separated Value (CSV) file format is a common structuring strategy +for text format files. In CSV flies, each line in the file represents a +row of data and (within each line of the file) the different data fields +(or columns) are separated from one another using a comma (`,`). If a +data field contains a comma, then it should be enclosed (typically in +quotation marks (`"`). Sometimes tabs `\t`, or the pipe symbol (`|`), or a +semi-colon (`;`) are used as separators instead of a comma, although +other symbols can be used. Because CSV is a text-only format, it doesn't +support any data formatting options. + +"CSV" is not a single, well-defined format (although see RFC 4180 for +one definition that is commonly used). Rather, in practice the term +"CSV" refers to any file that: + +- is plain text using a character set such as ASCII, Unicode, EBCDIC, + or Shift JIS, +- consists of records (typically one record per line), +- with the records divided into fields separated by delimiters + (typically a single reserved character such as comma, semicolon, or + tab, +- where every record has the same sequence of fields. + +Within these general constraints, many variations are in use. Therefore +"CSV" files are not entirely portable. Nevertheless, the variations are +fairly small, and many implementations allow users to glance at the file +(which is feasible because it is plain text), and then specify the +delimiter character(s), quoting rules, etc. + +**Warning:** Microsoft Excel™ will open .csv files, but depending on the +system's regional settings, it may expect a semicolon as a separator +instead of a comma, since in some languages the comma is used as the +decimal separator. Also, many regional versions of Excel will not be +able to deal with Unicode characters in a CSV file. + +### Html + +HyperText Markup Language (HTML) is the main markup language for +creating web pages and other information that can be displayed in a web +browser. Files typically have an extension of .html or .htm. HTML markup +provides a means to create structured documents by denoting structural +semantics for text such as headings, paragraphs, lists, links, quotes +and other items. Since 1996, the HTML specifications have been +maintained, with input from commercial software vendors, by the World +Wide Web Consortium (W3C). However, in 2000, HTML also became an +international standard (ISO/IEC 15445:2000). HTML 4.01 was published in +late 1999, with further errata published through 2001. In 2004 +development began on HTML5 in the Web Hypertext Application Technology +Working Group (WHATWG), which became a joint deliverable with the W3C in +2008. diff --git a/vendor/phpoffice/phpspreadsheet/docs/topics/images/01-01-autofilter.png b/vendor/phpoffice/phpspreadsheet/docs/topics/images/01-01-autofilter.png new file mode 100644 index 0000000000000000000000000000000000000000..8b5c4fad634025f4061da572ec954cdf267e8b76 GIT binary patch literal 45173 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 Rrnn0HnVVP{KQMF-{V!{iXSVjgC4j@D8Gz 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

    =zRIBM;1AayTPQ_VZBzzE0wk1U4P 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 literal 0 HcmV?d00001 diff --git a/vendor/phpoffice/phpspreadsheet/docs/topics/images/01-03-filter-icon-1.png b/vendor/phpoffice/phpspreadsheet/docs/topics/images/01-03-filter-icon-1.png new file mode 100644 index 0000000000000000000000000000000000000000..e5a7e7dc09c53595d3d47a6e3aea9ef6d1253643 GIT binary patch literal 453 zcmeAS@N?(olHy`uVBq!ia0vp^f*{Pn%)r1{y`?=4$YCrFa(7}_cTVOdki(Mh=0^i@ie*gaU^Oi+> zTjm{m{`xr(1ZD0G%G?^1y7l?v=g%MCbMmdW^sT=4_};zy_cRTh?%lq2@AkcGx3Bdq z+I#K#wQHBJwanYQcH6mYm(QKMd~WHsV?b-|%G#d+DYcRyzhI#4gaO0ys>VG)9h?Oo zk;M!Qe1}1p@p%4<6rkWLPZ!4!iE!V(My_T99vA)4^kWGg!AtlSp850N{#{V&ZEfG% z6>)q53Ts!+S2Ac;=~%UCSGc2)rBW)}Ztf$LG`d3ATAv-j=3 zFtw;D^8Ld3yQ3SN*4z-j%5~Oq&i}FxUAHP0`UbpIUdiwGxMX%+Px`%jiz!!^Wyi|? z()T`BdGe|Bw47)awJUR`^QD)cy}0M-x^p7drulYC0U`O#H4L}C1ho`4@SXy?n!(f6 K&t;ucLK6T=xaoBO literal 0 HcmV?d00001 diff --git a/vendor/phpoffice/phpspreadsheet/docs/topics/images/01-03-filter-icon-2.png b/vendor/phpoffice/phpspreadsheet/docs/topics/images/01-03-filter-icon-2.png new file mode 100644 index 0000000000000000000000000000000000000000..1567245f34484f6caf50ca4c0ed4873d311a4abb GIT binary patch literal 640 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 X;)9YI`!@lP0b%fT^>bP0l+XkK&`adE literal 0 HcmV?d00001 diff --git a/vendor/phpoffice/phpspreadsheet/docs/topics/images/01-schematic.png b/vendor/phpoffice/phpspreadsheet/docs/topics/images/01-schematic.png new file mode 100644 index 0000000000000000000000000000000000000000..8b6779204c7416b176c6a45fd22e8874779da120 GIT binary patch literal 14519 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

    M&E2g}Bvvd* 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 MJXU#BAOnT}H$4rR*8l(j literal 0 HcmV?d00001 diff --git a/vendor/phpoffice/phpspreadsheet/docs/topics/images/04-01-simple-autofilter.png b/vendor/phpoffice/phpspreadsheet/docs/topics/images/04-01-simple-autofilter.png new file mode 100644 index 0000000000000000000000000000000000000000..5dcc6a47cee34a4bf7bec206542d336d0e2b2dbd GIT binary patch literal 67694 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 literal 0 HcmV?d00001 diff --git a/vendor/phpoffice/phpspreadsheet/docs/topics/images/04-02-dategroup-autofilter.png b/vendor/phpoffice/phpspreadsheet/docs/topics/images/04-02-dategroup-autofilter.png new file mode 100644 index 0000000000000000000000000000000000000000..da1f089be02b1e0f142e770548c72e917fe18323 GIT binary patch literal 49268 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 literal 0 HcmV?d00001 diff --git a/vendor/phpoffice/phpspreadsheet/docs/topics/images/04-03-custom-autofilter-1.png b/vendor/phpoffice/phpspreadsheet/docs/topics/images/04-03-custom-autofilter-1.png new file mode 100644 index 0000000000000000000000000000000000000000..0098d98a4999ed0671f357c568e6828dd8ba24b5 GIT binary patch literal 51786 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 literal 0 HcmV?d00001 diff --git a/vendor/phpoffice/phpspreadsheet/docs/topics/images/04-03-custom-autofilter-2.png b/vendor/phpoffice/phpspreadsheet/docs/topics/images/04-03-custom-autofilter-2.png new file mode 100644 index 0000000000000000000000000000000000000000..96ff2cf0fc856013ef1c7d2d7ddb1a57fe6f508f GIT binary patch literal 53489 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 y zga4KEgndUnkUu$6{TDSs!pB5@&f{hUg75%)VCSsHk)LEa&g};!VUDrBj$ZC^>s@g= zWn<6i@l*)8%2zUz-3tRb>T+p9KOT8cfUkN3Lsc7A3$%)JiH@}1R?Xs1txV_Ob@7iN zR$BsS_QTF^{pH(dvJSK7gh(T&NXCt889pvSO3OJNT8|)5@}I^^LM=Wt%uyiLGC8><#3fAnGM-kF=Rgs-AWb5# z7Ktx{h{5iQJ>U5R8!IhT{7mrMqz#_jW=UX_$ehSDCjx8*Yp87>hn%&Hql~=Nhm;^_ z@dxun$J4VI@eFMiU|^&r)P0MpmnCv3@^d72WSeE=P)PD+>i zO;rGMcdK;-KeJ8iEG+q{iMTwq+v`w)B5Y~R=n%oT4*CmifYO#QmJ*7$!cNk?0SyChxrnReWsO=JYxQAqvLQi5v?)7p{53em# zn@D)73Qh0T)iGlFi}KlyW=Wmo6Hoyh3kP7q0uQg>YVaj;*=D9zv3 zPM+27oV;TnXE|ID>krxoKC5g!!5%36fmXk(tV=m_u|)bBUb_!`{+}4g}jr{3&zeh4K_%vZKO=JZ*A7gxbc7FZ#sO z1)bK@;LC2bjAD(c*lNoq&3-trmH7ABt-~s;mtb=czk<@YPTY_XHws zxV*>py2TBsBRo- zeubojM0?gzPUnkQ%OvJS{r;N=n^)`7hd(kRP*61@pC*?#frffDq4Bo)gR+j9DHtip z{j(Ao0kcBso2AV>N9My9LXFvz|Hv?IZd4NhN6l3zm@oQ=YKPyFlwhJ@p`h>&Jtp^m zd(7z7jMkj8doAH0Eg_V7ABI_&HVFfr;;_^JD2n5l<<%U&M2!Sy_3!%eNHN zF>=;p8pW}Oq$9jRkw~*o54k}Ed@L{#hrLS`#{P^g5;I;w(A;Y15()kuU*Ba#83 zn3fD_twH$VmCP{8+m+lmJ&o~TI7kLO5NX&pv&d;=#PBi2$0z|3;q9BWp!+>6>h ztOeFfQjU$8w@Wprhh1#>e|3CH?JQ1$bd1ygNn50~h|!7W9hh>=p@o<__l|y_ycl zS(206ZzNS6OIhG0GyOz>*@h2+yZS-D{5ks{;2JPDtC$q`!oSeDk>wNE>4Hl&>9gBd+6R8l`CuL*MTcyML=B}a6!%5cZ~(rT*9)$Ob{3E|@!^!^um zc~cZF_0hT%iS`|UEJcj_*7E*UP0H94C}BVKeOoGYD)*c#!Msj5rL!y?2z2CF>Klb& z56w^jd}LI~L@>9cuhW@r$G)WXWI{6!<}j-Y%t(t_v(sXXnHX3Ix_~YXq9cNcw|;P9 zF1Y1tF6gHYAG7z4*`5!3-CsHgup2*U5;`ZlX3a*CU-M=yBLU{@{ka|lDhktPKl3Q+ z5_M9z3PpJV12e?`enCRJsZkcDa#k`xhkMF$!g1p0Vrk_T+t+P?Si?2-%}F zq_OZEP;=h%mxt3)?1g+e{%>_fg3T7p+9~KyB0wf(+>K}{aMGI!Z2Tp%PL_B%#t0Fa zl-8(9g|7pH<*T8CW&?zFPjSap1`^P&N|tgdf>*|E%(VoSS|yR0pA zWTx@xdJV9KU%~HQ?#4Ba+Aw5dm)8q(Tl)C1NB5;?e@`arl*D1m9SL&FNo&QAkjxEA z6-@9`E__+g+i+jqJ3S$5+R=EUB(`^Q^yu!3#^Q-WraU=K3WAsH$PGN)Z;uc;JJ3Pe z&54uJLA<9NV3#Sh5eQJ@N z0y?Tt=BKZ~+@;tJ61XYA|(=f3tb3Bq=|HCB282}gcgC&HFTu6&^sjbo`2&x zo_o&y?|aXYF&qQP-fOSD*81kx=G^C)VqLrJ+PZn`#w_7BPlY&j-pKHxEE~xQ3=0S~ z3=%~#^%0{%dG%tb=?ZG*Qu`!hG-F>!r>o>OEAy9@+<%NUrKIh9?Fk<4NqEkcWPCxE z5s2eldwnV7Hg^_6!s!!~&)Z>#>Vxv`x4x~duWWoC7pq%42ZkK7&dcI6g3TF@A@>Nl4(lC+SF;#Bb360z(zEVI3Ol3Z}ukyqIyxkNE^0lLM! z1>J%`!9(6XNtmNTX{$Zc3%WL_!!fsxg~CcihH<&^yueDPK}p(t6Kr3;DC}2z>PN4` zy-uJ)^X6&vnE>#)^P%oFuPAB>BO0N#1gFqZ$}_!>o%$|iJih5>GHz^{Ut1%)x5j>R zvndw6nWhA91ZKgRN!+ln&soG+UL{z@%VURT^qNt#;-blZszU86Lzt>TYx(#=MekR7 zFZ&k;p|VG$(_L|GX3BXPpY_xe4`*CM;cvwQW*@`okAP<5z{aJQQ1@Ud%xYqDnfg5_ z_Dv< zr#D?8Wp~b5r(% zIU1yC8QK7PgSjHP1=L2vKb-mqn@rVR9j5(+1~Fl4wh%Vi+Z7RS!EKh1AfQ|*#BNGC z@_Ts0#w#Ibpwx|Fw?y+Zg&(c;qYOgg<<573VTiioiK( zb>aJ9DtU%vK3%Uv`HltVBOk+R5<3EI9BiCmtONp-&2wI_Nqd=e1H@D|5|l#)usuza zK;tjQMaF5yImSJqV;?We?=kQlc9tT(hgT^C=aSF2RvuM~HVF<10?LXRq0@)+@AD4Y z0n=JT@aNmd!;ZSDnxt(BcOK&6qCu^4*qgF$hF6UQG@4=y=KBP(qMm8(! z{6+?5GeLsUR6zj*@?kL>lR#RZ`dCc+eJEKQ!W2NAEJ#+U6OLYZC}!Cm;CR0es>>Kq zJ*Q%xLbXWnZED5nsn;!WA8e%UB9+%LUh6qNH^CiOXe5$?=b8g_7tF$Z9+7tpQUb5)B9Zm!(FyKmhtFDewW8x{lKzMN)Xj| zmHl25t(Zg2Tu#@QZ^rQc`DYN@o1=(C1z4vs*7bG6LF#7^m#) ze`;k8xFp^;d|iCGFp=74Q9ZXAKK&_AEpoLi_-y={a*p+&2#Ks=w`pohTj*4S2<(q_ ze@R#z`dS)eGxfn^+@V&%+wlrT!F|$ZT0?CX1?y$01^0r)1Q~XAeWh!K=T(NcYAY;D z7(sc0ZsmvHotw$m3rBv)`P&UuF44bQ5=f5`QrY8i3NuE3SK>cPXRpV3Lb6zAU&|1@ zUa!--27AOxXxSPBSc>bfwn^Wcd6U&;-D{%So(`mwy7nCO_r(!R?z`5;ZEfh8hWn;yug?r8vRWz(#@? z*5k0061^SZo!%qArvxW@1&c#C9e^cD0kROWez-o7xnzr@b}xbFJ!ZG}h@BZB%D#ukc^+x3}2xg`5qZrmE$BDh&6HOnRH%$uxmo%f?J$xXYA>l@iU`aH@ zUte~MfnEvr-f}xWfA)+bAX^a9X+fiANqBGD_k8RnW;@N53+w5yuPg64o313xyjK&q zr~n@SG?Od~{@Ju+jP7U*F6Q(U^inVL!R?*mqfrW%@JnmmNeRX=?}u;b!rdrjjgK6> z?tjh6)N9@HT7Pg%8;z6!g`2-4gPew*CE_>jQ^89dYnPAQFv>&)NgX-7-A z#y*>bFb7I$2|s!E3=USb za+c-MwtxA;%05z8wIS$g}DPC-VoY5#5S|$ggInPNi84S#oAmhZHm| zAYxfb{^QIkIj*Z!Vxs38^~TD3jr@(vz2$8to2)}AUWS1~1dC*QhM*?OedjDgHZun* zeS>08QbN*7kbeZGrK}rGjRHoA8J(LN4iv&G<|%6uo`!eg6O=lIr5A-Utsh6ZaubyD z^265$ixY}Z&5M94h3@P=nlUpH9k@ie+Ei8ovf80?ctN;LHtwIO-haLBL%E)vGT>#| zGiWApDvm%q<0)F2H^2zR0y-W+Ojv-{^&HJ8ARC+LylSWdHz9XNKEDbh0xr}K{uwM= z2GhidL0^A#xG=uc;3tzK&D{Mm`jdc}giDyk34g4E+lyKI$d00ULdqxTe_-yN6NZ(o zoz7vMHx&Y(es(TP^G0NS!KHjE48xktb?lh>SlPvWm|x^gUdFabj4!_8;*KxR*{#_| z&zn$eh)f@{AG`LGH3f&?sWp}auhr@pYM)%;Jh9Cjf{mN4%81H0?tCoC5^o(evdS_E ztmYt1i9w#K4Rmk=A-Z`3zaG)-R%^=|X?r-~jKKmxcZ5kOf|yiU+qNjr*|)Zk%WDeJ8RW9CHv#P+$hb?`$z9y zoWNUZhQlVQ>=n`tKT)@&AowMCIdT*Lx@8Go2-(6 zX93Y<=QDgFynK~tLs#N?H>QI%{y{k}Q3Eph(B^?4N}w`C?q~--c(L`Uq%GHG7@`&o zPNQlB$aK5sAHi)i9vfKvEZoR?}J$FdJnF(EY{e|rfVA7TSHZMH1N#^ zkQb@bJi+&+SaBmw1l_*P%X$|6mHHV!cx!dIoI{XM`v_(!7#O}dD$FHl=+Y)^$BcIQ z*~RMDluz3hA|D5@2ubh5kO0o^K}VNteFkXFLC|%`BJNYRXS$@Qa@rdRG~eJ$-}~ip zc%Q)+M9yZc7mc*Ag#snx(ZC&;o`2`u#_Q#y!@M(~m7HfA6=KM4iL)P>&s?>ycg_$q ztczF>;z4_imG~--A-Be4-p3?3)ViMDHO6cvUuG(Y^ejIv{~0z{rE3>CFczHo{+w^& zbW_~OwzE(LkGxVn>q(bebB-PXxuQm#Xwmv37aFatMTI@QA5VZvnQ#B7(W97?$cDa) zXRU60&5bLx;5wVmqn%M3T6&2nRYCNUP-jHrsHaXpRwV?#yVjq=yqbX|KAdvhf4zp! zmX4<}lmj*Adyv)}uGKLPE;kTPca=3Wo>$1MWYUnS@+ywoaZLXsH#3;YVF z=@UC!%51F}(oO&x?9(1+F{2%M03o^3I~O@7-$56)j3wo# z{96^K5dG|SviIZ12f=-=9wW`llZ-%+p|&y44{XSuOS=TE?K)vL2|!*5vDVxs*=^zG zU>QQ1PkRF@*5VnzU@nlZ0}eK6NiHgzyTWg_9>cn^)Z?vr1{SZ0-#>_n**@gUS=wjT z9a)vws4yO(uTBjOP}e*Pz#Nh!e(nGT`dZ|%j%0H$h5h7Zgw)Pb-u&BfqQiyh`iNO~ zhI$Hob$8Y5QPP&ow%*_fx%8)nYaB-IP|(VC!FX_Is;-zp<}jsr+`kF2J@xXvj?}y4 z%GF#3D7M`hOv71cnu?>YGnb_T8wQhtX<-m0D$yOsPI2jRS}L0Ej(*`&0de6*-nNN5 za#lN5{g8g^tvZ{FW*)`%S`fyGF#pS8x)t}0btxs4x6h{!`JGO4X&)uhzO;OssT6sA zi!^I{Q^`aGmsT7i`r`VE#aHTCKKeu#6q|2NSvXn-x9ecoOl#nxXvWqPopQC7l~2@u zKk>xG z0|%k4M-E`&GadK>aCqAz%yo}PosV~X@&4PDq--4XmG&W}D40gl_a1`ju&pU{%3q|^ zd*#B^gWE8y$Z*OI{|4YZ@!R`{M<24k=K6Y4V+UA0PnE3@;X7S4{EFyUyLN?SX3j-O zos+uBqVA_6x-TWh%#nL|D~+Qw!~DmvbE~C!Yh{tXtorTXbwsAC6r@m2zGiclte?jAB%edL7;Egw!R{{blR2xLFQ`Y z#)K}@)(I}fJs`NK0jS)5@oXuda$Gd4rf3;<9gNO&*SyDaf&S`U`yUcKp5vHGyClpMA~ZX4^Q9NPuS5=VIMRjJ5}8O1km z-;>!t>aq1MJJl&S&c2WH*C0!TF)Fa?aORVFOsD$po+fr99<=H2<$-@paovu+5@`Wv+ z+=E_z=JI&1%!%{&on@@gG&J%AYhJqtWG3KoKhEc6*lN>hZQMSY3vi^c@$c+(vvhV{ zp8Dgdj!cGf#l87A@SAT*(R5={6^KXZQYnwr>*Jo*9xsWF5wik8z@a1J55>~e3kW1n zKM;f2I#j>%@*S@_)f^1CE1o)X!~)@*6Nj)D8*e0E<+1Z!mP=Fs9K;lVd0oS)_&mB8q3LsuUI=-D>-OcDG1Og=(q?cKX^Jtci;5{E|uz zRn2GE<{x-*u0G;g(+$hJI%27+Xy{!*p5DCKJ3xqDL=t~E1z8@POrhX4=IjsjxC9l~ zqC5EUp|aR$3w6teWzv(9z>7Pun9bvFX0k6Qv-IECd73JYZ!*YHCYpVWB?e=%sEWrkvx*6UucHNgA zx4YR20<6!dxd^?TT~_G53eaLa8q?N`9DG-qGA`cyeyMgjrbKrF;{vO`vVSStCFG|} zP`gEFJO40?hduX@xr-!k7^P|gL+9$azHL=A{SrW!PF3xNshx+G$FiS24FNp~^Km)_<_wSfi z`Ym5(M0B6PIK5FB1R*t6KsH$#K;LwxBFG=sG?rFr9m$h)9<9iFZiWQRm+?8);@vFCGTSmfV=ZfZ%2D+AJG zWpobHit)S9lM>gg^@oXNk3Ra?*G&iTOM?+=_D!6z!#C{$r<^sm`fbnV!`_jkUrk8H z##NuR?FJ^#^_rhnEho$K*RJ1vdHuBB@CIjJQct<_@GkFZYmW>LD6XL*GC;q&E;lO_3?1J{wc1d=JD>3coS>$1ol&+6Lz3v$L zt)ISK=SgL|pI}mVsLN&R5Ppa+rtr$BJ7OHyt)2Are3?x`sJqvwn=2lEqZ(g#CRbkT zkuS}uXM@Kqj(aaEA>phs&6w}an8n2>`Y*;VF2eNHMarO5{etSX<}DBVcQ&E8aT3e< zK*Xftd$rqEDGXaWz*~&AsRs`BJ1OJTQ(|BD#~wgyx^|{BRJl0gLSX@UMNCy&0A7IN zXkbN3eto2}M;EE*ScW7XJbEk91Etk_fa*f^3Y+XchVkY%D^xARe zj0|r?oh4>0i4zfKmStzre8L`SR`c|Mw9VY?3W+kzA-xt;lcy5nb5p7MwL@qWd9^aY zN@lxnHk7$0!)#CTcbMWQhA1b}J{How>Pr_|fMn#Ybv@_zq^+_zUYOZ5}=CpF?efON>%9-Y82GI6Fp^lPzy4tW84XWVHW#W zA{BvPwueK5Il=6t=E=#Mru~INXqWMi4Nv7Oar&QrKH9ChyP=$C=!3IlM{I>ZoiH7j z81v{RGJFHXoW|XVWc77&PU#p1n^dD`<3s4zR}NgAo%$XrMQUUQrsk~egx>=%H6|8m zP}p^Qfc3*nR7~tavyC;ykzur-d0L{8jMx<5IaLM8sY9X_MYDeLK8b zOvVKpPdxp==sRM`lb}3L?(v0);Gbh2gk3Y;5U_$DQ|1}O%=*;9+uhliAYOU)POWEe zXkd|*ee?Vtva5!ZlnBZf3WMOTu(#TJbp^8e`a=!=RxIqf>h+kY;%a^OR`d1oZuN{4 z2oh8CEAVLLYIz`=+b3=yCtB(akoDH|5;jBXC;Z$t=I*k45Pg2NeEh1>D~7s^IK=|* zhi$y|1@VW{TPE$r*Y%9$|M`okH`maNQwQhspvU$7e|{dir+ot1AlI@liv8Ika$Nr% zg?R-wlu{7sHbHAC{qx6-6179X&!81A|I{k`brq|ic7o|(4Z-R|;nO#d-xY!i@N4!%#4?&PSd4p?5i0MK=3Hafae|ZMT6y2h=o(ZBPn5DzTUOoPK15*RzR&3SpEGh z%a{5;u2$_^FO7_dd!$PVZuIv*WRAKb+TlUYp|}}3U1L!cz_jJWpQVUwst}@M4z+{$ zO$OW~Edh5ZhlWaJ4XI}_$Ew-9t-d?FViyo{bX+L^>vNmo0qb})XCmgi@m!_Ee7-)9h_WUo;XSQCzkcb#y!N$FqBZh6 zRh}qnV6Jt9;H2c@BU&X4ao$<={3%%_ApSVMr)(YK_kn=A$j`odR^gokN|C7Dk(njt`52>MYT(U5w59${TEavo=Wa zWzup1+`|v?&@Cml?t2u2Wk2ZXc7pGBzNFu;`$p{f{09R1Wkyy%6#~!eoM)Fqe#a9I zyHk$t1DXzqM)%ckedqsq7zDHSf6P}gP7nu3X@D-Nhw7mId{-tc@R}_f0Royl(66S! zU*qjssA2F|KR2|oLz;{MIzhCL4rb+n8jz$3&}!|2;A}5v%ATLSzs*N4y-YP=5jt@+ z9P^!NN=2{#-iDWe03hFwt((Z_KmqXgX2Q4qWcRt$K({XVH+SxEV(q$~&9|PAaJfC@ ze-8Bd8O$6&e4`9hEt`qx_551OK)aQ9Dlh__QP1s>*WvO<+W%axJwAMI?)JNkx;D`b zd~US%2PdpcW4!?(!ughXk=7l6&`3H8Hzep%@KOKW&LH})m*V)sDA;T5JGZmZ7SOms zoBzzXZpM^2kxulHoj(+cGfmh`GWhRVL)PWVW`?m5P11#2lub9S%RDT z^5gD&{$ouHMUq?x3e9-|DXkLZ5TpM9@?K{WE~@k@m2-t z-Pna<0pX$yfZeF;&7Z$Fp%m z!=^tXd%p{;@a{Ycxp$)qH`E&UMbOdpCRlgd+oIoW(EOM&=+^^1=_46*i7g$=wGp6# zK0pr=zBLRHgsU{d`8Lm))-mp{3!^ep_ZPn8b-lZ;%6}5A1rbu`oP+jVSt`G#ADbJb zs2};!k|Vt9Vr|7utfe9Zw!}gfPfl*kYHHXj2R+ZU^&nNWfdym&+U-;{pUm5<$4c^d z@A`SAAwwD>RNspvIbiH<+n*gV^23u=XQ@TzhjmxVir%hXJy9&?NTtH+zsP>6n^1zT z=ozV%WJOE#-&C}2I8xhh@&4yzT{o!n1-i;aE5jPRW!@~Ur{v*=e*U}x0HlPv4c8iuY;__e4-~)d(L#2bTCl2?~0Q?`-|L1&d7DDnKn=U z4>h!eA?S83Kwj|Y6ld`-??4$E&FK=vtqS|{01|_>6?0t^Vq1cQfZ2vG12b08k)$Si zp#KzT#`UB^6RQ? z7BPRy#Dx7gbN-`}+I`}wV`xL87y*E*@ldR6*D!%E0N-HyM9a;^E=obqiGe>JJUWs3 z_dMiIN>>FO3+v&g9Kh6i8vq-SD8TLWIv@Z9E!2bTvY-%6%K*ov){!OumuotR%d9KUuW zzZS6P8#vg}jGE$Y0F{t15Cq4AY^+y_&@T`lW4GDe_1VruDKJ-!+j}WEh*bvl*1B>T zV@6x5wZUO%;O=78qfeJ#N7R6s8(!#)iS8c>bbf64m<8 zn~{hKKhjgZCkj2!o)x=q^8;)V3IBzLaKU{@p!4 zSX$r|&higt=yZ(y9vT|zxV5CSHx;SLtKB3HsD1Bj`ghWnJ#BmP*ONqTQPTjGMCY~P zT2QPuFbU4*z=ka?G9#yiP1mlym%Iv@Zji>ZBDy|5F0>$-=oXmxi|viNlRb5-U8VVS z3w=Llwq+DV&USp%((1^}2?g4c*|p_~!Eg`b%PF8+s!o|}ieps=%D3D5p63=7FT4JR zIriBn&@MoCl<0A5p|I(>V6Gp{CGOgFjTu#HJ%WvZ zJ*@dq_lb)}2J(u$*Sx-wV6OCNa(6p>Uf@3dd`iY5yO&QDscvPX+pxWRMbI0@tDl1&A{D3t{os+ih3CR zNTC=!ul{IutuapiH_*GJ2*n~dbVJ4yN^w9}k^aj#tB_HhBHvga^HXEv$MSC9fB}Yl z{hAKofY)KwcJJ6$$aEtcHLHk~@e3Ap^X=JN{iiEjckfo1bXo6>rRd%#H@VoPa|b@} zobNVY5|Zp?G`qtY%?$5@e(ojmWi?vBj4oyVfcldA*_lEmHJ~94La1q1e!XvD&dbtj z`xjUa%7dWzqaq%*u@utINvVjeDoV>>Ho;bN!v}_ z2UIt^5xp|mJ~ zrtx!5&St<_=nvnZI+J{#p_bQ|$;>}wI5Yj3v1>AYUq|k6YXDDyUC$M>CH3zggQnvi zr+Ph`>ZC&%2XPd&X7z6b%6I`Fl>oS!w>p9LUtU3vfgMxy_KN#TY9QN!O8Dl=GW&Rg zV7?e?wl$dP8yF0B1Z)X&5D=RT0yJ8?U%xqLIfa``ka552dDwVn$zp}P*6Lko#5!bX zP;^t6%8n*%(;pGq+gxVq8!=P}`f4tSTE4w6qD@(5i|&+dZ){9*1O%k@d;IaS59dX5y$>e~G*Em~cYVmk1jbTd-57?lki{>ZcxM}du&)`WHKdmkkbZqEIm<3V1#XxQ0 zXNh^=7p=UHXt!0%3-L8VzYo`#o!lqaff|-8Hpdoj2PEJfsPzj`&KiC2A~i5>iD>%! z+MW|S<~jV0r0@+SF=RpWCNxF~WnB3%jWJQ$gO9FO!zqV^`FwihmM-o{+wEw(;nITqwY272RlO?h`a|?!oZ@ext*j z@f~Qdi$T1AKLktLNNlzA+phJe76o|qULSLe+^)h^`nhH1X1vKH+GylnZz`@YClx=( zvVI4EVOTtDSm#6Q<%gZ%(d)7|qZbhNy-w;+?X2Fot)1Vg#MC1F@x_|Iv5Uq?17UCh z%7B>z*gms!bK6cbGvIY8X+{C7-T)p2j_fCgOQ6r$IcaM?=s8+?TA8aR;`Ya8!j4F| zyU>{yD6zJs{2bk9*pbzBUu(W=m!@+=s6gReEgq_AbS?d`rkQFoN15H->h^en=qty! z@nP#_ndd6R&o#+vu6v(d-DZ-rWm-$qxh-`dh?K~+NRerDux{08iuLYKbhKV>0%iwv zYZst?#1f&rRv39L_^qWfk>N*r^V`#!NO$vtAfS1s->vi$d}dgAh^EZwpn@T~e5 zpSq6x*vW<61)xIygAAOWo*pwFEdz4kAAuy78CpX~86dnz9sdxxMh0Mb{y3&7iYQeQ zhGMtw4U2Vnw8<($y-Y-TZenBV;RgI<)9VwS17Y-S(1|qX;Ayy-KoH zKHA3T8sh+_thB{`wZ6P7DVEyX5i`w1v3y*@I%jKdbCR$>mu9kBB$s-j2<$3RdZnxm zfDhNwaEldYj5|y9LP1_K{NHW(N!qdF)qN1->VaC?Hf9YrkWJ=+MK&&>uUutz36J_U zv7JAEMl}PxfJh}Dt#cmx@gJes@m6ZD+aIzgbR$wG8bK^;y6lMOzlT@(f(3w(Z~3BP zAXpg%ktgOz9SFAkTOG4p7r^)=WX}!3m*>X3wEUc1QSk;4YE(aN0X$wEW@rh)S6yl5 zQqadMcAX6#k5T1~b&97CYVwH<2}dbgmU9D7mhc|>BIe*Hu)#tWMyZ|2NpM$LgmF`s zMWUE8+OzNZ)B3GxFKau-cKOPy+Oc?Dhp1-)qDAQtsF<6rPB3%mS}| zan66-NPG`TY$;jR{aj`SNFe#18Bu<=oz4J2WWBd@bD>Ib#qf`BXgPlg)aDy2Ab?(T z$MseTcTSt|{|na4=cdlM4jj4VK==$OMrliuRfRW&qyi*R8}Ec11Hr8K6hId;f$G#w zb%P4j|K(bNmTe}HdB9=L&s+r{ar4T6RjJH5VK^~a477SoSZ4p?bSUKLoe(OptK+J| zN%ZzH<||eD6Sh`08s6X&`W?>9_*MrJ(EQ2^9>cOD*d&}oJkxcc-%#o0s%N0T*lGSe zWckm(qcMD2gd4gkin4t98Gu5AkZ)-roxv`yE=&D)ONZ@)CG3Bn$be+<R2e z%;TF(Skwv=C{N5$0nZdJhCa6Wtb6|m6?XloA;<#t#*%`}Mxij^C`}w4Kh)h(Uat?L z2cC+r=c(2cryfaZyJ!vFUo2$593BYNkefyA#Nn^&OAQe3C#XZvelZv+I)DjXL8 z0b-Gg0FNB$U-JMI%D+3H0o;SMBJ*}Ay!72MnGe7m)Iqq5wjk8l0|eI8BLH*JX!CDd z4-j`fy~c#SQUrJfS`RZG$KXvst3(SwmyQ}AK$-bp{y-h*nA?=Ei_lPyFPHB^(inOj zucjf4bq2o_6Bhh5W9eH-l;mc%YXv#~|5 zIiD$lBfLjrzUNbQ57!*kP11Km_5?Ij^Z@kC5MbIY9sX`YvpEn#61qV>J9gU$`%|Y)tz-9sbddgeX2so5_4_^=#f(nbfZn zN5W0t_y58m(-T*_Ia`4^>$rK)!V-7T=Owk)ve?_o?3zgAkTFw!W0gAI z)pD_5X!^9f(I2Lw+V?nb4TMKBEW#RTA#_KRva=CFp}F2n&dJ6lH^PBRDR0e#R;sQ9 zam>3g^>Y#mEGn~+Bot{(&pLThcqSaZ$ukKTPUqZ9QI#_#rC1)gM*mK8=qFy2?UE_E z4q63ZsfF*rb1f~Ul9G~RNATGD9I~k%0R}}lDR1`Tn&B{We;mT>=Zn<%tAsiss&ir} zk<89bfx0#c-oV7QW=47PcD(>;MzyP;&6{PYJn;uUk#<1sK4}`FnLCe3G6mpE)Lxxn2znqO282Uin!zRuIznSNHt9Wx8pDN5NO z53k=$@Rs8mHs3zu+K>Ip;vHoA`^;eEK8E!GBB-yb#^>YQw`I3G6fOX5)^6Kd;Y1S% zBpGOiV1WbNhT^vOCxZ1UVn2~0x|Q9>f!hkbp=`8KRCxZ|sSOTFcR}q&%$RXvff_iV z<3zVr5Jej#@bB|d8rxZk4M-dV@+*L2C;ZRsg4Nx3IO=o4C|5s5pZ_3wKu26W{{e}0 z6hMZeX%CLW1`AaG2sSeiscaqrkhzT}eLSHOx0ryTY%8QfeHs=XQJr-0}<3Tx7nQy73LjP z8%=THH6aweTtw9BY!I|xzMUa4s<*@2b&sVR(BqQeRTy=RzT};$`QSS|BW1WQ)Dtby z&HIB-Hbpzxf9RvI>h0dQkQ8*O+|)bsD{S5umEs!f-L4!k7pB;}^VUR}PM@6*+sCtH z4J2ER9y7rMpNBy}1C@x#dSxY_FseOC+Pu1wI!y~&1q@4}j>8MrHk5If7Q)Ok~$~L?!l( zJ+(Vym+PAK*MiPDfqW$6#0xg$vO$*upxGKdj&HQ_0_8cRCZZl$8^P1!J0Gb5a;Epd zqag^VLwx=^7=JYjyVlT7mL;iTci%aFU1x7{=FeIaHZPW-=erHux)IL$?m&Dfj3^Qn zpB{hpds&m|!=jDFAc07-{$zWcX0oJ#FxxRd@bM?(H0B2KvDb7Aw^jhfFbCj=7>auU zsg&3>9JOy}+J;4xdjbFUcWNth*o1WXZnyTR6pe(Ms-$m^447Y~@y=u2q9mA59o@c3 z)3v4nUUrtQeXyzNM1RY;F9^P5=C#zL;!s4auc}`sS>YYO_K)(i+%@qeHmpw6bFw2e zsJOR1+GbM1&u$MK-enRMuxx_$$}o7zB7V_d9~&otUg3Cj+Q}v%qge3h`}a`5+b+o` zC8XfoZ%#^R^SjY0LH{-`=O}+UAZZi>bDMhb0~5B-|I`17F#PtF8-fM+xEUZ-$O8@{ zz!HMu{5!1($Tm>uIkqRuc7FIO>H*ZJ|2rW90J)gYpFf|PT%z-FSsfi={(VXU6B>0c zgjmZpO)=0scNuVK!$s=|tP<9|$7HOHApmcnQaGlD%<3{c{@>xYgKNSehW!PT+-mv1 zgNC5hPmiLXN)`Gl(g1q!$5gHWHRw>OP1cZgW$V9h3c&(_PmCtJY8oTwHx!=)HyX%k zo3|&XzM-uPjga;OfqApm7-1hMD3h~V6C}F%N{9a)@bec%8Kg;=qmP-$vqa1_x;w6- z5k|Yg8wU>UT_WupSx1K{7_Z9z5hnx$n~RjwBwWydUe_F8u0<^fcifC?PPp|#DmCRd zFUfBHWDgzN{6$~*uPMfXR{IO!2~6L9Va~_5q6_u8Xhs=fkc%P2E${cqdz0*lL%09r z&xH{^auqK1!WAp={ttnwWTm71AJ?^R-MaM&R_*okivgh;!C1@33Ui^x~ z=Z^=wezNyUX95YZAsY89yXH#%8N`UG3>3)F!+0xSVBZ%xCN=!?vS}YUc+xGsN^@5ms_ANH-<_1E(d&k0h>{J5Yqc-d5t6y<$EU z+NURF?`9a+0npYjfQC(N2xFCIJyPNNd*Y&0!Qv>-{ixnl2q@1crR(-3V113yq5Ie* zUq%Ue9FtN;h!%eI1%yE6fyTH0fYw;3V6n9h3*x(eLxHZ>_7PJVytHUUcTCJL8 zbDK*|-q2nhjaNbmeRB|;e75(%2!b7{{ErQ*fnxW`Nco^H>jB|cxd8=3NSMVOl6G;k zF;A!rBf381b@zzzC2_Bo?qjKt>uII>j%-#p?dOf%(OZEJn%}=>g`Xm({%9@4VhtgfF~b z{*t!;SpEZdEZ&Lt$p)G`R@!SwpqYG+X7rfck=a_Mz_ivCU@toZ(^07&Cs*v>nC7vp zl-;4pnX0RF7GRohNRo z={gNrmHvB)gtJI>NfBSDAz_H^<#|Fv^&#eQsEmiOheOaENaBlX7FT_&VCrtQ5B& zd(Zx^tT?&CRuz>Ca|!UOnEaYVc!n@|yV~p>h0gOR?99?+cLTil{!jif1=)aaW=c%ay~p3!j#kaB5%k6RKT^za z`s-BV1TaEDK}2sJj2)Qssyg=oMajtL-;B6({&DO8COt=Ebd?Cqfg z>lbS%9AL9obRVD}TmP3A$MK!On)C(8eaAGTFfqb%6Vfj)J$wHA@pN;%!&e!aE>i}mKW57)h; z@;`~d4{EFHk-=Nki!A#emt%ysu6^yaq9HCUKqJL{gjx#xe9ogrR=(t9R8&;FB2A__ z$y9+Uy=%$U-yB>+If83ZWLjEes2S+v+pEFG;>O~*47<1&wzQ3cZ+!$63p*foCi;FM zuJMkB3$^}J8Hx{RC42$u%ei}w88_a&`uG~itd+?-RChbw&a$??-ZNTE`z@F~>2pQi z-a?|o{zlGcHTCYF8=~r(?Hw&Gysd4?W~>@1QVQmDH3hY#BJ=sLX4YXdEI{;m<;$IL zmGFdi>RDDHX==W;mXRDw&?sUQT}yUU11mRy4QJmn|g+ z-9LU4o^lVDY+M}y7C4$Krt)|dba2&K=5yrzuSnYF3x*9QXE+$w7KdN{VIlHTYp{vC zv2K=GCk<`(xV0C!0LtH}kE2U_Q5h5|2^Xcg*gzL-6G{QIh%Ur7AsMP^%~pANER}{^$@k z4=0pAz(k9O^qem>jaBg(nSEF`ctRKyFEQ=zo2<8)$YXc!?aoypSw5D@ulMXyjC!&5bmQ~)t@>wAgr2*dT?P4)v>N?$kI7{EV|C=h$)VoU1#lc<6+{wSM*Hq$ zUGd_XE90>6E}gu`yCOY<>mSa(;{lvxth0px8q3Vh<%hDdWwro?T=U~~fsX4l>J=yw z2X_J=A78O4p5chQhC7TsEr=upWoAu{h+xrzLZK!B4pK!neu_#;2xLo?5W!|i?_rod ziWE&@MWEl$SlQW;tiG%}Sb(#;HlUnOGNs^dfUyxD#73G#UP~x&xaWnewGF{xGzz6s z!{`t{WnJlNjVJEgKDZ-BafuOQJ0AT=yO9Hw4n|7p2Yh=zXEzW=5O$NzzQ$A;97obS ze`Vib^GyRAEW0BtB|S7Wq~HU3`e1O}swrH`!?$};7-3ry0h0cd{=j>{1t*97(Qmcx zEXojac9zmJwWR-JKW*+!L zbxV?P>_Qoza^l5nUUPBD3Pv@3%@l~)Ma(-s3{$hBg{o&qt z_p@IAT%LXmb#W{8;7Uo}*)>+JnHQluUlsR@@vBbA4k1{_`iEq=Kj3?J0I_leP-ImM z$Z{;q&yTYRuOI9>)CPY6j+v%Xu}QxOKTJuf>kEghrq_TK9^sjf+3ztyIO&)+Gat1uLrGkQXkTk|4~-nXY#!`Jm$ zYe-;w)7*)iRQX(geNKG|{U`nM9=Pc-{**mv2Ydq;cE-Q#B5vprQ>>iaoH*R`OWd`p z>s8m}qC^oD$ihqgUWN6IoG87XZl~b8DYLQnMES4hdQmjV3}6=KAQg0UF4fPbWJ=^- z(ZdFwyQTNskJSv84T#QPTs$%zJ+-wBl3iUW*D)#o0Dnk#yVpXJ6m8X$Ig%S@oKbsO zS~bYBO6A@{KYXTM9rJ9c0YlDt65?gA)>jlE47JMh#hBx^C_Z?qA82w`Wz$yo9PwIs zyEORqvk>CW2pCk{*RE-hsjwcoUDdE(<3ss#WTch)Yn+V;H8r(971??Yc-e^?K+S^B z1&+JJMdpl$k7H87%;NW7+)OZFO>Yflmf$MvxACgPViN(dAJuday1Zt8Gev-T(frU& zmtBDsUokWa0H0LfRWd`q==och3I+aPxhpU4>*aOo@PKM>mv&>Fb8c3tqdojX3j42L zd`FH|B>{LnJ1q@OZ@`N*p6^UI%uIFCKIQ!X9Ngcd4vuV>ACh91MG>#C%6Y`u zz#2{X*!&xkJhg=ye{4?UV{fh>9lE=%&w%;Vj+j*Mi^Pw8nq7Ft?>BAB`v|VXbk=se zjeXtJ%i@CqCubDCQ zc=E1|P5u8V;u*;b5SeijX~oaTo<-nMObMB-za9`BpbJlK3cv1yWiv-|Fl zT9@vC=%pd~pHC5cV~VWV_^&OGB0DO#&UF3%U;W>+3#qJy?1U}#oP846C@8;gyA#r-{5Pirtv~*)yd4?pbHXuGRDIvx_hM?1k-q}DM8&Ss*o0O80MWzrik9{rOKcRdA P_^0wf<9_ZvBj5iIv^nmF literal 0 HcmV?d00001 diff --git a/vendor/phpoffice/phpspreadsheet/docs/topics/images/04-04-dynamic-autofilter.png b/vendor/phpoffice/phpspreadsheet/docs/topics/images/04-04-dynamic-autofilter.png new file mode 100644 index 0000000000000000000000000000000000000000..6cb905d97fc573a4a4b53833f1cd5757b96a6bc3 GIT binary patch literal 111531 zcmY(rbyyWqyFN@evT3BdQ^`%Y(nxoQNOx|!OGLUsKnP z_ROq!>R$JHo;8FkDM+KE5TZaqL7{*AAn^$b3MLQ=3RV*d0SXHG_s*;c@BrHBle8FA z*(mWI@B_|5R9+McsxlV!(Fh*+jcorx%Lxh!%lY*WdfA-J9SZ6t|D%Mcs=NMSE3zMM zZ{zjKOEYA}t^H(qF>P_P&j%kG6M-Q4ZKBZ%k5;IQCh?5F(TWklwU5YzrSauHb*iPR zF|njs944+C4mxJ5gS|WszJxsG>GMJ9K3H$5aFlOp$2n_qXZT?<^th~S*=iZ0;NqrM zU%8R!Wq3msyKvcmBQz@0c|cS4Lx&sp?KIC*J&Vyvr-jYrrwhEzFO%zF-r@g=Gaq($AkZnu)LCRS5e{T3Seq8BPDMT;Xec4*7p^z)p<#ZiUffTR1( zlW+Kv_7++8^HG&YyRXZlB3l(01 zWplR~487PebVulBUA(>g=akc9)(k2WyK}IadoYBA27-SdnNSRxVKPfRcuu(XfR!7V zZbKsuiQ;t;DU%Y0iV4_S&uKdrHjV~AZFbUqTOlZK?~GVr7HE6ML8&azr&sZQ7%{j5 z zI(Q4=M%AzI?p!;oe}N|G zH0Bd6Ars3p?v%<0b*fOT|6*3$xljEB`8${+A-FVuH*BVOfpO(5Wh&~)mw`H0UI+I= zV3kv(Uu>*8TpWRut6E2i%CSUnfma@hlNsj*rbEG`lFDrR0}K1)qTggC>lmzQU=lO; zi;;0%{OY>`{{G&uqPXk*x=U-)B;VU8NhTgi6lF%8fw{dF=Y*?+bf#d-C-AK2vx@?P ziS}LBhqX0V#~e_GT3m zm&-{dMim0wgky+i#p$^=8&>e`Gi|}V{mq53`}N(&L&tJ_GG8kPOq}Rc)9sIW%Q|+) zpYJ*FO4~R-g}FsV7=*=r5O|Wib#|<8ECfH6#r@?-EFH)gYLhc+hV~~-wF!3LpJFJJ zGiKIH*(_y`=Z}S2#p3-=+*rLxj2hFN^$azd!H4MkxIwlSuPDT!!UgwekH%p>rbo&k z@ymLBcOp+WrH4Skl$Whx%VV{&O8!F>ZbBLrCc(*UN?3HIZTIL03K^P6Pc74Sk)mKL zA2VV5h}(?_zhd4mvyq;ZnWMhmC#Ii*HMO(`rj^%d3XcyzA)I;_pSUG_S0 z7%;GC@ehX&7r!z$r|m3^T}0JVD-83Xis)uVl=ZPHFk&vQvK9GGu;R%mA!b;jC0*b3 zsW6m)RYLU$O6@_K>xzzE4HIL3vO%`0;#xla^2&Sj&PXn;qM+cTr zzU&e#)a%TS`nE(eE(~-i>@2q7A}F!3-VM*>-?DL!_H(vd#q=fGtjB#WQ?Hx!uk;L@ z&Wk|d;EQUMWpru+okhT(%&hL|s0Cr1C+L?88AUOtb)&M9B-nV+3BEj7X@57pTjRZ34?N;6x_*^A0^}-4&EqBH# zK0o&Cj+<1lpae4a==SdNS$;1Td%P&j3f7K2^qN_myfnEa(H}%D+bolamFwN$pI@d` zcY~t~zhH=k=Nc<=aF`DiBCXWEE`-g~5uT`dLpIuYCec_ufRkGqi^Rk(WP?XM=H|_R z!kV*w7>gC@F8Q17ZuoiLeSmHyH$Z<+I%8s&_jl3y2h3&mzZjW)sBaW^ZtJ@aF_Yz} z_mz$5e@)IWq+r5H?e#FUte!f%Vuu?EW|(;7YPVvFx42P}YTvosS$rSU0x<~i1b>h) zkxSuA!Y9rgJ$Fqe^@5|t4)^?yB(vr`N`2@|ItFxZ!Bg7RIfD&Vf6n{58fc^xIjKoRX`@6gc=rgN^*2a}CgAY-WpWoTxS??V*-$s;lwuNpd{ zFR5Ck7*4F5q#3_HMaYA5jIw-G*H|J3*2avE`guV2Bp-wVrK+?l<4sfolZ6Dzm^=m6 z1c6GS;wF63QTu4+`gqe}v589$PD|p;u?e5rK0n` z?5J|kCPjK0*R7yLhFbbku|7!T7i3098hqxHgEx8P#0-C&otG@GsK^{;b)y4C9nUs< zDM|M9?M}HNu{BtiTU%h|$;K|XtlsVs*Ms0+yFOA>R!(>l#f>@V@MQ&7Ao&~UxGpD^ zFbPhna!4+yy0t2LMPmR*$|ySnL8 zO-_gJT2`x`ony{MemNUPrL=#?*PVF*Tl~S69>$9M$v9hayonTh`Qn@@l=f|V>ogX#)LgRw1Dxc!sC6E@#nEcp5Df|2d{HSJ0G zSu1gK4~v+J!)AI(U6*Q$gnk*~T$7?0_+FGU(L_24+GXAHg7T@bTo$3k(v@v*jJiDha?V~T zn`nEnKSIlBIiH}*Itz&ZQ#z@b54+?VZA%ls+7s$Kirfox5VjkB?&SW5Yzri6=j`mmLVMr~e<~$n5PpvtCpT%(vEIx1UP%DiX=|)S8Osxx% zO~dctvH^=u=9Z0@=xMN*9SFj(S|BHFyZH^xovzRH+J8QXv2Gz?9Xk2l&t}@xfLjEs zJ5@!c+Y_==Z#sAYwZmnHA#61P4f!1+Y(0Sq8Gf@Z&HNO~yyC$;7CYy-Q(beR?Gk{h z{eCNQ&4c;Ko4D#IW9*OpV*qzXN)N9L^H85ol}pvx#kr=oYyqBilZSL7ZGYri=m

    |gk4Ft{irS)R%4JabzZ*&$$ z@W!_I12S(qs!jiBaPA+Eao0s_dV+S4c1#-Li1dhmV*Uk1Z1hfLi${KBc5g$l{ZjJX zxFz%C?%lZYFpQ;;E2(08$d=vUl=~D+gEs-uoBqAvamy(IOHK&1ZuiVY(68Q};IBLm zm^GwhoDRsGSv8t+in?f}GWZvliOdF_L6X*KsN}wXDCyIy((~C?_x4spz85 z4n5AE=caszUurO^kLK%2$eNd%bBi)ELv6I9lhb+YSA`J~VoGpqiZ#bo~>8-X( zNi-`$8xiwA3p=2K++>ak@X%roP}ud`oGEUgZwBgwp^YEUmTvM;zS_s0**yCKTsf6<5@q=%`c|>Ey#`eMn zqzyF7W7kP}yY+<8FW9EJP#x17(x*2;v7Ait5liF$EosW0NN}^E2J@(vgPL8?fBrK7(Lq*EiUi)l79LIbx&T@`R zIxXYJ47}Mo;j8SLpTn2%L07dG`{Uk%Ipx1$Bu+0? zI$>%K`*vU+A1~NUJPo=1;lqMAq#(w`0@SSeSX?A+clTH;JjZnV48x&=7=E58UZPJE z5myJ+hrauK`j&IwDDLlF(%jFk`6N~PZZs?aJ(^eNMKY(m!j|kB^&1*?xHoD zh_G+)lHuVJo4~1`mixTEAMrI))JokqI8T&Wp?l5O;Z`SB$by{?=u%WBeiubbw7$dS z#eOUuH9f|GY}4pl&Oe)91AUGWLlW=_rMy{j$muc)PbsRQ)Xb>f@&~IhbzpBR*@wyJ z#y}WMvxv}!m}W0>JW)&}3GMo1zVQX0RjJwZ5?_2$$n5eh8H%=w>hYjif5u77cTJBS z*fPdj&FK!$Lgine%X*qr6fbm6?7Wt5+r3!{($?hSp*)FHk-0;guHX`pW#qeaXSN_K*& z7D@hsv(v`kkNT#Vs1jAl-SE=P%+V6{bagT`p0PvD@pza|s3(Bm^U~6s>NHN8R1M`M zvV>lQ1nA9TT4k<8&VmZK>>kr$QO*C9TNmZ~ldRL|ZF5PNkrMvJw>eV!AInym{hdxXL=+XsT`K?cKl_aWg7E!RT2hpDiw^Z+o_5< zZYfGttvK(IR27VPPBfJqq{x@0J{?2lC!k3gI`hERug+q3i){;>@}0@`#KH`Jf1v>8 z^5l~1fXO(oqWO$sQFWzW0@E`(n;Tf`&SSLsR5EA*Ud(0b=)WP0z%k_ZZRB?vsS4^i(H@eXGy|*kK2FJ@LxcYuPs$a6^3I(5>zjTR2`d13%QGmG)a`pNH1Scla1og0d92{$X2t}6Oot=P?jW}il2VDR1N%X+EQ)YZ`mm@ zle&s8&(HFhdN+O$Q#8&8(Mv?MiXG)kGKHw2XYxt`5ZeN%1S4WWE4Ma4Ti7|W4(i0q zv|P=vpehas>-PH$cMBKBY|Md#d5&d{;)Ir*o!!ZRd6mSRA(sT9z}yHZzsL^ zf^SHEu)f#=`pfiLH-BwZKw=~q>>ywQe|w94Tprx*iL%kq24&)Qb?$jj37B{tdFvCa zD0D~)U^J?BY#wZOsH!U*DIf^h+JCk*(szyoRb+NG>;r=m*=?Si$R8Mi$qH`O^9kVT zA5g`+X0)79zM!1e&XyE9hNrp){ZHF5p?cv@exK$)^r6_VK$J0hpgySj

    6A1@$qC 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

    RPVEMRpmyUY_2B3R3ou;~q7N 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 G|NjAX=Qhg# literal 0 HcmV?d00001 diff --git a/vendor/phpoffice/phpspreadsheet/docs/topics/images/08-column-width.png b/vendor/phpoffice/phpspreadsheet/docs/topics/images/08-column-width.png new file mode 100644 index 0000000000000000000000000000000000000000..1419943413d6f871c495de1fda642ebd7a9f05c2 GIT binary patch literal 14985 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 F{{RpA=L-M; literal 0 HcmV?d00001 diff --git a/vendor/phpoffice/phpspreadsheet/docs/topics/images/08-page-setup-margins.png b/vendor/phpoffice/phpspreadsheet/docs/topics/images/08-page-setup-margins.png new file mode 100644 index 0000000000000000000000000000000000000000..36e07a9b69aba676926a0dac959a91b399ea5617 GIT binary patch literal 125173 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

    NE6V3$KNym{v~)U$gt7Z19s>1cj@gN(q8v(I( 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 LNkvXXu0mjfu6ewA literal 0 HcmV?d00001 diff --git a/vendor/phpoffice/phpspreadsheet/docs/topics/images/08-page-setup-scaling-options.png b/vendor/phpoffice/phpspreadsheet/docs/topics/images/08-page-setup-scaling-options.png new file mode 100644 index 0000000000000000000000000000000000000000..80fb5f00c391d985a94515feb07d92c8a7bfdbad GIT binary patch literal 24136 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⁢+M*--LFW2^S986nA3(;Lx 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'127.0.0.1', 6379); +$pool = new \Cache\Adapter\Redis\RedisCachePool($client); +$simpleCache = new \Cache\Bridge\SimpleCache\SimpleCacheBridge($pool); + +\PhpOffice\PhpSpreadsheet\Settings::setCache($simpleCache); +``` + +### Memcache + +Require the packages into your project: + +```sh +composer require cache/simple-cache-bridge cache/memcache-adapter +``` + +Configure PhpSpreadsheet with something like: + +```php +$client = new \Memcache(); +$client->connect('localhost', 11211); +$pool = new \Cache\Adapter\Memcache\MemcacheCachePool($client); +$simpleCache = new \Cache\Bridge\SimpleCache\SimpleCacheBridge($pool); + +\PhpOffice\PhpSpreadsheet\Settings::setCache($simpleCache); +``` diff --git a/vendor/phpoffice/phpspreadsheet/docs/topics/migration-from-PHPExcel.md b/vendor/phpoffice/phpspreadsheet/docs/topics/migration-from-PHPExcel.md new file mode 100644 index 0000000..011b377 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/docs/topics/migration-from-PHPExcel.md @@ -0,0 +1,433 @@ +# Migration from PHPExcel + +PhpSpreadsheet introduced many breaking changes by introducing +namespaces and renaming some classes. To help you migrate existing +project, a tool was written to replace all references to PHPExcel +classes to their new names. But there are also manual changes that +need to be done. + +## Automated tool + +The tool is included in PhpSpreadsheet. It scans recursively all files +and directories, starting from the current directory. Assuming it was +installed with composer, it can be run like so: + +``` sh +cd /project/to/migrate/src +php /project/to/migrate/vendor/phpoffice/phpspreadsheet/bin/migrate-from-phpexcel +``` + +**Important** The tool will irreversibly modify your sources, be sure to +backup everything, and double check the result before committing. + +## Manual changes + +In addition to automated changes, a few things need to be migrated manually. + +### Renamed readers and writers + +When using `IOFactory::createReader()`, `IOFactory::createWriter()` and +`IOFactory::identify()`, the reader/writer short names are used. Those were +changed, along as their corresponding class, to remove ambiguity: + +Before | After +-----------------|--------- +`'CSV'` | `'Csv'` +`'Excel2003XML'` | `'Xml'` +`'Excel2007'` | `'Xlsx'` +`'Excel5'` | `'Xls'` +`'Gnumeric'` | `'Gnumeric'` +`'HTML'` | `'Html'` +`'OOCalc'` | `'Ods'` +`'OpenDocument'` | `'Ods'` +`'PDF'` | `'Pdf'` +`'SYLK'` | `'Slk'` + +### Simplified IOFactory + +The following methods : + +- `PHPExcel_IOFactory::getSearchLocations()` +- `PHPExcel_IOFactory::setSearchLocations()` +- `PHPExcel_IOFactory::addSearchLocation()` + +were replaced by `IOFactory::registerReader()` and `IOFactory::registerWriter()`. That means +IOFactory now relies on classes autoloading. + +Before: + +```php +\PHPExcel_IOFactory::addSearchLocation($type, $location, $classname); +``` + +After: + +```php +\PhpOffice\PhpSpreadsheet\IOFactory::registerReader($type, $classname); +``` + +### Removed deprecated things + +#### Worksheet::duplicateStyleArray() + +``` php +// Before +$worksheet->duplicateStyleArray($styles, $range, $advanced); + +// After +$worksheet->getStyle($range)->applyFromArray($styles, $advanced); +``` + +#### DataType::dataTypeForValue() + +``` php +// Before +DataType::dataTypeForValue($value); + +// After +DefaultValueBinder::dataTypeForValue($value); +``` + +#### Conditional::getCondition() + +``` php +// Before +$conditional->getCondition(); + +// After +$conditional->getConditions()[0]; +``` + +#### Conditional::setCondition() + +``` php +// Before +$conditional->setCondition($value); + +// After +$conditional->setConditions($value); +``` + +#### Worksheet::getDefaultStyle() + +``` php +// Before +$worksheet->getDefaultStyle(); + +// After +$worksheet->getParent()->getDefaultStyle(); +``` + +#### Worksheet::setDefaultStyle() + +``` php +// Before +$worksheet->setDefaultStyle($value); + +// After +$worksheet->getParent()->getDefaultStyle()->applyFromArray([ + 'font' => [ + 'name' => $pValue->getFont()->getName(), + 'size' => $pValue->getFont()->getSize(), + ], +]); + +``` + +#### Worksheet::setSharedStyle() + +``` php +// Before +$worksheet->setSharedStyle($sharedStyle, $range); + +// After +$worksheet->duplicateStyle($sharedStyle, $range); +``` + +#### Worksheet::getSelectedCell() + +``` php +// Before +$worksheet->getSelectedCell(); + +// After +$worksheet->getSelectedCells(); +``` + +#### Writer\Xls::setTempDir() + +``` php +// Before +$writer->setTempDir(); + +// After, there is no way to set temporary storage directory anymore +``` + +### Autoloader + +The class `PHPExcel_Autoloader` was removed entirely and is replaced by composer +autoloading mechanism. + +### Writing PDF + +PDF libraries must be installed via composer. And the following methods were removed +and are replaced by `IOFactory::registerWriter()` instead: + +- `PHPExcel_Settings::getPdfRenderer()` +- `PHPExcel_Settings::setPdfRenderer()` +- `PHPExcel_Settings::getPdfRendererName()` +- `PHPExcel_Settings::setPdfRendererName()` + +Before: + +```php +\PHPExcel_Settings::setPdfRendererName(PHPExcel_Settings::PDF_RENDERER_MPDF); +\PHPExcel_Settings::setPdfRenderer($somePath); +$writer = \PHPExcel_IOFactory::createWriter($spreadsheet, 'PDF'); +``` + +After: + +```php +$writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Mpdf'); + +// Or alternatively +\PhpOffice\PhpSpreadsheet\IOFactory::registerWriter('Pdf', \PhpOffice\PhpSpreadsheet\Writer\Pdf\Mpdf::class); +$writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Pdf'); + +// Or alternatively +$writer = new \PhpOffice\PhpSpreadsheet\Writer\Pdf\Mpdf($spreadsheet); +``` + +### Rendering charts + +When rendering charts for HTML or PDF outputs, the process was also simplified. And while +JpGraph support is still available, it is unfortunately not up to date for latest PHP versions +and it will generate various warnings. + +If you rely on this feature, please consider +contributing either patches to JpGraph or another `IRenderer` implementation (a good +candidate might be [CpChart](https://github.com/szymach/c-pchart)). + +Before: + +```php +$rendererName = \PHPExcel_Settings::CHART_RENDERER_JPGRAPH; +$rendererLibrary = 'jpgraph3.5.0b1/src/'; +$rendererLibraryPath = '/php/libraries/Charts/' . $rendererLibrary; + +\PHPExcel_Settings::setChartRenderer($rendererName, $rendererLibraryPath); +``` + +After: + +Require the dependency via composer: + +```sh +composer require jpgraph/jpgraph +``` + +And then: + +```php +Settings::setChartRenderer(\PhpOffice\PhpSpreadsheet\Chart\Renderer\JpGraph::class); +``` + +### PclZip and ZipArchive + +Support for PclZip were dropped in favor of the more complete and modern +[PHP extension ZipArchive](https://php.net/manual/en/book.zip.php). +So the following were removed: + +- `PclZip` +- `PHPExcel_Settings::setZipClass()` +- `PHPExcel_Settings::getZipClass()` +- `PHPExcel_Shared_ZipArchive` +- `PHPExcel_Shared_ZipStreamWrapper` + +### Cell caching + +Cell caching was heavily refactored to leverage +[PSR-16](https://www.php-fig.org/psr/psr-16/). That means most classes +related to that feature were removed: + +- `PHPExcel_CachedObjectStorage_APC` +- `PHPExcel_CachedObjectStorage_DiscISAM` +- `PHPExcel_CachedObjectStorage_ICache` +- `PHPExcel_CachedObjectStorage_Igbinary` +- `PHPExcel_CachedObjectStorage_Memcache` +- `PHPExcel_CachedObjectStorage_Memory` +- `PHPExcel_CachedObjectStorage_MemoryGZip` +- `PHPExcel_CachedObjectStorage_MemorySerialized` +- `PHPExcel_CachedObjectStorage_PHPTemp` +- `PHPExcel_CachedObjectStorage_SQLite` +- `PHPExcel_CachedObjectStorage_SQLite3` +- `PHPExcel_CachedObjectStorage_Wincache` + +In addition to that, `\PhpOffice\PhpSpreadsheet::getCellCollection()` was renamed +to `\PhpOffice\PhpSpreadsheet::getCoordinates()` and +`\PhpOffice\PhpSpreadsheet::getCellCacheController()` to +`\PhpOffice\PhpSpreadsheet::getCellCollection()` for clarity. + +Refer to [the new documentation](./memory_saving.md) to see how to migrate. + +### Dropped conditionally returned cell + +For all the following methods, it is no more possible to change the type of +returned value. It always return the Worksheet and never the Cell or Rule: + +- Worksheet::setCellValue() +- Worksheet::setCellValueByColumnAndRow() +- Worksheet::setCellValueExplicit() +- Worksheet::setCellValueExplicitByColumnAndRow() +- Worksheet::addRule() + +Migration would be similar to: + +``` php +// Before +$cell = $worksheet->setCellValue('A1', 'value', true); + +// After +$cell = $worksheet->getCell('A1')->setValue('value'); +``` + +### Standardized keys for styling + +Array keys used for styling have been standardized for a more coherent experience. +It now uses the same wording and casing as the getter and setter: + +```php +// Before +$style = [ + 'numberformat' => [ + 'code' => NumberFormat::FORMAT_CURRENCY_EUR_SIMPLE, + ], + 'font' => [ + 'strike' => true, + 'superScript' => true, + 'subScript' => true, + ], + 'alignment' => [ + 'rotation' => 90, + 'readorder' => Alignment::READORDER_RTL, + 'wrap' => true, + ], + 'borders' => [ + 'diagonaldirection' => Borders::DIAGONAL_BOTH, + 'allborders' => [ + 'style' => Border::BORDER_THIN, + ], + ], + 'fill' => [ + 'type' => Fill::FILL_GRADIENT_LINEAR, + 'startcolor' => [ + 'argb' => 'FFA0A0A0', + ], + 'endcolor' => [ + 'argb' => 'FFFFFFFF', + ], + ], +]; + +// After +$style = [ + 'numberFormat' => [ + 'formatCode' => NumberFormat::FORMAT_CURRENCY_EUR_SIMPLE, + ], + 'font' => [ + 'strikethrough' => true, + 'superscript' => true, + 'subscript' => true, + ], + 'alignment' => [ + 'textRotation' => 90, + 'readOrder' => Alignment::READORDER_RTL, + 'wrapText' => true, + ], + 'borders' => [ + 'diagonalDirection' => Borders::DIAGONAL_BOTH, + 'allBorders' => [ + 'borderStyle' => Border::BORDER_THIN, + ], + ], + 'fill' => [ + 'fillType' => Fill::FILL_GRADIENT_LINEAR, + 'startColor' => [ + 'argb' => 'FFA0A0A0', + ], + 'endColor' => [ + 'argb' => 'FFFFFFFF', + ], + ], +]; +``` + +### Dedicated class to manipulate coordinates + +Methods to manipulate coordinates that used to exists in `PHPExcel_Cell` were extracted +to a dedicated new class `\PhpOffice\PhpSpreadsheet\Cell\Coordinate`. The methods are: + +- `absoluteCoordinate()` +- `absoluteReference()` +- `buildRange()` +- `columnIndexFromString()` +- `coordinateFromString()` +- `extractAllCellReferencesInRange()` +- `getRangeBoundaries()` +- `mergeRangesInCollection()` +- `rangeBoundaries()` +- `rangeDimension()` +- `splitRange()` +- `stringFromColumnIndex()` + +### Column index based on 1 + +Column indexes are now based on 1. So column `A` is the index `1`. This is consistent +with rows starting at 1 and Excel function `COLUMN()` that returns `1` for column `A`. +So the code must be adapted with something like: + +```php +// Before +$cell = $worksheet->getCellByColumnAndRow($column, $row); + +for ($column = 0; $column < $max; $column++) { + $worksheet->setCellValueByColumnAndRow($column, $row, 'value ' . $column); +} + +// After +$cell = $worksheet->getCellByColumnAndRow($column + 1, $row); + +for ($column = 1; $column <= $max; $column++) { + $worksheet->setCellValueByColumnAndRow($column, $row, 'value ' . $column); +} +``` + +All the following methods are affected: + +- `PHPExcel_Worksheet::cellExistsByColumnAndRow()` +- `PHPExcel_Worksheet::freezePaneByColumnAndRow()` +- `PHPExcel_Worksheet::getCellByColumnAndRow()` +- `PHPExcel_Worksheet::getColumnDimensionByColumn()` +- `PHPExcel_Worksheet::getCommentByColumnAndRow()` +- `PHPExcel_Worksheet::getStyleByColumnAndRow()` +- `PHPExcel_Worksheet::insertNewColumnBeforeByIndex()` +- `PHPExcel_Worksheet::mergeCellsByColumnAndRow()` +- `PHPExcel_Worksheet::protectCellsByColumnAndRow()` +- `PHPExcel_Worksheet::removeColumnByIndex()` +- `PHPExcel_Worksheet::setAutoFilterByColumnAndRow()` +- `PHPExcel_Worksheet::setBreakByColumnAndRow()` +- `PHPExcel_Worksheet::setCellValueByColumnAndRow()` +- `PHPExcel_Worksheet::setCellValueExplicitByColumnAndRow()` +- `PHPExcel_Worksheet::setSelectedCellByColumnAndRow()` +- `PHPExcel_Worksheet::stringFromColumnIndex()` +- `PHPExcel_Worksheet::unmergeCellsByColumnAndRow()` +- `PHPExcel_Worksheet::unprotectCellsByColumnAndRow()` +- `PHPExcel_Worksheet_PageSetup::addPrintAreaByColumnAndRow()` +- `PHPExcel_Worksheet_PageSetup::setPrintAreaByColumnAndRow()` + +### Removed default values + +Default values for many methods were removed when it did not make sense. Typically, +setter methods should not have default values. For a complete list of methods and +their original default values, see [that commit](https://github.com/PHPOffice/PhpSpreadsheet/commit/033a4bdad56340795a5bf7ec3c8a2fde005cda24). diff --git a/vendor/phpoffice/phpspreadsheet/docs/topics/reading-and-writing-to-file.md b/vendor/phpoffice/phpspreadsheet/docs/topics/reading-and-writing-to-file.md new file mode 100644 index 0000000..3b6a037 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/docs/topics/reading-and-writing-to-file.md @@ -0,0 +1,928 @@ +# Reading and writing to file + +As you already know from the [architecture](./architecture.md#readers-and-writers), +reading and writing to a +persisted storage is not possible using the base PhpSpreadsheet classes. +For this purpose, PhpSpreadsheet provides readers and writers, which are +implementations of `\PhpOffice\PhpSpreadsheet\Reader\IReader` and +`\PhpOffice\PhpSpreadsheet\Writer\IWriter`. + +## \PhpOffice\PhpSpreadsheet\IOFactory + +The PhpSpreadsheet API offers multiple methods to create a +`\PhpOffice\PhpSpreadsheet\Reader\IReader` or +`\PhpOffice\PhpSpreadsheet\Writer\IWriter` instance: + +Direct creation via `\PhpOffice\PhpSpreadsheet\IOFactory`. All examples +underneath demonstrate the direct creation method. Note that you can +also use the `\PhpOffice\PhpSpreadsheet\IOFactory` class to do this. + +### Creating `\PhpOffice\PhpSpreadsheet\Reader\IReader` using `\PhpOffice\PhpSpreadsheet\IOFactory` + +There are 2 methods for reading in a file into PhpSpreadsheet: using +automatic file type resolving or explicitly. + +Automatic file type resolving checks the different +`\PhpOffice\PhpSpreadsheet\Reader\IReader` distributed with +PhpSpreadsheet. If one of them can load the specified file name, the +file is loaded using that `\PhpOffice\PhpSpreadsheet\Reader\IReader`. +Explicit mode requires you to specify which +`\PhpOffice\PhpSpreadsheet\Reader\IReader` should be used. + +You can create a `\PhpOffice\PhpSpreadsheet\Reader\IReader` instance using +`\PhpOffice\PhpSpreadsheet\IOFactory` in automatic file type resolving +mode using the following code sample: + +``` php +$spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load("05featuredemo.xlsx"); +``` + +A typical use of this feature is when you need to read files uploaded by +your users, and you don’t know whether they are uploading xls or xlsx +files. + +If you need to set some properties on the reader, (e.g. to only read +data, see more about this later), then you may instead want to use this +variant: + +``` php +$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReaderForFile("05featuredemo.xlsx"); +$reader->setReadDataOnly(true); +$reader->load("05featuredemo.xlsx"); +``` + +You can create a `\PhpOffice\PhpSpreadsheet\Reader\IReader` instance using +`\PhpOffice\PhpSpreadsheet\IOFactory` in explicit mode using the following +code sample: + +``` php +$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader("Xlsx"); +$spreadsheet = $reader->load("05featuredemo.xlsx"); +``` + +Note that automatic type resolving mode is slightly slower than explicit +mode. + +### Creating `\PhpOffice\PhpSpreadsheet\Writer\IWriter` using `\PhpOffice\PhpSpreadsheet\IOFactory` + +You can create a `\PhpOffice\PhpSpreadsheet\Writer\IWriter` instance using +`\PhpOffice\PhpSpreadsheet\IOFactory`: + +``` php +$writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, "Xlsx"); +$writer->save("05featuredemo.xlsx"); +``` + +## Excel 2007 (SpreadsheetML) file format + +Xlsx file format is the main file format of PhpSpreadsheet. It allows +outputting the in-memory spreadsheet to a .xlsx file. + +### \PhpOffice\PhpSpreadsheet\Reader\Xlsx + +#### Reading a spreadsheet + +You can read an .xlsx file using the following code: + +``` php +$reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx(); +$spreadsheet = $reader->load("05featuredemo.xlsx"); +``` + +#### Read data only + +You can set the option setReadDataOnly on the reader, to instruct the +reader to ignore styling, data validation, … and just read cell data: + +``` php +$reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx(); +$reader->setReadDataOnly(true); +$spreadsheet = $reader->load("05featuredemo.xlsx"); +``` + +#### Read specific sheets only + +You can set the option setLoadSheetsOnly on the reader, to instruct the +reader to only load the sheets with a given name: + +``` php +$reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx(); +$reader->setLoadSheetsOnly(["Sheet 1", "My special sheet"]); +$spreadsheet = $reader->load("05featuredemo.xlsx"); +``` + +#### Read specific cells only + +You can set the option setReadFilter on the reader, to instruct the +reader to only load the cells which match a given rule. A read filter +can be any class which implements +`\PhpOffice\PhpSpreadsheet\Reader\IReadFilter`. By default, all cells are +read using the `\PhpOffice\PhpSpreadsheet\Reader\DefaultReadFilter`. + +The following code will only read row 1 and rows 20 – 30 of any sheet in +the Excel file: + +``` php +class MyReadFilter implements \PhpOffice\PhpSpreadsheet\Reader\IReadFilter { + + public function readCell($column, $row, $worksheetName = '') { + // Read title row and rows 20 - 30 + if ($row == 1 || ($row >= 20 && $row <= 30)) { + return true; + } + return false; + } +} + +$reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx(); +$reader->setReadFilter( new MyReadFilter() ); +$spreadsheet = $reader->load("06largescale.xlsx"); +``` + +### \PhpOffice\PhpSpreadsheet\Writer\Xlsx + +#### Writing a spreadsheet + +You can write an .xlsx file using the following code: + +``` php +$writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($spreadsheet); +$writer->save("05featuredemo.xlsx"); +``` + +#### Formula pre-calculation + +By default, this writer pre-calculates all formulas in the spreadsheet. +This can be slow on large spreadsheets, and maybe even unwanted. You can +however disable formula pre-calculation: + +``` php +$writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($spreadsheet); +$writer->setPreCalculateFormulas(false); +$writer->save("05featuredemo.xlsx"); +``` + +#### Office 2003 compatibility pack + +Because of a bug in the Office2003 compatibility pack, there can be some +small issues when opening Xlsx spreadsheets (mostly related to formula +calculation). You can enable Office2003 compatibility with the following +code: + + $writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($spreadsheet); + $writer->setOffice2003Compatibility(true); + $writer->save("05featuredemo.xlsx"); + +**Office2003 compatibility option should only be used when needed** because +it disables several Office2007 file format options, resulting in a +lower-featured Office2007 spreadsheet. + +## Excel 5 (BIFF) file format + +Xls file format is the old Excel file format, implemented in +PhpSpreadsheet to provide a uniform manner to create both .xlsx and .xls +files. It is basically a modified version of [PEAR +Spreadsheet\_Excel\_Writer](https://pear.php.net/package/Spreadsheet_Excel_Writer), +although it has been extended and has fewer limitations and more +features than the old PEAR library. This can read all BIFF versions that +use OLE2: BIFF5 (introduced with office 95) through BIFF8, but cannot +read earlier versions. + +Xls file format will not be developed any further, it just provides an +additional file format for PhpSpreadsheet. + +**Excel5 (BIFF) limitations** Please note that BIFF file format has some +limits regarding to styling cells and handling large spreadsheets via +PHP. + +### \PhpOffice\PhpSpreadsheet\Reader\Xls + +#### Reading a spreadsheet + +You can read an .xls file using the following code: + +``` php +$reader = new \PhpOffice\PhpSpreadsheet\Reader\Xls(); +$spreadsheet = $reader->load("05featuredemo.xls"); +``` + +#### Read data only + +You can set the option setReadDataOnly on the reader, to instruct the +reader to ignore styling, data validation, … and just read cell data: + +``` php +$reader = new \PhpOffice\PhpSpreadsheet\Reader\Xls(); +$reader->setReadDataOnly(true); +$spreadsheet = $reader->load("05featuredemo.xls"); +``` + +#### Read specific sheets only + +You can set the option setLoadSheetsOnly on the reader, to instruct the +reader to only load the sheets with a given name: + +``` php +$reader = new \PhpOffice\PhpSpreadsheet\Reader\Xls(); +$reader->setLoadSheetsOnly(["Sheet 1", "My special sheet"]); +$spreadsheet = $reader->load("05featuredemo.xls"); +``` + +#### Read specific cells only + +You can set the option setReadFilter on the reader, to instruct the +reader to only load the cells which match a given rule. A read filter +can be any class which implements +`\PhpOffice\PhpSpreadsheet\Reader\IReadFilter`. By default, all cells are +read using the `\PhpOffice\PhpSpreadsheet\Reader\DefaultReadFilter`. + +The following code will only read row 1 and rows 20 to 30 of any sheet +in the Excel file: + +``` php +class MyReadFilter implements \PhpOffice\PhpSpreadsheet\Reader\IReadFilter { + + public function readCell($column, $row, $worksheetName = '') { + // Read title row and rows 20 - 30 + if ($row == 1 || ($row >= 20 && $row <= 30)) { + return true; + } + return false; + } +} + +$reader = new \PhpOffice\PhpSpreadsheet\Reader\Xls(); +$reader->setReadFilter( new MyReadFilter() ); +$spreadsheet = $reader->load("06largescale.xls"); +``` + +### \PhpOffice\PhpSpreadsheet\Writer\Xls + +#### Writing a spreadsheet + +You can write an .xls file using the following code: + +``` php +$writer = new \PhpOffice\PhpSpreadsheet\Writer\Xls($spreadsheet); +$writer->save("05featuredemo.xls"); +``` + +## Excel 2003 XML file format + +Excel 2003 XML file format is a file format which can be used in older +versions of Microsoft Excel. + +**Excel 2003 XML limitations** Please note that Excel 2003 XML format +has some limits regarding to styling cells and handling large +spreadsheets via PHP. + +### \PhpOffice\PhpSpreadsheet\Reader\Xml + +#### Reading a spreadsheet + +You can read an Excel 2003 .xml file using the following code: + +``` php +$reader = new \PhpOffice\PhpSpreadsheet\Reader\Xml(); +$spreadsheet = $reader->load("05featuredemo.xml"); +``` + +#### Read specific cells only + +You can set the option setReadFilter on the reader, to instruct the +reader to only load the cells which match a given rule. A read filter +can be any class which implements +`\PhpOffice\PhpSpreadsheet\Reader\IReadFilter`. By default, all cells are +read using the `\PhpOffice\PhpSpreadsheet\Reader\DefaultReadFilter`. + +The following code will only read row 1 and rows 20 to 30 of any sheet +in the Excel file: + +``` php +class MyReadFilter implements \PhpOffice\PhpSpreadsheet\Reader\IReadFilter { + + public function readCell($column, $row, $worksheetName = '') { + // Read title row and rows 20 - 30 + if ($row == 1 || ($row >= 20 && $row <= 30)) { + return true; + } + return false; + } + +} + +$reader = new \PhpOffice\PhpSpreadsheet\Reader\Xml(); +$reader->setReadFilter( new MyReadFilter() ); +$spreadsheet = $reader->load("06largescale.xml"); +``` + +## Symbolic LinK (SYLK) + +Symbolic Link (SYLK) is a Microsoft file format typically used to +exchange data between applications, specifically spreadsheets. SYLK +files conventionally have a .slk suffix. Composed of only displayable +ANSI characters, it can be easily created and processed by other +applications, such as databases. + +**SYLK limitations** Please note that SYLK file format has some limits +regarding to styling cells and handling large spreadsheets via PHP. + +### \PhpOffice\PhpSpreadsheet\Reader\Slk + +#### Reading a spreadsheet + +You can read an .slk file using the following code: + +``` php +$reader = new \PhpOffice\PhpSpreadsheet\Reader\Slk(); +$spreadsheet = $reader->load("05featuredemo.slk"); +``` + +#### Read specific cells only + +You can set the option setReadFilter on the reader, to instruct the +reader to only load the cells which match a given rule. A read filter +can be any class which implements +`\PhpOffice\PhpSpreadsheet\Reader\IReadFilter`. By default, all cells are +read using the `\PhpOffice\PhpSpreadsheet\Reader\DefaultReadFilter`. + +The following code will only read row 1 and rows 20 to 30 of any sheet +in the SYLK file: + +``` php +class MyReadFilter implements \PhpOffice\PhpSpreadsheet\Reader\IReadFilter { + + public function readCell($column, $row, $worksheetName = '') { + // Read title row and rows 20 - 30 + if ($row == 1 || ($row >= 20 && $row <= 30)) { + return true; + } + return false; + } + +} + +$reader = new \PhpOffice\PhpSpreadsheet\Reader\Slk(); +$reader->setReadFilter( new MyReadFilter() ); +$spreadsheet = $reader->load("06largescale.slk"); +``` + +## Open/Libre Office (.ods) + +Open Office or Libre Office .ods files are the standard file format for +Open Office or Libre Office Calc files. + +### \PhpOffice\PhpSpreadsheet\Reader\Ods + +#### Reading a spreadsheet + +You can read an .ods file using the following code: + +``` php +$reader = new \PhpOffice\PhpSpreadsheet\Reader\Ods(); +$spreadsheet = $reader->load("05featuredemo.ods"); +``` + +#### Read specific cells only + +You can set the option setReadFilter on the reader, to instruct the +reader to only load the cells which match a given rule. A read filter +can be any class which implements +`\PhpOffice\PhpSpreadsheet\Reader\IReadFilter`. By default, all cells are +read using the `\PhpOffice\PhpSpreadsheet\Reader\DefaultReadFilter`. + +The following code will only read row 1 and rows 20 to 30 of any sheet +in the Calc file: + +``` php +class MyReadFilter implements \PhpOffice\PhpSpreadsheet\Reader\IReadFilter { + + public function readCell($column, $row, $worksheetName = '') { + // Read title row and rows 20 - 30 + if ($row == 1 || ($row >= 20 && $row <= 30)) { + return true; + } + return false; + } + +} + +$reader = new PhpOffice\PhpSpreadsheet\Reader\Ods(); +$reader->setReadFilter( new MyReadFilter() ); +$spreadsheet = $reader->load("06largescale.ods"); +``` + +## CSV (Comma Separated Values) + +CSV (Comma Separated Values) are often used as an import/export file +format with other systems. PhpSpreadsheet allows reading and writing to +CSV files. + +**CSV limitations** Please note that CSV file format has some limits +regarding to styling cells, number formatting, ... + +### \PhpOffice\PhpSpreadsheet\Reader\Csv + +#### Reading a CSV file + +You can read a .csv file using the following code: + +``` php +$reader = new \PhpOffice\PhpSpreadsheet\Reader\Csv(); +$spreadsheet = $reader->load("sample.csv"); +``` + +#### Setting CSV options + +Often, CSV files are not really "comma separated", or use semicolon (`;`) +as a separator. You can instruct +`\PhpOffice\PhpSpreadsheet\Reader\Csv` some options before reading a CSV +file. + +The separator will be auto-detected, so in most cases it should not be necessary +to specify it. But in cases where auto-detection does not fit the use-case, then +it can be set manually. + +Note that `\PhpOffice\PhpSpreadsheet\Reader\Csv` by default assumes that +the loaded CSV file is UTF-8 encoded. If you are reading CSV files that +were created in Microsoft Office Excel the correct input encoding may +rather be Windows-1252 (CP1252). Always make sure that the input +encoding is set appropriately. + +``` php +$reader = new \PhpOffice\PhpSpreadsheet\Reader\Csv(); +$reader->setInputEncoding('CP1252'); +$reader->setDelimiter(';'); +$reader->setEnclosure(''); +$reader->setSheetIndex(0); + +$spreadsheet = $reader->load("sample.csv"); +``` + +#### Read a specific worksheet + +CSV files can only contain one worksheet. Therefore, you can specify +which sheet to read from CSV: + +``` php +$reader->setSheetIndex(0); +``` + +#### Read into existing spreadsheet + +When working with CSV files, it might occur that you want to import CSV +data into an existing `Spreadsheet` object. The following code loads a +CSV file into an existing `$spreadsheet` containing some sheets, and +imports onto the 6th sheet: + +``` php +$reader = new \PhpOffice\PhpSpreadsheet\Reader\Csv(); +$reader->setDelimiter(';'); +$reader->setEnclosure(''); +$reader->setSheetIndex(5); + +$reader->loadIntoExisting("05featuredemo.csv", $spreadsheet); +``` + +### \PhpOffice\PhpSpreadsheet\Writer\Csv + +#### Writing a CSV file + +You can write a .csv file using the following code: + +``` php +$writer = new \PhpOffice\PhpSpreadsheet\Writer\Csv($spreadsheet); +$writer->save("05featuredemo.csv"); +``` + +#### Setting CSV options + +Often, CSV files are not really "comma separated", or use semicolon (`;`) +as a separator. You can instruct +`\PhpOffice\PhpSpreadsheet\Writer\Csv` some options before writing a CSV +file: + +``` php +$writer = new \PhpOffice\PhpSpreadsheet\Writer\Csv($spreadsheet); +$writer->setDelimiter(';'); +$writer->setEnclosure(''); +$writer->setLineEnding("\r\n"); +$writer->setSheetIndex(0); + +$writer->save("05featuredemo.csv"); +``` + +#### Write a specific worksheet + +CSV files can only contain one worksheet. Therefore, you can specify +which sheet to write to CSV: + +``` php +$writer->setSheetIndex(0); +``` + +#### Formula pre-calculation + +By default, this writer pre-calculates all formulas in the spreadsheet. +This can be slow on large spreadsheets, and maybe even unwanted. You can +however disable formula pre-calculation: + +``` php +$writer = new \PhpOffice\PhpSpreadsheet\Writer\Csv($spreadsheet); +$writer->setPreCalculateFormulas(false); +$writer->save("05featuredemo.csv"); +``` + +#### Writing UTF-8 CSV files + +A CSV file can be marked as UTF-8 by writing a BOM file header. This can +be enabled by using the following code: + +``` php +$writer = new \PhpOffice\PhpSpreadsheet\Writer\Csv($spreadsheet); +$writer->setUseBOM(true); +$writer->save("05featuredemo.csv"); +``` + +#### Decimal and thousands separators + +If the worksheet you are exporting contains numbers with decimal or +thousands separators then you should think about what characters you +want to use for those before doing the export. + +By default PhpSpreadsheet looks up in the server's locale settings to +decide what characters to use. But to avoid problems it is recommended +to set the characters explicitly as shown below. + +English users will want to use this before doing the export: + +``` php +\PhpOffice\PhpSpreadsheet\Shared\StringHelper::setDecimalSeparator('.'); +\PhpOffice\PhpSpreadsheet\Shared\StringHelper::setThousandsSeparator(','); +``` + +German users will want to use the opposite values. + +``` php +\PhpOffice\PhpSpreadsheet\Shared\StringHelper::setDecimalSeparator(','); +\PhpOffice\PhpSpreadsheet\Shared\StringHelper::setThousandsSeparator('.'); +``` + +Note that the above code sets decimal and thousand separators as global +options. This also affects how HTML and PDF is exported. + +## HTML + +PhpSpreadsheet allows you to read or write a spreadsheet as HTML format, +for quick representation of the data in it to anyone who does not have a +spreadsheet application on their PC, or loading files saved by other +scripts that simply create HTML markup and give it a .xls file +extension. + +**HTML limitations** Please note that HTML file format has some limits +regarding to styling cells, number formatting, ... + +### \PhpOffice\PhpSpreadsheet\Reader\Html + +#### Reading a spreadsheet + +You can read an .html or .htm file using the following code: + +``` php +$reader = new \PhpOffice\PhpSpreadsheet\Reader\Html(); + +$spreadsheet = $reader->load("05featuredemo.html"); +``` + +**HTML limitations** Please note that HTML reader is still experimental +and does not yet support merged cells or nested tables cleanly + +### \PhpOffice\PhpSpreadsheet\Writer\Html + +Please note that `\PhpOffice\PhpSpreadsheet\Writer\Html` only outputs the +first worksheet by default. + +#### Writing a spreadsheet + +You can write a .htm file using the following code: + +``` php +$writer = new \PhpOffice\PhpSpreadsheet\Writer\Html($spreadsheet); + +$writer->save("05featuredemo.htm"); +``` + +#### Write all worksheets + +HTML files can contain one or more worksheets. If you want to write all +sheets into a single HTML file, use the following code: + +``` php +$writer->writeAllSheets(); +``` + +#### Write a specific worksheet + +HTML files can contain one or more worksheets. Therefore, you can +specify which sheet to write to HTML: + +``` php +$writer->setSheetIndex(0); +``` + +#### Setting the images root of the HTML file + +There might be situations where you want to explicitly set the included +images root. For example, instead of: + + ``` html + + ``` + +You might want to see: + +``` html + +``` + +You can use the following code to achieve this result: + +``` php +$writer->setImagesRoot('http://www.example.com'); +``` + +#### Formula pre-calculation + +By default, this writer pre-calculates all formulas in the spreadsheet. +This can be slow on large spreadsheets, and maybe even unwanted. You can +however disable formula pre-calculation: + +``` php +$writer = new \PhpOffice\PhpSpreadsheet\Writer\Html($spreadsheet); +$writer->setPreCalculateFormulas(false); + +$writer->save("05featuredemo.htm"); +``` + +#### Embedding generated HTML in a web page + +There might be a situation where you want to embed the generated HTML in +an existing website. \PhpOffice\PhpSpreadsheet\Writer\Html provides +support to generate only specific parts of the HTML code, which allows +you to use these parts in your website. + +Supported methods: + +- `generateHTMLHeader()` +- `generateStyles()` +- `generateSheetData()` +- `generateHTMLFooter()` + +Here's an example which retrieves all parts independently and merges +them into a resulting HTML page: + +``` php +generateHTMLHeader(); +?> + + +?> + +--> + + +generateSheetData(); +echo $writer->generateHTMLFooter(); +?> +``` + +#### Writing UTF-8 HTML files + +A HTML file can be marked as UTF-8 by writing a BOM file header. This +can be enabled by using the following code: + +``` php +$writer = new \PhpOffice\PhpSpreadsheet\Writer\Html($spreadsheet); +$writer->setUseBOM(true); + +$writer->save("05featuredemo.htm"); +``` + +#### Decimal and thousands separators + +See section `\PhpOffice\PhpSpreadsheet\Writer\Csv` how to control the +appearance of these. + +## PDF + +PhpSpreadsheet allows you to write a spreadsheet into PDF format, for +fast distribution of represented data. + +**PDF limitations** Please note that PDF file format has some limits +regarding to styling cells, number formatting, ... + +### \PhpOffice\PhpSpreadsheet\Writer\Pdf + +PhpSpreadsheet’s PDF Writer is a wrapper for a 3rd-Party PDF Rendering +library such as TCPDF, mPDF or Dompdf. You must now install a PDF +rendering library yourself; but PhpSpreadsheet will work with a number +of different libraries. + +Currently, the following libraries are supported: + +Library | Downloadable from | PhpSpreadsheet writer +--------|-------------------------------------|---------------------- +TCPDF | https://github.com/tecnickcom/tcpdf | Tcpdf +mPDF | https://github.com/mpdf/mpdf | Mpdf +Dompdf | https://github.com/dompdf/dompdf | Dompdf + +The different libraries have different strengths and weaknesses. Some +generate better formatted output than others, some are faster or use +less memory than others, while some generate smaller .pdf files. It is +the developers choice which one they wish to use, appropriate to their +own circumstances. + +You can instantiate a writer with its specific name, like so: + +``` php +$writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Mpdf'); +``` + +Or you can register which writer you are using with a more generic name, +so you don't need to remember which library you chose, only that you want +to write PDF files: + +``` php +$class = \PhpOffice\PhpSpreadsheet\Writer\Pdf\Mpdf::class; +\PhpOffice\PhpSpreadsheet\IOFactory::registerWriter('Pdf', $class); +$writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Pdf'); +``` + +Or you can instantiate directly the writer of your choice like so: + +``` php +$writer = \PhpOffice\PhpSpreadsheet\Writer\Pdf\Mpdf($spreadsheet); +``` + +#### Custom implementation or configuration + +If you need a custom implementation, or custom configuration, of a supported +PDF library. You can extends the PDF library, and the PDF writer like so: + +``` php +class My_Custom_TCPDF extends TCPDF +{ + // ... +} + +class My_Custom_TCPDF_Writer extends \PhpOffice\PhpSpreadsheet\Writer\Pdf\Tcpdf +{ + protected function createExternalWriterInstance($orientation, $unit, $paperSize) + { + $instance = new My_Custom_TCPDF($orientation, $unit, $paperSize); + + // more configuration of $instance + + return $instance; + } +} + +\PhpOffice\PhpSpreadsheet\IOFactory::registerWriter('Pdf', MY_TCPDF_WRITER::class); +``` + +#### Writing a spreadsheet + +Once you have identified the Renderer that you wish to use for PDF +generation, you can write a .pdf file using the following code: + +``` php +$writer = new \PhpOffice\PhpSpreadsheet\Writer\Pdf\Mpdf($spreadsheet); +$writer->save("05featuredemo.pdf"); +``` + +Please note that `\PhpOffice\PhpSpreadsheet\Writer\Pdf` only outputs the +first worksheet by default. + +#### Write all worksheets + +PDF files can contain one or more worksheets. If you want to write all +sheets into a single PDF file, use the following code: + +``` php +$writer->writeAllSheets(); +``` + +#### Write a specific worksheet + +PDF files can contain one or more worksheets. Therefore, you can specify +which sheet to write to PDF: + +``` php +$writer->setSheetIndex(0); +``` + +#### Formula pre-calculation + +By default, this writer pre-calculates all formulas in the spreadsheet. +This can be slow on large spreadsheets, and maybe even unwanted. You can +however disable formula pre-calculation: + +``` php +$writer = new \PhpOffice\PhpSpreadsheet\Writer\Pdf\Mpdf($spreadsheet); +$writer->setPreCalculateFormulas(false); + +$writer->save("05featuredemo.pdf"); +``` + +#### Decimal and thousands separators + +See section `\PhpOffice\PhpSpreadsheet\Writer\Csv` how to control the +appearance of these. + +## Generating Excel files from templates (read, modify, write) + +Readers and writers are the tools that allow you to generate Excel files +from templates. This requires less coding effort than generating the +Excel file from scratch, especially if your template has many styles, +page setup properties, headers etc. + +Here is an example how to open a template file, fill in a couple of +fields and save it again: + +``` php +$spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load('template.xlsx'); + +$worksheet = $spreadsheet->getActiveSheet(); + +$worksheet->getCell('A1')->setValue('John'); +$worksheet->getCell('A2')->setValue('Smith'); + +$writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Xls'); +$writer->save('write.xls'); +``` + +Notice that it is ok to load an xlsx file and generate an xls file. + +## Generating Excel files from HTML content + +If you are generating an Excel file from pre-rendered HTML content you can do so +automatically using the HTML Reader. This is most useful when you are generating +Excel files from web application content that would be downloaded/sent to a user. + +For example: + +```php +$htmlString = ' + + + + + + + + + +
    Hello World
    Hello
    World
    Hello
    World
    '; + +$reader = new \PhpOffice\PhpSpreadsheet\Reader\Html(); +$spreadsheet = $reader->loadFromString($htmlString); + +$writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Xls'); +$writer->save('write.xls'); +``` + +Suppose you have multiple worksheets you'd like created from html. This can be +accomplished as follows. + +```php +$firstHtmlString = ' + + + +
    Hello World
    '; +$secondHtmlString = ' + + + +
    Hello World
    '; + +$reader = new \PhpOffice\PhpSpreadsheet\Reader\Html(); +$spreadsheet = $reader->loadFromString($firstHtmlString); +$reader->setSheetIndex(1); +$spreadhseet = $reader->loadFromString($secondHtmlString, $spreadsheet); + +$writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Xls'); +$writer->save('write.xls'); +``` diff --git a/vendor/phpoffice/phpspreadsheet/docs/topics/reading-files.md b/vendor/phpoffice/phpspreadsheet/docs/topics/reading-files.md new file mode 100644 index 0000000..779082d --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/docs/topics/reading-files.md @@ -0,0 +1,689 @@ +# Reading Files + +## Security + +XML-based formats such as OfficeOpen XML, Excel2003 XML, OASIS and +Gnumeric are susceptible to XML External Entity Processing (XXE) +injection attacks when reading spreadsheet files. This can lead to: + +- Disclosure whether a file is existent +- Server Side Request Forgery +- Command Execution (depending on the installed PHP wrappers) + +To prevent this, by default every XML-based Reader looks for XML +entities declared inside the DOCTYPE and if any is found an exception +is raised. + +Read more [about of XXE injection](https://websec.io/2012/08/27/Preventing-XXE-in-PHP.html). + +## Loading a Spreadsheet File + +The simplest way to load a workbook file is to let PhpSpreadsheet's IO +Factory identify the file type and load it, calling the static `load()` +method of the `\PhpOffice\PhpSpreadsheet\IOFactory` class. + +``` php +$inputFileName = './sampleData/example1.xls'; + +/** Load $inputFileName to a Spreadsheet Object **/ +$spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($inputFileName); +``` + +See `samples/Reader/01_Simple_file_reader_using_IOFactory.php` for a working +example of this code. + +The `load()` method will attempt to identify the file type, and +instantiate a loader for that file type; using it to load the file and +store the data and any formatting in a `Spreadsheet` object. + +The method makes an initial guess at the loader to instantiate based on +the file extension; but will test the file before actually executing the +load: so if (for example) the file is actually a CSV file or contains +HTML markup, but that has been given a .xls extension (quite a common +practise), it will reject the Xls loader that it would normally use for +a .xls file; and test the file using the other loaders until it finds +the appropriate loader, and then use that to read the file. + +While easy to implement in your code, and you don't need to worry about +the file type; this isn't the most efficient method to load a file; and +it lacks the flexibility to configure the loader in any way before +actually reading the file into a `Spreadsheet` object. + +## Creating a Reader and Loading a Spreadsheet File + +If you know the file type of the spreadsheet file that you need to load, +you can instantiate a new reader object for that file type, then use the +reader's `load()` method to read the file to a `Spreadsheet` object. It is +possible to instantiate the reader objects for each of the different +supported filetype by name. However, you may get unpredictable results +if the file isn't of the right type (e.g. it is a CSV with an extension +of .xls), although this type of exception should normally be trapped. + +``` php +$inputFileName = './sampleData/example1.xls'; + +/** Create a new Xls Reader **/ +$reader = new \PhpOffice\PhpSpreadsheet\Reader\Xls(); +// $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx(); +// $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xml(); +// $reader = new \PhpOffice\PhpSpreadsheet\Reader\Ods(); +// $reader = new \PhpOffice\PhpSpreadsheet\Reader\Slk(); +// $reader = new \PhpOffice\PhpSpreadsheet\Reader\Gnumeric(); +// $reader = new \PhpOffice\PhpSpreadsheet\Reader\Csv(); +/** Load $inputFileName to a Spreadsheet Object **/ +$spreadsheet = $reader->load($inputFileName); +``` + +See `samples/Reader/02_Simple_file_reader_using_a_specified_reader.php` +for a working example of this code. + +Alternatively, you can use the IO Factory's `createReader()` method to +instantiate the reader object for you, simply telling it the file type +of the reader that you want instantiating. + +``` php +$inputFileType = 'Xls'; +// $inputFileType = 'Xlsx'; +// $inputFileType = 'Xml'; +// $inputFileType = 'Ods'; +// $inputFileType = 'Slk'; +// $inputFileType = 'Gnumeric'; +// $inputFileType = 'Csv'; +$inputFileName = './sampleData/example1.xls'; + +/** Create a new Reader of the type defined in $inputFileType **/ +$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader($inputFileType); +/** Load $inputFileName to a Spreadsheet Object **/ +$spreadsheet = $reader->load($inputFileName); +``` + +See `samples/Reader/03_Simple_file_reader_using_the_IOFactory_to_return_a_reader.php` +for a working example of this code. + +If you're uncertain of the filetype, you can use the `IOFactory::identify()` +method to identify the reader that you need, before using the +`createReader()` method to instantiate the reader object. + +``` php +$inputFileName = './sampleData/example1.xls'; + +/** Identify the type of $inputFileName **/ +$inputFileType = \PhpOffice\PhpSpreadsheet\IOFactory::identify($inputFileName); +/** Create a new Reader of the type that has been identified **/ +$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader($inputFileType); +/** Load $inputFileName to a Spreadsheet Object **/ +$spreadsheet = $reader->load($inputFileName); +``` + +See `samples/Reader/04_Simple_file_reader_using_the_IOFactory_to_identify_a_reader_to_use.php` +for a working example of this code. + +## Spreadsheet Reader Options + +Once you have created a reader object for the workbook that you want to +load, you have the opportunity to set additional options before +executing the `load()` method. + +### Reading Only Data from a Spreadsheet File + +If you're only interested in the cell values in a workbook, but don't +need any of the cell formatting information, then you can set the reader +to read only the data values and any formulae from each cell using the +`setReadDataOnly()` method. + +``` php +$inputFileType = 'Xls'; +$inputFileName = './sampleData/example1.xls'; + +/** Create a new Reader of the type defined in $inputFileType **/ +$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader($inputFileType); +/** Advise the Reader that we only want to load cell data **/ +$reader->setReadDataOnly(true); +/** Load $inputFileName to a Spreadsheet Object **/ +$spreadsheet = $reader->load($inputFileName); +``` + +See `samples/Reader/05_Simple_file_reader_using_the_read_data_only_option.php` +for a working example of this code. + +It is important to note that Workbooks (and PhpSpreadsheet) store dates +and times as simple numeric values: they can only be distinguished from +other numeric values by the format mask that is applied to that cell. +When setting read data only to true, PhpSpreadsheet doesn't read the +cell format masks, so it is not possible to differentiate between +dates/times and numbers. + +The Gnumeric loader has been written to read the format masks for date +values even when read data only has been set to true, so it can +differentiate between dates/times and numbers; but this change hasn't +yet been implemented for the other readers. + +Reading Only Data from a Spreadsheet File applies to Readers: + +Reader | Y/N |Reader | Y/N |Reader | Y/N | +----------|:---:|--------|:---:|--------------|:---:| +Xlsx | YES | Xls | YES | Xml | YES | +Ods | YES | SYLK | NO | Gnumeric | YES | +CSV | NO | HTML | NO + +### Reading Only Named WorkSheets from a File + +If your workbook contains a number of worksheets, but you are only +interested in reading some of those, then you can use the +`setLoadSheetsOnly()` method to identify those sheets you are interested +in reading. + +To read a single sheet, you can pass that sheet name as a parameter to +the `setLoadSheetsOnly()` method. + +``` php +$inputFileType = 'Xls'; +$inputFileName = './sampleData/example1.xls'; +$sheetname = 'Data Sheet #2'; + +/** Create a new Reader of the type defined in $inputFileType **/ +$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader($inputFileType); +/** Advise the Reader of which WorkSheets we want to load **/ +$reader->setLoadSheetsOnly($sheetname); +/** Load $inputFileName to a Spreadsheet Object **/ +$spreadsheet = $reader->load($inputFileName); +``` + +See `samples/Reader/07_Simple_file_reader_loading_a_single_named_worksheet.php` +for a working example of this code. + +If you want to read more than just a single sheet, you can pass a list +of sheet names as an array parameter to the `setLoadSheetsOnly()` method. + +``` php +$inputFileType = 'Xls'; +$inputFileName = './sampleData/example1.xls'; +$sheetnames = ['Data Sheet #1','Data Sheet #3']; + +/** Create a new Reader of the type defined in $inputFileType **/ +$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader($inputFileType); +/** Advise the Reader of which WorkSheets we want to load **/ +$reader->setLoadSheetsOnly($sheetnames); +/** Load $inputFileName to a Spreadsheet Object **/ +$spreadsheet = $reader->load($inputFileName); +``` + +See `samples/Reader/08_Simple_file_reader_loading_several_named_worksheets.php` +for a working example of this code. + +To reset this option to the default, you can call the `setLoadAllSheets()` +method. + +``` php +$inputFileType = 'Xls'; +$inputFileName = './sampleData/example1.xls'; + +/** Create a new Reader of the type defined in $inputFileType **/ +$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader($inputFileType); +/** Advise the Reader to load all Worksheets **/ +$reader->setLoadAllSheets(); +/** Load $inputFileName to a Spreadsheet Object **/ +$spreadsheet = $reader->load($inputFileName); +``` + +See `samples/Reader/06_Simple_file_reader_loading_all_worksheets.php` for a +working example of this code. + +Reading Only Named WorkSheets from a File applies to Readers: + +Reader | Y/N |Reader | Y/N |Reader | Y/N | +----------|:---:|--------|:---:|--------------|:---:| +Xlsx | YES | Xls | YES | Xml | YES | +Ods | YES | SYLK | NO | Gnumeric | YES | +CSV | NO | HTML | NO + +### Reading Only Specific Columns and Rows from a File (Read Filters) + +If you are only interested in reading part of a worksheet, then you can +write a filter class that identifies whether or not individual cells +should be read by the loader. A read filter must implement the +`\PhpOffice\PhpSpreadsheet\Reader\IReadFilter` interface, and contain a +`readCell()` method that accepts arguments of `$column`, `$row` and +`$worksheetName`, and return a boolean true or false that indicates +whether a workbook cell identified by those arguments should be read or +not. + +``` php +$inputFileType = 'Xls'; +$inputFileName = './sampleData/example1.xls'; +$sheetname = 'Data Sheet #3'; + +/** Define a Read Filter class implementing \PhpOffice\PhpSpreadsheet\Reader\IReadFilter */ +class MyReadFilter implements \PhpOffice\PhpSpreadsheet\Reader\IReadFilter +{ + public function readCell($column, $row, $worksheetName = '') { + // Read rows 1 to 7 and columns A to E only + if ($row >= 1 && $row <= 7) { + if (in_array($column,range('A','E'))) { + return true; + } + } + return false; + } +} + +/** Create an Instance of our Read Filter **/ +$filterSubset = new MyReadFilter(); + +/** Create a new Reader of the type defined in $inputFileType **/ +$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader($inputFileType); +/** Tell the Reader that we want to use the Read Filter **/ +$reader->setReadFilter($filterSubset); +/** Load only the rows and columns that match our filter to Spreadsheet **/ +$spreadsheet = $reader->load($inputFileName); +``` + +See `samples/Reader/09_Simple_file_reader_using_a_read_filter.php` for a +working example of this code. + +This example is not particularly useful, because it can only be used in +a very specific circumstance (when you only want cells in the range +A1:E7 from your worksheet. A generic Read Filter would probably be more +useful: + +``` php +/** Define a Read Filter class implementing \PhpOffice\PhpSpreadsheet\Reader\IReadFilter */ +class MyReadFilter implements \PhpOffice\PhpSpreadsheet\Reader\IReadFilter +{ + private $startRow = 0; + private $endRow = 0; + private $columns = []; + + /** Get the list of rows and columns to read */ + public function __construct($startRow, $endRow, $columns) { + $this->startRow = $startRow; + $this->endRow = $endRow; + $this->columns = $columns; + } + + public function readCell($column, $row, $worksheetName = '') { + // Only read the rows and columns that were configured + if ($row >= $this->startRow && $row <= $this->endRow) { + if (in_array($column,$this->columns)) { + return true; + } + } + return false; + } +} + +/** Create an Instance of our Read Filter, passing in the cell range **/ +$filterSubset = new MyReadFilter(9,15,range('G','K')); +``` + +See `samples/Reader/10_Simple_file_reader_using_a_configurable_read_filter.php` +for a working example of this code. + +This can be particularly useful for conserving memory, by allowing you +to read and process a large workbook in "chunks": an example of this +usage might be when transferring data from an Excel worksheet to a +database. + +``` php +$inputFileType = 'Xls'; +$inputFileName = './sampleData/example2.xls'; + +/** Define a Read Filter class implementing \PhpOffice\PhpSpreadsheet\Reader\IReadFilter */ +class ChunkReadFilter implements \PhpOffice\PhpSpreadsheet\Reader\IReadFilter +{ + private $startRow = 0; + private $endRow = 0; + + /** Set the list of rows that we want to read */ + public function setRows($startRow, $chunkSize) { + $this->startRow = $startRow; + $this->endRow = $startRow + $chunkSize; + } + + public function readCell($column, $row, $worksheetName = '') { + // Only read the heading row, and the configured rows + if (($row == 1) || ($row >= $this->startRow && $row < $this->endRow)) { + return true; + } + return false; + } +} + +/** Create a new Reader of the type defined in $inputFileType **/ +$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader($inputFileType); + +/** Define how many rows we want to read for each "chunk" **/ +$chunkSize = 2048; +/** Create a new Instance of our Read Filter **/ +$chunkFilter = new ChunkReadFilter(); + +/** Tell the Reader that we want to use the Read Filter **/ +$reader->setReadFilter($chunkFilter); + +/** Loop to read our worksheet in "chunk size" blocks **/ +for ($startRow = 2; $startRow <= 65536; $startRow += $chunkSize) { + /** Tell the Read Filter which rows we want this iteration **/ + $chunkFilter->setRows($startRow,$chunkSize); + /** Load only the rows that match our filter **/ + $spreadsheet = $reader->load($inputFileName); + // Do some processing here +} +``` + +See `samples/Reader/12_Reading_a_workbook_in_chunks_using_a_configurable_read_filter_` +for a working example of this code. + +Using Read Filters applies to: + +Reader | Y/N |Reader | Y/N |Reader | Y/N | +----------|:---:|--------|:---:|--------------|:---:| +Xlsx | YES | Xls | YES | Xml | YES | +Ods | YES | SYLK | NO | Gnumeric | YES | +CSV | YES | HTML | NO | | | + +### Combining Multiple Files into a Single Spreadsheet Object + +While you can limit the number of worksheets that are read from a +workbook file using the `setLoadSheetsOnly()` method, certain readers also +allow you to combine several individual "sheets" from different files +into a single `Spreadsheet` object, where each individual file is a +single worksheet within that workbook. For each file that you read, you +need to indicate which worksheet index it should be loaded into using +the `setSheetIndex()` method of the `$reader`, then use the +`loadIntoExisting()` method rather than the `load()` method to actually read +the file into that worksheet. + +``` php +$inputFileType = 'Csv'; +$inputFileNames = [ + './sampleData/example1.csv', + './sampleData/example2.csv' + './sampleData/example3.csv' +]; + +/** Create a new Reader of the type defined in $inputFileType **/ +$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader($inputFileType); + +/** Extract the first named file from the array list **/ +$inputFileName = array_shift($inputFileNames); +/** Load the initial file to the first worksheet in a `Spreadsheet` Object **/ +$spreadsheet = $reader->load($inputFileName); +/** Set the worksheet title (to the filename that we've loaded) **/ +$spreadsheet->getActiveSheet() + ->setTitle(pathinfo($inputFileName,PATHINFO_BASENAME)); + +/** Loop through all the remaining files in the list **/ +foreach($inputFileNames as $sheet => $inputFileName) { + /** Increment the worksheet index pointer for the Reader **/ + $reader->setSheetIndex($sheet+1); + /** Load the current file into a new worksheet in Spreadsheet **/ + $reader->loadIntoExisting($inputFileName,$spreadsheet); + /** Set the worksheet title (to the filename that we've loaded) **/ + $spreadsheet->getActiveSheet() + ->setTitle(pathinfo($inputFileName,PATHINFO_BASENAME)); +} +``` + +See `samples/Reader/13_Simple_file_reader_for_multiple_CSV_files.php` for a +working example of this code. + +Note that using the same sheet index for multiple sheets won't append +files into the same sheet, but overwrite the results of the previous +load. You cannot load multiple CSV files into the same worksheet. + +Combining Multiple Files into a Single Spreadsheet Object applies to: + +Reader | Y/N |Reader | Y/N |Reader | Y/N | +----------|:---:|--------|:---:|--------------|:---:| +Xlsx | NO | Xls | NO | Xml | NO | +Ods | NO | SYLK | YES | Gnumeric | NO | +CSV | YES | HTML | NO + +### Combining Read Filters with the `setSheetIndex()` method to split a large CSV file across multiple Worksheets + +An Xls BIFF .xls file is limited to 65536 rows in a worksheet, while the +Xlsx Microsoft Office Open XML SpreadsheetML .xlsx file is limited to +1,048,576 rows in a worksheet; but a CSV file is not limited other than +by available disk space. This means that we wouldn’t ordinarily be able +to read all the rows from a very large CSV file that exceeded those +limits, and save it as an Xls or Xlsx file. However, by using Read +Filters to read the CSV file in "chunks" (using the ChunkReadFilter +Class that we defined in [the above section](#reading-only-specific-columns-and-rows-from-a-file-read-filters), +and the `setSheetIndex()` method of the `$reader`, we can split the CSV +file across several individual worksheets. + +``` php +$inputFileType = 'Csv'; +$inputFileName = './sampleData/example2.csv'; + +echo 'Loading file ',pathinfo($inputFileName,PATHINFO_BASENAME),' using IOFactory with a defined reader type of ',$inputFileType,'
    '; +/** Create a new Reader of the type defined in $inputFileType **/ +$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader($inputFileType); + +/** Define how many rows we want to read for each "chunk" **/ +$chunkSize = 65530; +/** Create a new Instance of our Read Filter **/ +$chunkFilter = new ChunkReadFilter(); + +/** Tell the Reader that we want to use the Read Filter **/ +/** and that we want to store it in contiguous rows/columns **/ + +$reader->setReadFilter($chunkFilter) + ->setContiguous(true); + +/** Instantiate a new Spreadsheet object manually **/ +$spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet(); + +/** Set a sheet index **/ +$sheet = 0; +/** Loop to read our worksheet in "chunk size" blocks **/ +/** $startRow is set to 2 initially because we always read the headings in row #1 **/ +for ($startRow = 2; $startRow <= 1000000; $startRow += $chunkSize) { + /** Tell the Read Filter which rows we want to read this loop **/ + $chunkFilter->setRows($startRow,$chunkSize); + + /** Increment the worksheet index pointer for the Reader **/ + $reader->setSheetIndex($sheet); + /** Load only the rows that match our filter into a new worksheet **/ + $reader->loadIntoExisting($inputFileName,$spreadsheet); + /** Set the worksheet title for the sheet that we've justloaded) **/ + /** and increment the sheet index as well **/ + $spreadsheet->getActiveSheet()->setTitle('Country Data #'.(++$sheet)); +} +``` + +See `samples/Reader/14_Reading_a_large_CSV_file_in_chunks_to_split_across_multiple_worksheets.php` +for a working example of this code. + +This code will read 65,530 rows at a time from the CSV file that we’re +loading, and store each "chunk" in a new worksheet. + +The `setContiguous()` method for the Reader is important here. It is +applicable only when working with a Read Filter, and identifies whether +or not the cells should be stored by their position within the CSV file, +or their position relative to the filter. + +For example, if the filter returned true for cells in the range B2:C3, +then with setContiguous set to false (the default) these would be loaded +as B2:C3 in the `Spreadsheet` object; but with setContiguous set to +true, they would be loaded as A1:B2. + +Splitting a single loaded file across multiple worksheets applies to: + +Reader | Y/N |Reader | Y/N |Reader | Y/N | +----------|:---:|--------|:---:|--------------|:---:| +Xlsx | NO | Xls | NO | Xml | NO | +Ods | NO | SYLK | NO | Gnumeric | NO | +CSV | YES | HTML | NO + +### Pipe or Tab Separated Value Files + +The CSV loader will attempt to auto-detect the separator used in the file. If it +cannot auto-detect, it will default to the comma. If this does not fit your +use-case, you can manually specify a separator by using the `setDelimiter()` +method. + +``` php +$inputFileType = 'Csv'; +$inputFileName = './sampleData/example1.tsv'; + +/** Create a new Reader of the type defined in $inputFileType **/ +$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader($inputFileType); +/** Set the delimiter to a TAB character **/ +$reader->setDelimiter("\t"); +// $reader->setDelimiter('|'); + +/** Load the file to a Spreadsheet Object **/ +$spreadsheet = $reader->load($inputFileName); +``` + +See `samples/Reader/15_Simple_file_reader_for_tab_separated_value_file_using_the_Advanced_Value_Binder.php` +for a working example of this code. + +In addition to the delimiter, you can also use the following methods to +set other attributes for the data load: + +Method | Default +-------------------|---------- +setEnclosure() | `"` +setInputEncoding() | `UTF-8` + +Setting CSV delimiter applies to: + +Reader | Y/N |Reader | Y/N |Reader | Y/N | +----------|:---:|--------|:---:|--------------|:---:| +Xlsx | NO | Xls | NO | Xml | NO | +Ods | NO | SYLK | NO | Gnumeric | NO | +CSV | YES | HTML | NO + +### A Brief Word about the Advanced Value Binder + +When loading data from a file that contains no formatting information, +such as a CSV file, then data is read either as strings or numbers +(float or integer). This means that PhpSpreadsheet does not +automatically recognise dates/times (such as `16-Apr-2009` or `13:30`), +booleans (`true` or `false`), percentages (`75%`), hyperlinks +(`https://www.example.com`), etc as anything other than simple strings. +However, you can apply additional processing that is executed against +these values during the load process within a Value Binder. + +A Value Binder is a class that implement the +`\PhpOffice\PhpSpreadsheet\Cell\IValueBinder` interface. It must contain a +`bindValue()` method that accepts a `\PhpOffice\PhpSpreadsheet\Cell\Cell` and a +value as arguments, and return a boolean `true` or `false` that indicates +whether the workbook cell has been populated with the value or not. The +Advanced Value Binder implements such a class: amongst other tests, it +identifies a string comprising "TRUE" or "FALSE" (based on locale +settings) and sets it to a boolean; or a number in scientific format +(e.g. "1.234e-5") and converts it to a float; or dates and times, +converting them to their Excel timestamp value – before storing the +value in the cell object. It also sets formatting for strings that are +identified as dates, times or percentages. It could easily be extended +to provide additional handling (including text or cell formatting) when +it encountered a hyperlink, or HTML markup within a CSV file. + +So using a Value Binder allows a great deal more flexibility in the +loader logic when reading unformatted text files. + +``` php +/** Tell PhpSpreadsheet that we want to use the Advanced Value Binder **/ +\PhpOffice\PhpSpreadsheet\Cell\Cell::setValueBinder( new \PhpOffice\PhpSpreadsheet\Cell\AdvancedValueBinder() ); + +$inputFileType = 'Csv'; +$inputFileName = './sampleData/example1.tsv'; + +$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader($inputFileType); +$reader->setDelimiter("\t"); +$spreadsheet = $reader->load($inputFileName); +``` + +See `samples/Reader/15_Simple_file_reader_for_tab_separated_value_file_using_the_Advanced_Value_Binder.php` +for a working example of this code. + +Loading using a Value Binder applies to: + +Reader | Y/N |Reader | Y/N |Reader | Y/N +----------|:---:|--------|:---:|--------------|:---: +Xlsx | NO | Xls | NO | Xml | NO +Ods | NO | SYLK | NO | Gnumeric | NO +CSV | YES | HTML | YES + +## Error Handling + +Of course, you should always apply some error handling to your scripts +as well. PhpSpreadsheet throws exceptions, so you can wrap all your code +that accesses the library methods within Try/Catch blocks to trap for +any problems that are encountered, and deal with them in an appropriate +manner. + +The PhpSpreadsheet Readers throw a +`\PhpOffice\PhpSpreadsheet\Reader\Exception`. + +``` php +$inputFileName = './sampleData/example-1.xls'; + +try { + /** Load $inputFileName to a Spreadsheet Object **/ + $spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($inputFileName); +} catch(\PhpOffice\PhpSpreadsheet\Reader\Exception $e) { + die('Error loading file: '.$e->getMessage()); +} +``` + +See `samples/Reader/16_Handling_loader_exceptions_using_TryCatch.php` for a +working example of this code. + +## Helper Methods + +You can retrieve a list of worksheet names contained in a file without +loading the whole file by using the Reader’s `listWorksheetNames()` +method; similarly, a `listWorksheetInfo()` method will retrieve the +dimensions of worksheet in a file without needing to load and parse the +whole file. + +### listWorksheetNames + +The `listWorksheetNames()` method returns a simple array listing each +worksheet name within the workbook: + +``` php +$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader($inputFileType); + +$worksheetNames = $reader->listWorksheetNames($inputFileName); + +echo '

    Worksheet Names

    '; +echo '
      '; +foreach ($worksheetNames as $worksheetName) { + echo '
    1. ', $worksheetName, '
    2. '; +} +echo '
    '; +``` + +See `samples/Reader/18_Reading_list_of_worksheets_without_loading_entire_file.php` +for a working example of this code. + +### listWorksheetInfo + +The `listWorksheetInfo()` method returns a nested array, with each entry +listing the name and dimensions for a worksheet: + +``` php +$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader($inputFileType); + +$worksheetData = $reader->listWorksheetInfo($inputFileName); + +echo '

    Worksheet Information

    '; +echo '
      '; +foreach ($worksheetData as $worksheet) { + echo '
    1. ', $worksheet['worksheetName'], '
      '; + echo 'Rows: ', $worksheet['totalRows'], + ' Columns: ', $worksheet['totalColumns'], '
      '; + echo 'Cell Range: A1:', + $worksheet['lastColumnLetter'], $worksheet['totalRows']; + echo '
    2. '; +} +echo '
    '; +``` + +See `samples/Reader/19_Reading_worksheet_information_without_loading_entire_file.php` +for a working example of this code. diff --git a/vendor/phpoffice/phpspreadsheet/docs/topics/recipes.md b/vendor/phpoffice/phpspreadsheet/docs/topics/recipes.md new file mode 100644 index 0000000..b0956b6 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/docs/topics/recipes.md @@ -0,0 +1,1506 @@ +# Recipes + +The following pages offer you some widely-used PhpSpreadsheet recipes. +Please note that these do NOT offer complete documentation on specific +PhpSpreadsheet API functions, but just a bump to get you started. If you +need specific API functions, please refer to the [API documentation](https://phpoffice.github.io/PhpSpreadsheet). + +For example, [setting a worksheet's page orientation and size +](#setting-a-worksheets-page-orientation-and-size) covers setting a page +orientation to A4. Other paper formats, like US Letter, are not covered +in this document, but in the PhpSpreadsheet [API documentation](https://phpoffice.github.io/PhpSpreadsheet). + +## Setting a spreadsheet's metadata + +PhpSpreadsheet allows an easy way to set a spreadsheet's metadata, using +document property accessors. Spreadsheet metadata can be useful for +finding a specific document in a file repository or a document +management system. For example Microsoft Sharepoint uses document +metadata to search for a specific document in its document lists. + +Setting spreadsheet metadata is done as follows: + +``` php +$spreadsheet->getProperties() + ->setCreator("Maarten Balliauw") + ->setLastModifiedBy("Maarten Balliauw") + ->setTitle("Office 2007 XLSX Test Document") + ->setSubject("Office 2007 XLSX Test Document") + ->setDescription( + "Test document for Office 2007 XLSX, generated using PHP classes." + ) + ->setKeywords("office 2007 openxml php") + ->setCategory("Test result file"); +``` + +## Setting a spreadsheet's active sheet + +The following line of code sets the active sheet index to the first +sheet: + +``` php +$spreadsheet->setActiveSheetIndex(0); +``` + +You can also set the active sheet by its name/title + +``` php +$spreadsheet->setActiveSheetIndexByName('DataSheet') +``` + +will change the currently active sheet to the worksheet called +"DataSheet". + +## Write a date or time into a cell + +In Excel, dates and Times are stored as numeric values counting the +number of days elapsed since 1900-01-01. For example, the date +'2008-12-31' is represented as 39813. You can verify this in Microsoft +Office Excel by entering that date in a cell and afterwards changing the +number format to 'General' so the true numeric value is revealed. +Likewise, '3:15 AM' is represented as 0.135417. + +PhpSpreadsheet works with UST (Universal Standard Time) date and Time +values, but does no internal conversions; so it is up to the developer +to ensure that values passed to the date/time conversion functions are +UST. + +Writing a date value in a cell consists of 2 lines of code. Select the +method that suits you the best. Here are some examples: + +``` php + +// MySQL-like timestamp '2008-12-31' or date string +\PhpOffice\PhpSpreadsheet\Cell\Cell::setValueBinder( new \PhpOffice\PhpSpreadsheet\Cell\AdvancedValueBinder() ); + +$spreadsheet->getActiveSheet() + ->setCellValue('D1', '2008-12-31'); + +$spreadsheet->getActiveSheet()->getStyle('D1') + ->getNumberFormat() + ->setFormatCode(\PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_DATE_YYYYMMDDSLASH); + +// PHP-time (Unix time) +$time = gmmktime(0,0,0,12,31,2008); // int(1230681600) +$spreadsheet->getActiveSheet() + ->setCellValue('D1', \PhpOffice\PhpSpreadsheet\Shared\Date::PHPToExcel($time)); +$spreadsheet->getActiveSheet()->getStyle('D1') + ->getNumberFormat() + ->setFormatCode(\PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_DATE_YYYYMMDDSLASH); + +// Excel-date/time +$spreadsheet->getActiveSheet()->setCellValue('D1', 39813) +$spreadsheet->getActiveSheet()->getStyle('D1') + ->getNumberFormat() + ->setFormatCode(\PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_DATE_YYYYMMDDSLASH); +``` + +The above methods for entering a date all yield the same result. +`\PhpOffice\PhpSpreadsheet\Style\NumberFormat` provides a lot of +pre-defined date formats. + +The `\PhpOffice\PhpSpreadsheet\Shared\Date::PHPToExcel()` method will also +work with a PHP DateTime object. + +Similarly, times (or date and time values) can be entered in the same +fashion: just remember to use an appropriate format code. + +**Note:** + +See section "Using value binders to facilitate data entry" to learn more +about the AdvancedValueBinder used in the first example. Excel can also +operate in a 1904-based calendar (default for workbooks saved on Mac). +Normally, you do not have to worry about this when using PhpSpreadsheet. + +## Write a formula into a cell + +Inside the Excel file, formulas are always stored as they would appear +in an English version of Microsoft Office Excel, and PhpSpreadsheet +handles all formulae internally in this format. This means that the +following rules hold: + +- Decimal separator is `.` (period) +- Function argument separator is `,` (comma) +- Matrix row separator is `;` (semicolon) +- English function names must be used + +This is regardless of which language version of Microsoft Office Excel +may have been used to create the Excel file. + +When the final workbook is opened by the user, Microsoft Office Excel +will take care of displaying the formula according the applications +language. Translation is taken care of by the application! + +The following line of code writes the formula +`=IF(C4>500,"profit","loss")` into the cell B8. Note that the +formula must start with `=` to make PhpSpreadsheet recognise this as a +formula. + +``` php +$spreadsheet->getActiveSheet()->setCellValue('B8','=IF(C4>500,"profit","loss")'); +``` + +If you want to write a string beginning with an `=` character to a +cell, then you should use the `setCellValueExplicit()` method. + +``` php +$spreadsheet->getActiveSheet() + ->setCellValueExplicit( + 'B8', + '=IF(C4>500,"profit","loss")', + \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING + ); +``` + +A cell's formula can be read again using the following line of code: + +``` php +$formula = $spreadsheet->getActiveSheet()->getCell('B8')->getValue(); +``` + +If you need the calculated value of a cell, use the following code. This +is further explained in [the calculation engine](./calculation-engine.md). + +``` php +$value = $spreadsheet->getActiveSheet()->getCell('B8')->getCalculatedValue(); +``` + +## Locale Settings for Formulae + +Some localisation elements have been included in PhpSpreadsheet. You can +set a locale by changing the settings. To set the locale to Russian you +would use: + +``` php +$locale = 'ru'; +$validLocale = \PhpOffice\PhpSpreadsheet\Settings::setLocale($locale); +if (!$validLocale) { + echo 'Unable to set locale to '.$locale." - reverting to en_us
    \n"; +} +``` + +If Russian language files aren't available, the `setLocale()` method +will return an error, and English settings will be used throughout. + +Once you have set a locale, you can translate a formula from its +internal English coding. + +``` php +$formula = $spreadsheet->getActiveSheet()->getCell('B8')->getValue(); +$translatedFormula = \PhpOffice\PhpSpreadsheet\Calculation\Calculation::getInstance()->_translateFormulaToLocale($formula); +``` + +You can also create a formula using the function names and argument +separators appropriate to the defined locale; then translate it to +English before setting the cell value: + +``` php +$formula = '=ДНЕЙ360(ДАТА(2010;2;5);ДАТА(2010;12;31);ИСТИНА)'; +$internalFormula = \PhpOffice\PhpSpreadsheet\Calculation\Calculation::getInstance()->translateFormulaToEnglish($formula); +$spreadsheet->getActiveSheet()->setCellValue('B8',$internalFormula); +``` + +Currently, formula translation only translates the function names, the +constants TRUE and FALSE, and the function argument separators. + +At present, the following locale settings are supported: + +Language | | Locale Code +---------------------|----------------------|------------- +Czech | Ceština | cs +Danish | Dansk | da +German | Deutsch | de +Spanish | Español | es +Finnish | Suomi | fi +French | Français | fr +Hungarian | Magyar | hu +Italian | Italiano | it +Dutch | Nederlands | nl +Norwegian | Norsk | no +Polish | Jezyk polski | pl +Portuguese | Português | pt +Brazilian Portuguese | Português Brasileiro | pt_br +Russian | русский язык | ru +Swedish | Svenska | sv +Turkish | Türkçe | tr + +## Write a newline character "\n" in a cell (ALT+"Enter") + +In Microsoft Office Excel you get a line break in a cell by hitting +ALT+"Enter". When you do that, it automatically turns on "wrap text" for +the cell. + +Here is how to achieve this in PhpSpreadsheet: + +``` php +$spreadsheet->getActiveSheet()->getCell('A1')->setValue("hello\nworld"); +$spreadsheet->getActiveSheet()->getStyle('A1')->getAlignment()->setWrapText(true); +``` + +**Tip** + +Read more about formatting cells using `getStyle()` elsewhere. + +**Tip** + +AdvancedValuebinder.php automatically turns on "wrap text" for the cell +when it sees a newline character in a string that you are inserting in a +cell. Just like Microsoft Office Excel. Try this: + +``` php +\PhpOffice\PhpSpreadsheet\Cell\Cell::setValueBinder( new \PhpOffice\PhpSpreadsheet\Cell\AdvancedValueBinder() ); + +$spreadsheet->getActiveSheet()->getCell('A1')->setValue("hello\nworld"); +``` + +Read more about AdvancedValueBinder.php elsewhere. + +## Explicitly set a cell's datatype + +You can set a cell's datatype explicitly by using the cell's +setValueExplicit method, or the setCellValueExplicit method of a +worksheet. Here's an example: + +``` php +$spreadsheet->getActiveSheet()->getCell('A1') + ->setValueExplicit( + '25', + \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_NUMERIC + ); +``` + +## Change a cell into a clickable URL + +You can make a cell a clickable URL by setting its hyperlink property: + +``` php +$spreadsheet->getActiveSheet()->setCellValue('E26', 'www.phpexcel.net'); +$spreadsheet->getActiveSheet()->getCell('E26')->getHyperlink()->setUrl('https://www.example.com'); +``` + +If you want to make a hyperlink to another worksheet/cell, use the +following code: + +``` php +$spreadsheet->getActiveSheet()->setCellValue('E26', 'www.phpexcel.net'); +$spreadsheet->getActiveSheet()->getCell('E26')->getHyperlink()->setUrl("sheet://'Sheetname'!A1"); +``` + +## Setting Printer Options for Excel files + +### Setting a worksheet's page orientation and size + +Setting a worksheet's page orientation and size can be done using the +following lines of code: + +``` php +$spreadsheet->getActiveSheet()->getPageSetup() + ->setOrientation(\PhpOffice\PhpSpreadsheet\Worksheet\PageSetup::ORIENTATION_LANDSCAPE); +$spreadsheet->getActiveSheet()->getPageSetup() + ->setPaperSize(\PhpOffice\PhpSpreadsheet\Worksheet\PageSetup::PAPERSIZE_A4); +``` + +Note that there are additional page settings available. Please refer to +the [API documentation](https://phpoffice.github.io/PhpSpreadsheet) for all possible options. + +### Page Setup: Scaling options + +The page setup scaling options in PhpSpreadsheet relate directly to the +scaling options in the "Page Setup" dialog as shown in the illustration. + +Default values in PhpSpreadsheet correspond to default values in MS +Office Excel as shown in illustration + +![08-page-setup-scaling-options.png](./images/08-page-setup-scaling-options.png) + +method | initial value | calling method will trigger | Note +--------------------|:-------------:|-----------------------------|------ +setFitToPage(...) | FALSE | - | +setScale(...) | 100 | setFitToPage(FALSE) | +setFitToWidth(...) | 1 | setFitToPage(TRUE) | value 0 means do-not-fit-to-width +setFitToHeight(...) | 1 | setFitToPage(TRUE) | value 0 means do-not-fit-to-height + +#### Example + +Here is how to fit to 1 page wide by infinite pages tall: + +``` php +$spreadsheet->getActiveSheet()->getPageSetup()->setFitToWidth(1); +$spreadsheet->getActiveSheet()->getPageSetup()->setFitToHeight(0); +``` + +As you can see, it is not necessary to call setFitToPage(TRUE) since +setFitToWidth(...) and setFitToHeight(...) triggers this. + +If you use `setFitToWidth()` you should in general also specify +`setFitToHeight()` explicitly like in the example. Be careful relying on +the initial values. + +### Page margins + +To set page margins for a worksheet, use this code: + +``` php +$spreadsheet->getActiveSheet()->getPageMargins()->setTop(1); +$spreadsheet->getActiveSheet()->getPageMargins()->setRight(0.75); +$spreadsheet->getActiveSheet()->getPageMargins()->setLeft(0.75); +$spreadsheet->getActiveSheet()->getPageMargins()->setBottom(1); +``` + +Note that the margin values are specified in inches. + +![08-page-setup-margins.png](./images/08-page-setup-margins.png) + +### Center a page horizontally/vertically + +To center a page horizontally/vertically, you can use the following +code: + +``` php +$spreadsheet->getActiveSheet()->getPageSetup()->setHorizontalCentered(true); +$spreadsheet->getActiveSheet()->getPageSetup()->setVerticalCentered(false); +``` + +### Setting the print header and footer of a worksheet + +Setting a worksheet's print header and footer can be done using the +following lines of code: + +``` php +$spreadsheet->getActiveSheet()->getHeaderFooter() + ->setOddHeader('&C&HPlease treat this document as confidential!'); +$spreadsheet->getActiveSheet()->getHeaderFooter() + ->setOddFooter('&L&B' . $spreadsheet->getProperties()->getTitle() . '&RPage &P of &N'); +``` + +Substitution and formatting codes (starting with &) can be used inside +headers and footers. 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. + +The following codes are supported by Xlsx: + +Code | Meaning +-------------------------|----------- +`&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" - Please make sure to add the image to the header/footer (see Tip for picture) +`&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` | Code for "bold font style" +`&"-,Regular"` | Code for "regular font style" +`&"-,Italic"` | Code for "italic font style" +`&I` | Code for "italic font style" +`&"-,Bold Italic"` | Code for "bold italic font style" +`&O` | Code for "outline style" +`&H` | Code for "shadow style" + +**Tip** + +The above table of codes may seem overwhelming first time you are trying to +figure out how to write some header or footer. Luckily, there is an easier way. +Let Microsoft Office Excel do the work for you.For example, create in Microsoft + Office Excel an xlsx file where you insert the header and footer as desired +using the programs own interface. Save file as test.xlsx. Now, take that file +and read off the values using PhpSpreadsheet as follows: + +```php +$spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load('test.xlsx'); +$worksheet = $spreadsheet->getActiveSheet(); + +var_dump($worksheet->getHeaderFooter()->getOddFooter()); +var_dump($worksheet->getHeaderFooter()->getEvenFooter()); +var_dump($worksheet->getHeaderFooter()->getOddHeader()); +var_dump($worksheet->getHeaderFooter()->getEvenHeader()); +``` + +That reveals the codes for the even/odd header and footer. Experienced +users may find it easier to rename test.xlsx to test.zip, unzip it, and +inspect directly the contents of the relevant xl/worksheets/sheetX.xml +to find the codes for header/footer. + +**Tip for picture** + +```php +$drawing = new \PhpOffice\PhpSpreadsheet\Worksheet\HeaderFooterDrawing(); +$drawing->setName('PhpSpreadsheet logo'); +$drawing->setPath('./images/PhpSpreadsheet_logo.png'); +$drawing->setHeight(36); +$spreadsheet->getActiveSheet()->getHeaderFooter()->addImage($drawing, \PhpOffice\PhpSpreadsheet\Worksheet\HeaderFooter::IMAGE_HEADER_LEFT); +``` + +### Setting printing breaks on a row or column + +To set a print break, use the following code, which sets a row break on +row 10. + +``` php +$spreadsheet->getActiveSheet()->setBreak('A10', \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::BREAK_ROW); +``` + +The following line of code sets a print break on column D: + +``` php +$spreadsheet->getActiveSheet()->setBreak('D10', \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::BREAK_COLUMN); +``` + +### Show/hide gridlines when printing + +To show/hide gridlines when printing, use the following code: + +```php +$spreadsheet->getActiveSheet()->setShowGridlines(true); +``` + +### Setting rows/columns to repeat at top/left + +PhpSpreadsheet can repeat specific rows/cells at top/left of a page. The +following code is an example of how to repeat row 1 to 5 on each printed +page of a specific worksheet: + +``` php +$spreadsheet->getActiveSheet()->getPageSetup()->setRowsToRepeatAtTopByStartAndEnd(1, 5); +``` + +### Specify printing area + +To specify a worksheet's printing area, use the following code: + +``` php +$spreadsheet->getActiveSheet()->getPageSetup()->setPrintArea('A1:E5'); +``` + +There can also be multiple printing areas in a single worksheet: + +``` php +$spreadsheet->getActiveSheet()->getPageSetup()->setPrintArea('A1:E5,G4:M20'); +``` + +## Styles + +### Formatting cells + +A cell can be formatted with font, border, fill, ... style information. +For example, one can set the foreground colour of a cell to red, aligned +to the right, and the border to black and thick border style. Let's do +that on cell B2: + +``` php +$spreadsheet->getActiveSheet()->getStyle('B2') + ->getFont()->getColor()->setARGB(\PhpOffice\PhpSpreadsheet\Style\Color::COLOR_RED); +$spreadsheet->getActiveSheet()->getStyle('B2') + ->getAlignment()->setHorizontal(\PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_RIGHT); +$spreadsheet->getActiveSheet()->getStyle('B2') + ->getBorders()->getTop()->setBorderStyle(\PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THICK); +$spreadsheet->getActiveSheet()->getStyle('B2') + ->getBorders()->getBottom()->setBorderStyle(\PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THICK); +$spreadsheet->getActiveSheet()->getStyle('B2') + ->getBorders()->getLeft()->setBorderStyle(\PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THICK); +$spreadsheet->getActiveSheet()->getStyle('B2') + ->getBorders()->getRight()->setBorderStyle(\PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THICK); +$spreadsheet->getActiveSheet()->getStyle('B2') + ->getFill()->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID); +$spreadsheet->getActiveSheet()->getStyle('B2') + ->getFill()->getStartColor()->setARGB('FFFF0000'); +``` + +`getStyle()` also accepts a cell range as a parameter. For example, you +can set a red background color on a range of cells: + +``` php +$spreadsheet->getActiveSheet()->getStyle('B3:B7')->getFill() + ->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID) + ->getStartColor()->setARGB('FFFF0000'); +``` + +**Tip** It is recommended to style many cells at once, using e.g. +getStyle('A1:M500'), rather than styling the cells individually in a +loop. This is much faster compared to looping through cells and styling +them individually. + +There is also an alternative manner to set styles. The following code +sets a cell's style to font bold, alignment right, top border thin and a +gradient fill: + +``` php +$styleArray = [ + 'font' => [ + 'bold' => true, + ], + 'alignment' => [ + 'horizontal' => \PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_RIGHT, + ], + 'borders' => [ + 'top' => [ + 'borderStyle' => \PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THIN, + ], + ], + 'fill' => [ + 'fillType' => \PhpOffice\PhpSpreadsheet\Style\Fill::FILL_GRADIENT_LINEAR, + 'rotation' => 90, + 'startColor' => [ + 'argb' => 'FFA0A0A0', + ], + 'endColor' => [ + 'argb' => 'FFFFFFFF', + ], + ], +]; + +$spreadsheet->getActiveSheet()->getStyle('A3')->applyFromArray($styleArray); +``` + +Or with a range of cells: + +``` php +$spreadsheet->getActiveSheet()->getStyle('B3:B7')->applyFromArray($styleArray); +``` + +This alternative method using arrays should be faster in terms of +execution whenever you are setting more than one style property. But the +difference may barely be measurable unless you have many different +styles in your workbook. + +### Number formats + +You often want to format numbers in Excel. For example you may want a +thousands separator plus a fixed number of decimals after the decimal +separator. Or perhaps you want some numbers to be zero-padded. + +In Microsoft Office Excel you may be familiar with selecting a number +format from the "Format Cells" dialog. Here there are some predefined +number formats available including some for dates. The dialog is +designed in a way so you don't have to interact with the underlying raw +number format code unless you need a custom number format. + +In PhpSpreadsheet, you can also apply various predefined number formats. +Example: + +``` php +$spreadsheet->getActiveSheet()->getStyle('A1')->getNumberFormat() + ->setFormatCode(\PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_NUMBER_COMMA_SEPARATED1); +``` + +This will format a number e.g. 1587.2 so it shows up as 1,587.20 when +you open the workbook in MS Office Excel. (Depending on settings for +decimal and thousands separators in Microsoft Office Excel it may show +up as 1.587,20) + +You can achieve exactly the same as the above by using this: + +``` php +$spreadsheet->getActiveSheet()->getStyle('A1')->getNumberFormat() + ->setFormatCode('#,##0.00'); +``` + +In Microsoft Office Excel, as well as in PhpSpreadsheet, you will have +to interact with raw number format codes whenever you need some special +custom number format. Example: + +``` php +$spreadsheet->getActiveSheet()->getStyle('A1')->getNumberFormat() + ->setFormatCode('[Blue][>=3000]$#,##0;[Red][<0]$#,##0;$#,##0'); +``` + +Another example is when you want numbers zero-padded with leading zeros +to a fixed length: + +``` php +$spreadsheet->getActiveSheet()->getCell('A1')->setValue(19); +$spreadsheet->getActiveSheet()->getStyle('A1')->getNumberFormat() + ->setFormatCode('0000'); // will show as 0019 in Excel +``` + +**Tip** The rules for composing a number format code in Excel can be +rather complicated. Sometimes you know how to create some number format +in Microsoft Office Excel, but don't know what the underlying number +format code looks like. How do you find it? + +The readers shipped with PhpSpreadsheet come to the rescue. Load your +template workbook using e.g. Xlsx reader to reveal the number format +code. Example how read a number format code for cell A1: + +``` php +$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader('Xlsx'); +$spreadsheet = $reader->load('template.xlsx'); +var_dump($spreadsheet->getActiveSheet()->getStyle('A1')->getNumberFormat()->getFormatCode()); +``` + +Advanced users may find it faster to inspect the number format code +directly by renaming template.xlsx to template.zip, unzipping, and +looking for the relevant piece of XML code holding the number format +code in *xl/styles.xml*. + +### Alignment and wrap text + +Let's set vertical alignment to the top for cells A1:D4 + +``` php +$spreadsheet->getActiveSheet()->getStyle('A1:D4') + ->getAlignment()->setVertical(\PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_TOP); +``` + +Here is how to achieve wrap text: + +``` php +$spreadsheet->getActiveSheet()->getStyle('A1:D4') + ->getAlignment()->setWrapText(true); +``` + +### Setting the default style of a workbook + +It is possible to set the default style of a workbook. Let's set the +default font to Arial size 8: + +``` php +$spreadsheet->getDefaultStyle()->getFont()->setName('Arial'); +$spreadsheet->getDefaultStyle()->getFont()->setSize(8); +``` + +### Styling cell borders + +In PhpSpreadsheet it is easy to apply various borders on a rectangular +selection. Here is how to apply a thick red border outline around cells +B2:G8. + +``` php +$styleArray = [ + 'borders' => [ + 'outline' => [ + 'borderStyle' => \PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THICK, + 'color' => ['argb' => 'FFFF0000'], + ], + ], +]; + +$worksheet->getStyle('B2:G8')->applyFromArray($styleArray); +``` + +In Microsoft Office Excel, the above operation would correspond to +selecting the cells B2:G8, launching the style dialog, choosing a thick +red border, and clicking on the "Outline" border component. + +Note that the border outline is applied to the rectangular selection +B2:G8 as a whole, not on each cell individually. + +You can achieve any border effect by using just the 5 basic borders and +operating on a single cell at a time: + +- left +- right +- top +- bottom +- diagonal + +Additional shortcut borders come in handy like in the example above. +These are the shortcut borders available: + +- allBorders +- outline +- inside +- vertical +- horizontal + +An overview of all border shortcuts can be seen in the following image: + +![08-styling-border-options.png](./images/08-styling-border-options.png) + +If you simultaneously set e.g. allBorders and vertical, then we have +"overlapping" borders, and one of the components has to win over the +other where there is border overlap. In PhpSpreadsheet, from weakest to +strongest borders, the list is as follows: allBorders, outline/inside, +vertical/horizontal, left/right/top/bottom/diagonal. + +This border hierarchy can be utilized to achieve various effects in an +easy manner. + +### Valid array keys for style `applyFromArray()` + +The following table lists the valid array keys for +`\PhpOffice\PhpSpreadsheet\Style\Style::applyFromArray()` classes. If the "Maps +to property" column maps a key to a setter, the value provided for that +key will be applied directly. If the "Maps to property" column maps a +key to a getter, the value provided for that key will be applied as +another style array. + +**\PhpOffice\PhpSpreadsheet\Style\Style** + +Array key | Maps to property +-------------|------------------- +fill | getFill() +font | getFont() +borders | getBorders() +alignment | getAlignment() +numberFormat | getNumberFormat() +protection | getProtection() + +**\PhpOffice\PhpSpreadsheet\Style\Fill** + +Array key | Maps to property +-----------|------------------- +fillType | setFillType() +rotation | setRotation() +startColor | getStartColor() +endColor | getEndColor() +color | getStartColor() + +**\PhpOffice\PhpSpreadsheet\Style\Font** + +Array key | Maps to property +------------|------------------- +name | setName() +bold | setBold() +italic | setItalic() +underline | setUnderline() +strikethrough | setStrikethrough() +color | getColor() +size | setSize() +superscript | setSuperscript() +subscript | setSubscript() + +**\PhpOffice\PhpSpreadsheet\Style\Borders** + +Array key | Maps to property +------------------|------------------- +allBorders | getLeft(); getRight(); getTop(); getBottom() +left | getLeft() +right | getRight() +top | getTop() +bottom | getBottom() +diagonal | getDiagonal() +vertical | getVertical() +horizontal | getHorizontal() +diagonalDirection | setDiagonalDirection() +outline | setOutline() + +**\PhpOffice\PhpSpreadsheet\Style\Border** + +Array key | Maps to property +------------|------------------- +borderStyle | setBorderStyle() +color | getColor() + +**\PhpOffice\PhpSpreadsheet\Style\Alignment** + +Array key | Maps to property +------------|------------------- +horizontal | setHorizontal() +vertical | setVertical() +textRotation| setTextRotation() +wrapText | setWrapText() +shrinkToFit | setShrinkToFit() +indent | setIndent() + +**\PhpOffice\PhpSpreadsheet\Style\NumberFormat** + +Array key | Maps to property +----------|------------------- +formatCode | setFormatCode() + +**\PhpOffice\PhpSpreadsheet\Style\Protection** + +Array key | Maps to property +----------|------------------- +locked | setLocked() +hidden | setHidden() + +## Conditional formatting a cell + +A cell can be formatted conditionally, based on a specific rule. For +example, one can set the foreground colour of a cell to red if its value +is below zero, and to green if its value is zero or more. + +One can set a conditional style ruleset to a cell using the following +code: + +``` php +$conditional1 = new \PhpOffice\PhpSpreadsheet\Style\Conditional(); +$conditional1->setConditionType(\PhpOffice\PhpSpreadsheet\Style\Conditional::CONDITION_CELLIS); +$conditional1->setOperatorType(\PhpOffice\PhpSpreadsheet\Style\Conditional::OPERATOR_LESSTHAN); +$conditional1->addCondition('0'); +$conditional1->getStyle()->getFont()->getColor()->setARGB(\PhpOffice\PhpSpreadsheet\Style\Color::COLOR_RED); +$conditional1->getStyle()->getFont()->setBold(true); + +$conditional2 = new \PhpOffice\PhpSpreadsheet\Style\Conditional(); +$conditional2->setConditionType(\PhpOffice\PhpSpreadsheet\Style\Conditional::CONDITION_CELLIS); +$conditional2->setOperatorType(\PhpOffice\PhpSpreadsheet\Style\Conditional::OPERATOR_GREATERTHANOREQUAL); +$conditional2->addCondition('0'); +$conditional2->getStyle()->getFont()->getColor()->setARGB(\PhpOffice\PhpSpreadsheet\Style\Color::COLOR_GREEN); +$conditional2->getStyle()->getFont()->setBold(true); + +$conditionalStyles = $spreadsheet->getActiveSheet()->getStyle('B2')->getConditionalStyles(); +$conditionalStyles[] = $conditional1; +$conditionalStyles[] = $conditional2; + +$spreadsheet->getActiveSheet()->getStyle('B2')->setConditionalStyles($conditionalStyles); +``` + +If you want to copy the ruleset to other cells, you can duplicate the +style object: + +``` php +$spreadsheet->getActiveSheet() + ->duplicateStyle( + $spreadsheet->getActiveSheet()->getStyle('B2'), + 'B3:B7' + ); +``` + +## Add a comment to a cell + +To add a comment to a cell, use the following code. The example below +adds a comment to cell E11: + +``` php +$spreadsheet->getActiveSheet() + ->getComment('E11') + ->setAuthor('Mark Baker'); +$commentRichText = $spreadsheet->getActiveSheet() + ->getComment('E11') + ->getText()->createTextRun('PhpSpreadsheet:'); +$commentRichText->getFont()->setBold(true); +$spreadsheet->getActiveSheet() + ->getComment('E11') + ->getText()->createTextRun("\r\n"); +$spreadsheet->getActiveSheet() + ->getComment('E11') + ->getText()->createTextRun('Total amount on the current invoice, excluding VAT.'); +``` + +![08-cell-comment.png](./images/08-cell-comment.png) + +## Apply autofilter to a range of cells + +To apply an autofilter to a range of cells, use the following code: + +``` php +$spreadsheet->getActiveSheet()->setAutoFilter('A1:C9'); +``` + +**Make sure that you always include the complete filter range!** Excel +does support setting only the captionrow, but that's **not** a best +practice... + +## Setting security on a spreadsheet + +Excel offers 3 levels of "protection": + +- Document: allows you to set a password on a complete +spreadsheet, allowing changes to be made only when that password is +entered. +- Worksheet: offers other security options: you can +disallow inserting rows on a specific sheet, disallow sorting, ... +- Cell: offers the option to lock/unlock a cell as well as show/hide +the internal formula. + +An example on setting document security: + +``` php +$spreadsheet->getSecurity()->setLockWindows(true); +$spreadsheet->getSecurity()->setLockStructure(true); +$spreadsheet->getSecurity()->setWorkbookPassword("PhpSpreadsheet"); +``` + +An example on setting worksheet security: + +``` php +$spreadsheet->getActiveSheet() + ->getProtection()->setPassword('PhpSpreadsheet'); +$spreadsheet->getActiveSheet() + ->getProtection()->setSheet(true); +$spreadsheet->getActiveSheet() + ->getProtection()->setSort(true); +$spreadsheet->getActiveSheet() + ->getProtection()->setInsertRows(true); +$spreadsheet->getActiveSheet() + ->getProtection()->setFormatCells(true); +``` + +An example on setting cell security: + +``` php +$spreadsheet->getActiveSheet()->getStyle('B1') + ->getProtection() + ->setLocked(\PhpOffice\PhpSpreadsheet\Style\Protection::PROTECTION_UNPROTECTED); +``` + +**Make sure you enable worksheet protection if you need any of the +worksheet protection features!** This can be done using the following +code: + +``` php +$spreadsheet->getActiveSheet()->getProtection()->setSheet(true); +``` + +## Setting data validation on a cell + +Data validation is a powerful feature of Xlsx. It allows to specify an +input filter on the data that can be inserted in a specific cell. This +filter can be a range (i.e. value must be between 0 and 10), a list +(i.e. value must be picked from a list), ... + +The following piece of code only allows numbers between 10 and 20 to be +entered in cell B3: + +``` php +$validation = $spreadsheet->getActiveSheet()->getCell('B3') + ->getDataValidation(); +$validation->setType( \PhpOffice\PhpSpreadsheet\Cell\DataValidation::TYPE_WHOLE ); +$validation->setErrorStyle( \PhpOffice\PhpSpreadsheet\Cell\DataValidation::STYLE_STOP ); +$validation->setAllowBlank(true); +$validation->setShowInputMessage(true); +$validation->setShowErrorMessage(true); +$validation->setErrorTitle('Input error'); +$validation->setError('Number is not allowed!'); +$validation->setPromptTitle('Allowed input'); +$validation->setPrompt('Only numbers between 10 and 20 are allowed.'); +$validation->setFormula1(10); +$validation->setFormula2(20); +``` + +The following piece of code only allows an item picked from a list of +data to be entered in cell B5: + +``` php +$validation = $spreadsheet->getActiveSheet()->getCell('B5') + ->getDataValidation(); +$validation->setType( \PhpOffice\PhpSpreadsheet\Cell\DataValidation::TYPE_LIST ); +$validation->setErrorStyle( \PhpOffice\PhpSpreadsheet\Cell\DataValidation::STYLE_INFORMATION ); +$validation->setAllowBlank(false); +$validation->setShowInputMessage(true); +$validation->setShowErrorMessage(true); +$validation->setShowDropDown(true); +$validation->setErrorTitle('Input error'); +$validation->setError('Value is not in list.'); +$validation->setPromptTitle('Pick from list'); +$validation->setPrompt('Please pick a value from the drop-down list.'); +$validation->setFormula1('"Item A,Item B,Item C"'); +``` + +When using a data validation list like above, make sure you put the list +between `"` and `"` and that you split the items with a comma (`,`). + +It is important to remember that any string participating in an Excel +formula is allowed to be maximum 255 characters (not bytes). This sets a +limit on how many items you can have in the string "Item A,Item B,Item +C". Therefore it is normally a better idea to type the item values +directly in some cell range, say A1:A3, and instead use, say, +`$validation->setFormula1('Sheet!$A$1:$A$3')`. Another benefit is that +the item values themselves can contain the comma `,` character itself. + +If you need data validation on multiple cells, one can clone the +ruleset: + +``` php +$spreadsheet->getActiveSheet()->getCell('B8')->setDataValidation(clone $validation); +``` + +## Setting a column's width + +A column's width can be set using the following code: + +``` php +$spreadsheet->getActiveSheet()->getColumnDimension('D')->setWidth(12); +``` + +If you want PhpSpreadsheet to perform an automatic width calculation, +use the following code. PhpSpreadsheet will approximate the column with +to the width of the widest column value. + +``` php +$spreadsheet->getActiveSheet()->getColumnDimension('B')->setAutoSize(true); +``` + +![08-column-width.png](./images/08-column-width.png) + +The measure for column width in PhpSpreadsheet does **not** correspond +exactly to the measure you may be used to in Microsoft Office Excel. +Column widths are difficult to deal with in Excel, and there are several +measures for the column width. + +1. Inner width in character units +(e.g. 8.43 this is probably what you are familiar with in Excel) +2. Full width in pixels (e.g. 64 pixels) +3. Full width in character units (e.g. 9.140625, value -1 indicates unset width) + +**PhpSpreadsheet always +operates with "3. Full width in character units"** which is in fact the +only value that is stored in any Excel file, hence the most reliable +measure. Unfortunately, **Microsoft Office Excel does not present you +with this measure**. Instead measures 1 and 2 are computed by the +application when the file is opened and these values are presented in +various dialogues and tool tips. + +The character width unit is the width of +a `0` (zero) glyph in the workbooks default font. Therefore column +widths measured in character units in two different workbooks can only +be compared if they have the same default workbook font.If you have some +Excel file and need to know the column widths in measure 3, you can +read the Excel file with PhpSpreadsheet and echo the retrieved values. + +## Show/hide a column + +To set a worksheet's column visibility, you can use the following code. +The first line explicitly shows the column C, the second line hides +column D. + +``` php +$spreadsheet->getActiveSheet()->getColumnDimension('C')->setVisible(true); +$spreadsheet->getActiveSheet()->getColumnDimension('D')->setVisible(false); +``` + +## Group/outline a column + +To group/outline a column, you can use the following code: + +``` php +$spreadsheet->getActiveSheet()->getColumnDimension('E')->setOutlineLevel(1); +``` + +You can also collapse the column. Note that you should also set the +column invisible, otherwise the collapse will not be visible in Excel +2007. + +``` php +$spreadsheet->getActiveSheet()->getColumnDimension('E')->setCollapsed(true); +$spreadsheet->getActiveSheet()->getColumnDimension('E')->setVisible(false); +``` + +Please refer to the section "group/outline a row" for a complete example +on collapsing. + +You can instruct PhpSpreadsheet to add a summary to the right (default), +or to the left. The following code adds the summary to the left: + +``` php +$spreadsheet->getActiveSheet()->setShowSummaryRight(false); +``` + +## Setting a row's height + +A row's height can be set using the following code: + +``` php +$spreadsheet->getActiveSheet()->getRowDimension('10')->setRowHeight(100); +``` + +Excel measures row height in points, where 1 pt is 1/72 of an inch (or +about 0.35mm). The default value is 12.75 pts; and the permitted range +of values is between 0 and 409 pts, where 0 pts is a hidden row. + +## Show/hide a row + +To set a worksheet''s row visibility, you can use the following code. +The following example hides row number 10. + +``` php +$spreadsheet->getActiveSheet()->getRowDimension('10')->setVisible(false); +``` + +Note that if you apply active filters using an AutoFilter, then this +will override any rows that you hide or unhide manually within that +AutoFilter range if you save the file. + +## Group/outline a row + +To group/outline a row, you can use the following code: + +``` php +$spreadsheet->getActiveSheet()->getRowDimension('5')->setOutlineLevel(1); +``` + +You can also collapse the row. Note that you should also set the row +invisible, otherwise the collapse will not be visible in Excel 2007. + +``` php +$spreadsheet->getActiveSheet()->getRowDimension('5')->setCollapsed(true); +$spreadsheet->getActiveSheet()->getRowDimension('5')->setVisible(false); +``` + +Here's an example which collapses rows 50 to 80: + +``` php +for ($i = 51; $i <= 80; $i++) { + $spreadsheet->getActiveSheet()->setCellValue('A' . $i, "FName $i"); + $spreadsheet->getActiveSheet()->setCellValue('B' . $i, "LName $i"); + $spreadsheet->getActiveSheet()->setCellValue('C' . $i, "PhoneNo $i"); + $spreadsheet->getActiveSheet()->setCellValue('D' . $i, "FaxNo $i"); + $spreadsheet->getActiveSheet()->setCellValue('E' . $i, true); + $spreadsheet->getActiveSheet()->getRowDimension($i)->setOutlineLevel(1); + $spreadsheet->getActiveSheet()->getRowDimension($i)->setVisible(false); +} + +$spreadsheet->getActiveSheet()->getRowDimension(81)->setCollapsed(true); +``` + +You can instruct PhpSpreadsheet to add a summary below the collapsible +rows (default), or above. The following code adds the summary above: + +``` php +$spreadsheet->getActiveSheet()->setShowSummaryBelow(false); +``` + +## Merge/unmerge cells + +If you have a big piece of data you want to display in a worksheet, you +can merge two or more cells together, to become one cell. This can be +done using the following code: + +``` php +$spreadsheet->getActiveSheet()->mergeCells('A18:E22'); +``` + +Removing a merge can be done using the unmergeCells method: + +``` php +$spreadsheet->getActiveSheet()->unmergeCells('A18:E22'); +``` + +## Inserting rows/columns + +You can insert/remove rows/columns at a specific position. The following +code inserts 2 new rows, right before row 7: + +``` php +$spreadsheet->getActiveSheet()->insertNewRowBefore(7, 2); +``` + +## Add a drawing to a worksheet + +A drawing is always represented as a separate object, which can be added +to a worksheet. Therefore, you must first instantiate a new +`\PhpOffice\PhpSpreadsheet\Worksheet\Drawing`, and assign its properties a +meaningful value: + +``` php +$drawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing(); +$drawing->setName('Logo'); +$drawing->setDescription('Logo'); +$drawing->setPath('./images/officelogo.jpg'); +$drawing->setHeight(36); +``` + +To add the above drawing to the worksheet, use the following snippet of +code. PhpSpreadsheet creates the link between the drawing and the +worksheet: + +``` php +$drawing->setWorksheet($spreadsheet->getActiveSheet()); +``` + +You can set numerous properties on a drawing, here are some examples: + +``` php +$drawing->setName('Paid'); +$drawing->setDescription('Paid'); +$drawing->setPath('./images/paid.png'); +$drawing->setCoordinates('B15'); +$drawing->setOffsetX(110); +$drawing->setRotation(25); +$drawing->getShadow()->setVisible(true); +$drawing->getShadow()->setDirection(45); +``` + +You can also add images created using GD functions without needing to +save them to disk first as In-Memory drawings. + +``` php +// Use GD to create an in-memory image +$gdImage = @imagecreatetruecolor(120, 20) or die('Cannot Initialize new GD image stream'); +$textColor = imagecolorallocate($gdImage, 255, 255, 255); +imagestring($gdImage, 1, 5, 5, 'Created with PhpSpreadsheet', $textColor); + +// Add the In-Memory image to a worksheet +$drawing = new \PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing(); +$drawing->setName('In-Memory image 1'); +$drawing->setDescription('In-Memory image 1'); +$drawing->setCoordinates('A1'); +$drawing->setImageResource($gdImage); +$drawing->setRenderingFunction( + \PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing::RENDERING_JPEG +); +$drawing->setMimeType(\PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing::MIMETYPE_DEFAULT); +$drawing->setHeight(36); +$drawing->setWorksheet($spreadsheet->getActiveSheet()); +``` + +## Reading Images from a worksheet + +A commonly asked question is how to retrieve the images from a workbook +that has been loaded, and save them as individual image files to disk. + +The following code extracts images from the current active worksheet, +and writes each as a separate file. + +``` php +$i = 0; +foreach ($spreadsheet->getActiveSheet()->getDrawingCollection() as $drawing) { + if ($drawing instanceof \PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing) { + ob_start(); + call_user_func( + $drawing->getRenderingFunction(), + $drawing->getImageResource() + ); + $imageContents = ob_get_contents(); + ob_end_clean(); + switch ($drawing->getMimeType()) { + case \PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing::MIMETYPE_PNG : + $extension = 'png'; + break; + case \PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing::MIMETYPE_GIF: + $extension = 'gif'; + break; + case \PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing::MIMETYPE_JPEG : + $extension = 'jpg'; + break; + } + } else { + $zipReader = fopen($drawing->getPath(),'r'); + $imageContents = ''; + while (!feof($zipReader)) { + $imageContents .= fread($zipReader,1024); + } + fclose($zipReader); + $extension = $drawing->getExtension(); + } + $myFileName = '00_Image_'.++$i.'.'.$extension; + file_put_contents($myFileName,$imageContents); +} +``` + +## Add rich text to a cell + +Adding rich text to a cell can be done using +`\PhpOffice\PhpSpreadsheet\RichText\RichText` instances. Here''s an example, which +creates the following rich text string: + +> This invoice is ***payable within thirty days after the end of the +> month*** unless specified otherwise on the invoice. + +``` php +$richText = new \PhpOffice\PhpSpreadsheet\RichText\RichText(); +$richText->createText('This invoice is '); +$payable = $richText->createTextRun('payable within thirty days after the end of the month'); +$payable->getFont()->setBold(true); +$payable->getFont()->setItalic(true); +$payable->getFont()->setColor( new \PhpOffice\PhpSpreadsheet\Style\Color( \PhpOffice\PhpSpreadsheet\Style\Color::COLOR_DARKGREEN ) ); +$richText->createText(', unless specified otherwise on the invoice.'); +$spreadsheet->getActiveSheet()->getCell('A18')->setValue($richText); +``` + +## Define a named range + +PhpSpreadsheet supports the definition of named ranges. These can be +defined using the following code: + +``` php +// Add some data +$spreadsheet->setActiveSheetIndex(0); +$spreadsheet->getActiveSheet()->setCellValue('A1', 'Firstname:'); +$spreadsheet->getActiveSheet()->setCellValue('A2', 'Lastname:'); +$spreadsheet->getActiveSheet()->setCellValue('B1', 'Maarten'); +$spreadsheet->getActiveSheet()->setCellValue('B2', 'Balliauw'); + +// Define named ranges +$spreadsheet->addNamedRange( new \PhpOffice\PhpSpreadsheet\NamedRange('PersonFN', $spreadsheet->getActiveSheet(), 'B1') ); +$spreadsheet->addNamedRange( new \PhpOffice\PhpSpreadsheet\NamedRange('PersonLN', $spreadsheet->getActiveSheet(), 'B2') ); +``` + +Optionally, a fourth parameter can be passed defining the named range +local (i.e. only usable on the current worksheet). Named ranges are +global by default. + +## Redirect output to a client's web browser + +Sometimes, one really wants to output a file to a client''s browser, +especially when creating spreadsheets on-the-fly. There are some easy +steps that can be followed to do this: + +1. Create your PhpSpreadsheet spreadsheet +2. Output HTTP headers for the type of document you wish to output +3. Use the `\PhpOffice\PhpSpreadsheet\Writer\*` of your choice, and save + to `'php://output'` + +`\PhpOffice\PhpSpreadsheet\Writer\Xlsx` uses temporary storage when +writing to `php://output`. By default, temporary files are stored in the +script's working directory. When there is no access, it falls back to +the operating system's temporary files location. + +**This may not be safe for unauthorized viewing!** Depending on the +configuration of your operating system, temporary storage can be read by +anyone using the same temporary storage folder. When confidentiality of +your document is needed, it is recommended not to use `php://output`. + +### HTTP headers + +Example of a script redirecting an Excel 2007 file to the client's +browser: + +``` php +/* Here there will be some code where you create $spreadsheet */ + +// redirect output to client browser +header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); +header('Content-Disposition: attachment;filename="myfile.xlsx"'); +header('Cache-Control: max-age=0'); + +$writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Xlsx'); +$writer->save('php://output'); +``` + +Example of a script redirecting an Xls file to the client's browser: + +``` php +/* Here there will be some code where you create $spreadsheet */ + +// redirect output to client browser +header('Content-Type: application/vnd.ms-excel'); +header('Content-Disposition: attachment;filename="myfile.xls"'); +header('Cache-Control: max-age=0'); + +$writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Xls'); +$writer->save('php://output'); +``` + +**Caution:** + +Make sure not to include any echo statements or output any other +contents than the Excel file. There should be no whitespace before the +opening `` +tag (which can also be omitted to avoid problems). Make sure that your +script is saved without a BOM (Byte-order mark) because this counts as +echoing output. The same things apply to all included files. Failing to +follow the above guidelines may result in corrupt Excel files arriving +at the client browser, and/or that headers cannot be set by PHP +(resulting in warning messages). + +## Setting the default column width + +Default column width can be set using the following code: + +``` php +$spreadsheet->getActiveSheet()->getDefaultColumnDimension()->setWidth(12); +``` + +## Setting the default row height + +Default row height can be set using the following code: + +``` php +$spreadsheet->getActiveSheet()->getDefaultRowDimension()->setRowHeight(15); +``` + +## Add a GD drawing to a worksheet + +There might be a situation where you want to generate an in-memory image +using GD and add it to a `Spreadsheet` without first having to save this +file to a temporary location. + +Here''s an example which generates an image in memory and adds it to the +active worksheet: + +``` php +// Generate an image +$gdImage = @imagecreatetruecolor(120, 20) or die('Cannot Initialize new GD image stream'); +$textColor = imagecolorallocate($gdImage, 255, 255, 255); +imagestring($gdImage, 1, 5, 5, 'Created with PhpSpreadsheet', $textColor); + +// Add a drawing to the worksheet +$drawing = new \PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing(); +$drawing->setName('Sample image'); +$drawing->setDescription('Sample image'); +$drawing->setImageResource($gdImage); +$drawing->setRenderingFunction(\PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing::RENDERING_JPEG); +$drawing->setMimeType(\PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing::MIMETYPE_DEFAULT); +$drawing->setHeight(36); +$drawing->setWorksheet($spreadsheet->getActiveSheet()); +``` + +## Setting worksheet zoom level + +To set a worksheet's zoom level, the following code can be used: + +``` php +$spreadsheet->getActiveSheet()->getSheetView()->setZoomScale(75); +``` + +Note that zoom level should be in range 10 - 400. + +## Sheet tab color + +Sometimes you want to set a color for sheet tab. For example you can +have a red sheet tab: + +``` php +$worksheet->getTabColor()->setRGB('FF0000'); +``` + +## Creating worksheets in a workbook + +If you need to create more worksheets in the workbook, here is how: + +``` php +$worksheet1 = $spreadsheet->createSheet(); +$worksheet1->setTitle('Another sheet'); +``` + +Think of `createSheet()` as the "Insert sheet" button in Excel. When you +hit that button a new sheet is appended to the existing collection of +worksheets in the workbook. + +## Hidden worksheets (Sheet states) + +Set a worksheet to be **hidden** using this code: + +``` php +$spreadsheet->getActiveSheet() + ->setSheetState(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::SHEETSTATE_HIDDEN); +``` + +Sometimes you may even want the worksheet to be **"very hidden"**. The +available sheet states are : + +- `\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::SHEETSTATE_VISIBLE` +- `\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::SHEETSTATE_HIDDEN` +- `\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::SHEETSTATE_VERYHIDDEN` + +In Excel the sheet state "very hidden" can only be set programmatically, +e.g. with Visual Basic Macro. It is not possible to make such a sheet +visible via the user interface. + +## Right-to-left worksheet + +Worksheets can be set individually whether column `A` should start at +left or right side. Default is left. Here is how to set columns from +right-to-left. + +``` php +// right-to-left worksheet +$spreadsheet->getActiveSheet()->setRightToLeft(true); +``` diff --git a/vendor/phpoffice/phpspreadsheet/docs/topics/settings.md b/vendor/phpoffice/phpspreadsheet/docs/topics/settings.md new file mode 100644 index 0000000..a9aae9f --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/docs/topics/settings.md @@ -0,0 +1,45 @@ +# Configuration Settings + +Once you have included the PhpSpreadsheet files in your script, but +before instantiating a `Spreadsheet` object or loading a workbook file, +there are a number of configuration options that can be set which will +affect the subsequent behaviour of the script. + +## Cell collection caching + +By default, PhpSpreadsheet holds all cell objects in memory, but +you can specify alternatives to reduce memory consumption at the cost of speed. +Read more about [memory saving](./memory_saving.md). + +To enable cell caching, you must provide your own implementation of cache like so: + +``` php +$cache = new MyCustomPsr16Implementation(); + +\PhpOffice\PhpSpreadsheet\Settings::setCache($cache); +``` + +## Language/Locale + +Some localisation elements have been included in PhpSpreadsheet. You can +set a locale by changing the settings. To set the locale to Brazilian +Portuguese you would use: + +``` php +$locale = 'pt_br'; +$validLocale = \PhpOffice\PhpSpreadsheet\Settings::setLocale($locale); +if (!$validLocale) { + echo 'Unable to set locale to ' . $locale . " - reverting to en_us" . PHP_EOL; +} +``` + +- If Brazilian Portuguese language files aren't available, then Portuguese +will be enabled instead +- If Portuguese language files aren't available, +then the `setLocale()` method will return an error, and American English +(en\_us) settings will be used throughout. + +More details of the features available once a locale has been set, +including a list of the languages and locales currently supported, can +be found in [Locale Settings for +Formulae](./recipes.md#locale-settings-for-formulae). diff --git a/vendor/phpoffice/phpspreadsheet/docs/topics/worksheets.md b/vendor/phpoffice/phpspreadsheet/docs/topics/worksheets.md new file mode 100644 index 0000000..f97a006 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/docs/topics/worksheets.md @@ -0,0 +1,128 @@ +# Worksheets + +A worksheet is a collection of cells, formulae, images, graphs, etc. It +holds all data necessary to represent a spreadsheet worksheet. + +When you load a workbook from a spreadsheet file, it will be loaded with +all its existing worksheets (unless you specified that only certain +sheets should be loaded). When you load from non-spreadsheet files (such +as a CSV or HTML file) or from spreadsheet formats that don't identify +worksheets by name (such as SYLK), then a single worksheet called +"WorkSheet1" will be created containing the data from that file. + +When you instantiate a new workbook, PhpSpreadsheet will create it with +a single worksheet called "WorkSheet1". + +The `getSheetCount()` method will tell you the number of worksheets in +the workbook; while the `getSheetNames()` method will return a list of +all worksheets in the workbook, indexed by the order in which their +"tabs" would appear when opened in MS Excel (or other appropriate +Spreadsheet program). + +Individual worksheets can be accessed by name, or by their index +position in the workbook. The index position represents the order that +each worksheet "tab" is shown when the workbook is opened in MS Excel +(or other appropriate Spreadsheet program). To access a sheet by its +index, use the `getSheet()` method. + +``` php +// Get the second sheet in the workbook +// Note that sheets are indexed from 0 +$spreadsheet->getSheet(1); +``` + + +Methods also exist allowing you to reorder the worksheets in the +workbook. + +To access a sheet by name, use the `getSheetByName()` method, specifying +the name of the worksheet that you want to access. + +``` php +// Retrieve the worksheet called 'Worksheet 1' +$spreadsheet->getSheetByName('Worksheet 1'); +``` + +Alternatively, one worksheet is always the currently active worksheet, +and you can access that directly. The currently active worksheet is the +one that will be active when the workbook is opened in MS Excel (or +other appropriate Spreadsheet program). + +``` php +// Retrieve the current active worksheet +$spreadsheet->getActiveSheet(); +``` + +You can change the currently active sheet by index or by name using the +`setActiveSheetIndex()` and `setActiveSheetIndexByName()` methods. + +## Adding a new Worksheet + +You can add a new worksheet to the workbook using the `createSheet()` +method of the `Spreadsheet` object. By default, this will be created as +a new "last" sheet; but you can also specify an index position as an +argument, and the worksheet will be inserted at that position, shuffling +all subsequent worksheets in the collection down a place. + +``` php +$spreadsheet->createSheet(); +``` + +A new worksheet created using this method will be called +`Worksheet` where `` is the lowest number possible to +guarantee that the title is unique. + +Alternatively, you can instantiate a new worksheet (setting the title to +whatever you choose) and then insert it into your workbook using the +`addSheet()` method. + +``` php +// Create a new worksheet called "My Data" +$myWorkSheet = new \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet($spreadsheet, 'My Data'); + +// Attach the "My Data" worksheet as the first worksheet in the Spreadsheet object +$spreadsheet->addSheet($myWorkSheet, 0); +``` + +If you don't specify an index position as the second argument, then the +new worksheet will be added after the last existing worksheet. + +## Copying Worksheets + +Sheets within the same workbook can be copied by creating a clone of the +worksheet you wish to copy, and then using the `addSheet()` method to +insert the clone into the workbook. + +``` php +$clonedWorksheet = clone $spreadsheet->getSheetByName('Worksheet 1'); +$clonedWorksheet->setTitle('Copy of Worksheet 1'); +$spreadsheet->addSheet($clonedWorksheet); +``` + +You can also copy worksheets from one workbook to another, though this +is more complex as PhpSpreadsheet also has to replicate the styling +between the two workbooks. The `addExternalSheet()` method is provided for +this purpose. + + $clonedWorksheet = clone $spreadsheet1->getSheetByName('Worksheet 1'); + $spreadsheet->addExternalSheet($clonedWorksheet); + +In both cases, it is the developer's responsibility to ensure that +worksheet names are not duplicated. PhpSpreadsheet will throw an +exception if you attempt to copy worksheets that will result in a +duplicate name. + +## Removing a Worksheet + +You can delete a worksheet from a workbook, identified by its index +position, using the `removeSheetByIndex()` method + +``` php +$sheetIndex = $spreadsheet->getIndex( + $spreadsheet->getSheetByName('Worksheet 1') +); +$spreadsheet->removeSheetByIndex($sheetIndex); +``` + +If the currently active worksheet is deleted, then the sheet at the +previous index position will become the currently active sheet. diff --git a/vendor/phpoffice/phpspreadsheet/mkdocs.yml b/vendor/phpoffice/phpspreadsheet/mkdocs.yml new file mode 100644 index 0000000..cf87a14 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/mkdocs.yml @@ -0,0 +1,7 @@ +site_name: PhpSpreadsheet Documentation +repo_url: https://github.com/PHPOffice/phpspreadsheet +edit_uri: edit/master/docs/ + +theme: readthedocs +extra_css: + - extra/extra.css diff --git a/vendor/phpoffice/phpspreadsheet/phpunit.xml.dist b/vendor/phpoffice/phpspreadsheet/phpunit.xml.dist new file mode 100644 index 0000000..be3643d --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/phpunit.xml.dist @@ -0,0 +1,23 @@ + + + + + + + ./tests/PhpSpreadsheetTests + + + + ./src + + ./src/PhpSpreadsheet/Shared/JAMA + ./src/PhpSpreadsheet/Writer/PDF + + + + diff --git a/vendor/phpoffice/phpspreadsheet/samples/Autofilter/10_Autofilter.php b/vendor/phpoffice/phpspreadsheet/samples/Autofilter/10_Autofilter.php new file mode 100644 index 0000000..db9de54 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Autofilter/10_Autofilter.php @@ -0,0 +1,101 @@ +log('Create new Spreadsheet object'); +$spreadsheet = new Spreadsheet(); + +// Set document properties +$helper->log('Set document properties'); +$spreadsheet->getProperties()->setCreator('Maarten Balliauw') + ->setLastModifiedBy('Maarten Balliauw') + ->setTitle('PhpSpreadsheet Test Document') + ->setSubject('PhpSpreadsheet Test Document') + ->setDescription('Test document for PhpSpreadsheet, generated using PHP classes.') + ->setKeywords('office PhpSpreadsheet php') + ->setCategory('Test result file'); + +// Create the worksheet +$helper->log('Add data'); +$spreadsheet->setActiveSheetIndex(0); +$spreadsheet->getActiveSheet()->setCellValue('A1', 'Year') + ->setCellValue('B1', 'Quarter') + ->setCellValue('C1', 'Country') + ->setCellValue('D1', 'Sales'); + +$dataArray = [ + ['2010', 'Q1', 'United States', 790], + ['2010', 'Q2', 'United States', 730], + ['2010', 'Q3', 'United States', 860], + ['2010', 'Q4', 'United States', 850], + ['2011', 'Q1', 'United States', 800], + ['2011', 'Q2', 'United States', 700], + ['2011', 'Q3', 'United States', 900], + ['2011', 'Q4', 'United States', 950], + ['2010', 'Q1', 'Belgium', 380], + ['2010', 'Q2', 'Belgium', 390], + ['2010', 'Q3', 'Belgium', 420], + ['2010', 'Q4', 'Belgium', 460], + ['2011', 'Q1', 'Belgium', 400], + ['2011', 'Q2', 'Belgium', 350], + ['2011', 'Q3', 'Belgium', 450], + ['2011', 'Q4', 'Belgium', 500], + ['2010', 'Q1', 'UK', 690], + ['2010', 'Q2', 'UK', 610], + ['2010', 'Q3', 'UK', 620], + ['2010', 'Q4', 'UK', 600], + ['2011', 'Q1', 'UK', 720], + ['2011', 'Q2', 'UK', 650], + ['2011', 'Q3', 'UK', 580], + ['2011', 'Q4', 'UK', 510], + ['2010', 'Q1', 'France', 510], + ['2010', 'Q2', 'France', 490], + ['2010', 'Q3', 'France', 460], + ['2010', 'Q4', 'France', 590], + ['2011', 'Q1', 'France', 620], + ['2011', 'Q2', 'France', 650], + ['2011', 'Q3', 'France', 415], + ['2011', 'Q4', 'France', 570], + ['2010', 'Q1', 'Germany', 720], + ['2010', 'Q2', 'Germany', 680], + ['2010', 'Q3', 'Germany', 640], + ['2010', 'Q4', 'Germany', 660], + ['2011', 'Q1', 'Germany', 680], + ['2011', 'Q2', 'Germany', 620], + ['2011', 'Q3', 'Germany', 710], + ['2011', 'Q4', 'Germany', 690], + ['2010', 'Q1', 'Spain', 510], + ['2010', 'Q2', 'Spain', 490], + ['2010', 'Q3', 'Spain', 470], + ['2010', 'Q4', 'Spain', 420], + ['2011', 'Q1', 'Spain', 460], + ['2011', 'Q2', 'Spain', 390], + ['2011', 'Q3', 'Spain', 430], + ['2011', 'Q4', 'Spain', 415], + ['2010', 'Q1', 'Italy', 440], + ['2010', 'Q2', 'Italy', 410], + ['2010', 'Q3', 'Italy', 420], + ['2010', 'Q4', 'Italy', 450], + ['2011', 'Q1', 'Italy', 430], + ['2011', 'Q2', 'Italy', 370], + ['2011', 'Q3', 'Italy', 350], + ['2011', 'Q4', 'Italy', 335], +]; +$spreadsheet->getActiveSheet()->fromArray($dataArray, null, 'A2'); + +// Set title row bold +$helper->log('Set title row bold'); +$spreadsheet->getActiveSheet()->getStyle('A1:D1')->getFont()->setBold(true); + +// Set autofilter +$helper->log('Set autofilter'); +// Always include the complete filter range! +// Excel does support setting only the caption +// row, but that's not a best practise... +$spreadsheet->getActiveSheet()->setAutoFilter($spreadsheet->getActiveSheet()->calculateWorksheetDimension()); + +// Save +$helper->write($spreadsheet, __FILE__); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Autofilter/10_Autofilter_selection_1.php b/vendor/phpoffice/phpspreadsheet/samples/Autofilter/10_Autofilter_selection_1.php new file mode 100644 index 0000000..464b8c1 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Autofilter/10_Autofilter_selection_1.php @@ -0,0 +1,156 @@ +log('Create new Spreadsheet object'); +$spreadsheet = new Spreadsheet(); + +// Set document properties +$helper->log('Set document properties'); +$spreadsheet->getProperties()->setCreator('Maarten Balliauw') + ->setLastModifiedBy('Maarten Balliauw') + ->setTitle('PhpSpreadsheet Test Document') + ->setSubject('PhpSpreadsheet Test Document') + ->setDescription('Test document for PhpSpreadsheet, generated using PHP classes.') + ->setKeywords('office PhpSpreadsheet php') + ->setCategory('Test result file'); + +// Create the worksheet +$helper->log('Add data'); +$spreadsheet->setActiveSheetIndex(0); +$spreadsheet->getActiveSheet()->setCellValue('A1', 'Financial Year') + ->setCellValue('B1', 'Financial Period') + ->setCellValue('C1', 'Country') + ->setCellValue('D1', 'Date') + ->setCellValue('E1', 'Sales Value') + ->setCellValue('F1', 'Expenditure'); +$startYear = $endYear = $currentYear = date('Y'); +--$startYear; +++$endYear; + +$years = range($startYear, $endYear); +$periods = range(1, 12); +$countries = [ + 'United States', + 'UK', + 'France', + 'Germany', + 'Italy', + 'Spain', + 'Portugal', + 'Japan', +]; + +$row = 2; +foreach ($years as $year) { + foreach ($periods as $period) { + foreach ($countries as $country) { + $endDays = date('t', mktime(0, 0, 0, $period, 1, (int) $year)); + for ($i = 1; $i <= $endDays; ++$i) { + $eDate = Date::formattedPHPToExcel( + $year, + $period, + $i + ); + $value = rand(500, 1000) * (1 + (rand(-1, 1) / 4)); + $salesValue = $invoiceValue = null; + $incomeOrExpenditure = rand(-1, 1); + if ($incomeOrExpenditure == -1) { + $expenditure = rand(-500, -1000) * (1 + (rand(-1, 1) / 4)); + $income = null; + } elseif ($incomeOrExpenditure == 1) { + $expenditure = rand(-500, -1000) * (1 + (rand(-1, 1) / 4)); + $income = rand(500, 1000) * (1 + (rand(-1, 1) / 4)); + } else { + $expenditure = null; + $income = rand(500, 1000) * (1 + (rand(-1, 1) / 4)); + } + $dataArray = [$year, + $period, + $country, + $eDate, + $income, + $expenditure, + ]; + $spreadsheet->getActiveSheet()->fromArray($dataArray, null, 'A' . $row++); + } + } + } +} +--$row; + +// Set styling +$helper->log('Set styling'); +$spreadsheet->getActiveSheet()->getStyle('A1:F1')->getFont()->setBold(true); +$spreadsheet->getActiveSheet()->getStyle('A1:F1')->getAlignment()->setWrapText(true); +$spreadsheet->getActiveSheet()->getColumnDimension('C')->setWidth(12.5); +$spreadsheet->getActiveSheet()->getColumnDimension('D')->setWidth(10.5); +$spreadsheet->getActiveSheet()->getStyle('D2:D' . $row)->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_DATE_YYYYMMDD2); +$spreadsheet->getActiveSheet()->getStyle('E2:F' . $row)->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_CURRENCY_USD_SIMPLE); +$spreadsheet->getActiveSheet()->getColumnDimension('F')->setWidth(14); +$spreadsheet->getActiveSheet()->freezePane('A2'); + +// Set autofilter range +$helper->log('Set autofilter range'); +// Always include the complete filter range! +// Excel does support setting only the caption +// row, but that's not a best practise... +$spreadsheet->getActiveSheet()->setAutoFilter($spreadsheet->getActiveSheet()->calculateWorksheetDimension()); + +// Set active filters +$autoFilter = $spreadsheet->getActiveSheet()->getAutoFilter(); +$helper->log('Set active filters'); +// Filter the Country column on a filter value of countries beginning with the letter U (or Japan) +// We use * as a wildcard, so specify as U* and using a wildcard requires customFilter +$autoFilter->getColumn('C') + ->setFilterType(Column::AUTOFILTER_FILTERTYPE_CUSTOMFILTER) + ->createRule() + ->setRule( + Rule::AUTOFILTER_COLUMN_RULE_EQUAL, + 'u*' + ) + ->setRuleType(Rule::AUTOFILTER_RULETYPE_CUSTOMFILTER); +$autoFilter->getColumn('C') + ->createRule() + ->setRule( + Rule::AUTOFILTER_COLUMN_RULE_EQUAL, + 'japan' + ) + ->setRuleType(Rule::AUTOFILTER_RULETYPE_CUSTOMFILTER); +// Filter the Date column on a filter value of the first day of every period of the current year +// We us a dateGroup ruletype for this, although it is still a standard filter +foreach ($periods as $period) { + $endDate = date('t', mktime(0, 0, 0, $period, 1, (int) $currentYear)); + + $autoFilter->getColumn('D') + ->setFilterType(Column::AUTOFILTER_FILTERTYPE_FILTER) + ->createRule() + ->setRule( + Rule::AUTOFILTER_COLUMN_RULE_EQUAL, + [ + 'year' => $currentYear, + 'month' => $period, + 'day' => $endDate, + ] + ) + ->setRuleType(Rule::AUTOFILTER_RULETYPE_DATEGROUP); +} +// Display only sales values that are blank +// Standard filter, operator equals, and value of NULL +$autoFilter->getColumn('E') + ->setFilterType(Column::AUTOFILTER_FILTERTYPE_FILTER) + ->createRule() + ->setRule( + Rule::AUTOFILTER_COLUMN_RULE_EQUAL, + '' + ); + +// Save +$helper->write($spreadsheet, __FILE__); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Autofilter/10_Autofilter_selection_2.php b/vendor/phpoffice/phpspreadsheet/samples/Autofilter/10_Autofilter_selection_2.php new file mode 100644 index 0000000..1c55a0c --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Autofilter/10_Autofilter_selection_2.php @@ -0,0 +1,148 @@ +log('Create new Spreadsheet object'); +$spreadsheet = new Spreadsheet(); + +// Set document properties +$helper->log('Set document properties'); +$spreadsheet->getProperties()->setCreator('Maarten Balliauw') + ->setLastModifiedBy('Maarten Balliauw') + ->setTitle('PhpSpreadsheet Test Document') + ->setSubject('PhpSpreadsheet Test Document') + ->setDescription('Test document for PhpSpreadsheet, generated using PHP classes.') + ->setKeywords('office PhpSpreadsheet php') + ->setCategory('Test result file'); + +// Create the worksheet +$helper->log('Add data'); +$spreadsheet->setActiveSheetIndex(0); +$spreadsheet->getActiveSheet()->setCellValue('A1', 'Financial Year') + ->setCellValue('B1', 'Financial Period') + ->setCellValue('C1', 'Country') + ->setCellValue('D1', 'Date') + ->setCellValue('E1', 'Sales Value') + ->setCellValue('F1', 'Expenditure'); +$startYear = $endYear = $currentYear = date('Y'); +--$startYear; +++$endYear; + +$years = range($startYear, $endYear); +$periods = range(1, 12); +$countries = [ + 'United States', + 'UK', + 'France', + 'Germany', + 'Italy', + 'Spain', + 'Portugal', + 'Japan', +]; + +$row = 2; +foreach ($years as $year) { + foreach ($periods as $period) { + foreach ($countries as $country) { + $endDays = date('t', mktime(0, 0, 0, $period, 1, (int) $year)); + for ($i = 1; $i <= $endDays; ++$i) { + $eDate = Date::formattedPHPToExcel( + $year, + $period, + $i + ); + $value = rand(500, 1000) * (1 + (rand(-1, 1) / 4)); + $salesValue = $invoiceValue = null; + $incomeOrExpenditure = rand(-1, 1); + if ($incomeOrExpenditure == -1) { + $expenditure = rand(-500, -1000) * (1 + (rand(-1, 1) / 4)); + $income = null; + } elseif ($incomeOrExpenditure == 1) { + $expenditure = rand(-500, -1000) * (1 + (rand(-1, 1) / 4)); + $income = rand(500, 1000) * (1 + (rand(-1, 1) / 4)); + } else { + $expenditure = null; + $income = rand(500, 1000) * (1 + (rand(-1, 1) / 4)); + } + $dataArray = [$year, + $period, + $country, + $eDate, + $income, + $expenditure, + ]; + $spreadsheet->getActiveSheet()->fromArray($dataArray, null, 'A' . $row++); + } + } + } +} +--$row; + +// Set styling +$helper->log('Set styling'); +$spreadsheet->getActiveSheet()->getStyle('A1:F1')->getFont()->setBold(true); +$spreadsheet->getActiveSheet()->getStyle('A1:F1')->getAlignment()->setWrapText(true); +$spreadsheet->getActiveSheet()->getColumnDimension('C')->setWidth(12.5); +$spreadsheet->getActiveSheet()->getColumnDimension('D')->setWidth(10.5); +$spreadsheet->getActiveSheet()->getStyle('D2:D' . $row)->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_DATE_YYYYMMDD2); +$spreadsheet->getActiveSheet()->getStyle('E2:F' . $row)->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_CURRENCY_USD_SIMPLE); +$spreadsheet->getActiveSheet()->getColumnDimension('F')->setWidth(14); +$spreadsheet->getActiveSheet()->freezePane('A2'); + +// Set autofilter range +$helper->log('Set autofilter range'); +// Always include the complete filter range! +// Excel does support setting only the caption +// row, but that's not a best practise... +$spreadsheet->getActiveSheet()->setAutoFilter($spreadsheet->getActiveSheet()->calculateWorksheetDimension()); + +// Set active filters +$autoFilter = $spreadsheet->getActiveSheet()->getAutoFilter(); +$helper->log('Set active filters'); +// Filter the Country column on a filter value of Germany +// As it's just a simple value filter, we can use FILTERTYPE_FILTER +$autoFilter->getColumn('C') + ->setFilterType(Column::AUTOFILTER_FILTERTYPE_FILTER) + ->createRule() + ->setRule( + Rule::AUTOFILTER_COLUMN_RULE_EQUAL, + 'Germany' + ); +// Filter the Date column on a filter value of the year to date +$autoFilter->getColumn('D') + ->setFilterType(Column::AUTOFILTER_FILTERTYPE_DYNAMICFILTER) + ->createRule() + ->setRule( + Rule::AUTOFILTER_COLUMN_RULE_EQUAL, + null, + Rule::AUTOFILTER_RULETYPE_DYNAMIC_YEARTODATE + ) + ->setRuleType(Rule::AUTOFILTER_RULETYPE_DYNAMICFILTER); +// Display only sales values that are between 400 and 600 +$autoFilter->getColumn('E') + ->setFilterType(Column::AUTOFILTER_FILTERTYPE_CUSTOMFILTER) + ->createRule() + ->setRule( + Rule::AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL, + 400 + ) + ->setRuleType(Rule::AUTOFILTER_RULETYPE_CUSTOMFILTER); +$autoFilter->getColumn('E') + ->setJoin(Column::AUTOFILTER_COLUMN_JOIN_AND) + ->createRule() + ->setRule( + Rule::AUTOFILTER_COLUMN_RULE_LESSTHANOREQUAL, + 600 + ) + ->setRuleType(Rule::AUTOFILTER_RULETYPE_CUSTOMFILTER); + +// Save +$helper->write($spreadsheet, __FILE__); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Autofilter/10_Autofilter_selection_display.php b/vendor/phpoffice/phpspreadsheet/samples/Autofilter/10_Autofilter_selection_display.php new file mode 100644 index 0000000..5521155 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Autofilter/10_Autofilter_selection_display.php @@ -0,0 +1,170 @@ +log('Create new Spreadsheet object'); +$spreadsheet = new Spreadsheet(); + +// Set document properties +$helper->log('Set document properties'); +$spreadsheet->getProperties()->setCreator('Maarten Balliauw') + ->setLastModifiedBy('Maarten Balliauw') + ->setTitle('PhpSpreadsheet Test Document') + ->setSubject('PhpSpreadsheet Test Document') + ->setDescription('Test document for PhpSpreadsheet, generated using PHP classes.') + ->setKeywords('office PhpSpreadsheet php') + ->setCategory('Test result file'); + +// Create the worksheet +$helper->log('Add data'); +$spreadsheet->setActiveSheetIndex(0); +$spreadsheet->getActiveSheet()->setCellValue('A1', 'Financial Year') + ->setCellValue('B1', 'Financial Period') + ->setCellValue('C1', 'Country') + ->setCellValue('D1', 'Date') + ->setCellValue('E1', 'Sales Value') + ->setCellValue('F1', 'Expenditure'); +$startYear = $endYear = $currentYear = date('Y'); +--$startYear; +++$endYear; + +$years = range($startYear, $endYear); +$periods = range(1, 12); +$countries = [ + 'United States', + 'UK', + 'France', + 'Germany', + 'Italy', + 'Spain', + 'Portugal', + 'Japan', +]; + +$row = 2; +foreach ($years as $year) { + foreach ($periods as $period) { + foreach ($countries as $country) { + $endDays = date('t', mktime(0, 0, 0, $period, 1, (int) $year)); + for ($i = 1; $i <= $endDays; ++$i) { + $eDate = Date::formattedPHPToExcel( + $year, + $period, + $i + ); + $value = rand(500, 1000) * (1 + (rand(-1, 1) / 4)); + $salesValue = $invoiceValue = null; + $incomeOrExpenditure = rand(-1, 1); + if ($incomeOrExpenditure == -1) { + $expenditure = rand(-500, -1000) * (1 + (rand(-1, 1) / 4)); + $income = null; + } elseif ($incomeOrExpenditure == 1) { + $expenditure = rand(-500, -1000) * (1 + (rand(-1, 1) / 4)); + $income = rand(500, 1000) * (1 + (rand(-1, 1) / 4)); + } else { + $expenditure = null; + $income = rand(500, 1000) * (1 + (rand(-1, 1) / 4)); + } + $dataArray = [$year, + $period, + $country, + $eDate, + $income, + $expenditure, + ]; + $spreadsheet->getActiveSheet()->fromArray($dataArray, null, 'A' . $row++); + } + } + } +} +--$row; + +// Set styling +$helper->log('Set styling'); +$spreadsheet->getActiveSheet()->getStyle('A1:F1')->getFont()->setBold(true); +$spreadsheet->getActiveSheet()->getStyle('A1:F1')->getAlignment()->setWrapText(true); +$spreadsheet->getActiveSheet()->getColumnDimension('C')->setWidth(12.5); +$spreadsheet->getActiveSheet()->getColumnDimension('D')->setWidth(10.5); +$spreadsheet->getActiveSheet()->getStyle('D2:D' . $row)->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_DATE_YYYYMMDD2); +$spreadsheet->getActiveSheet()->getStyle('E2:F' . $row)->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_CURRENCY_USD_SIMPLE); +$spreadsheet->getActiveSheet()->getColumnDimension('F')->setWidth(14); +$spreadsheet->getActiveSheet()->freezePane('A2'); + +// Set autofilter range +$helper->log('Set autofilter range'); +// Always include the complete filter range! +// Excel does support setting only the caption +// row, but that's not a best practise... +$spreadsheet->getActiveSheet()->setAutoFilter($spreadsheet->getActiveSheet()->calculateWorksheetDimension()); + +// Set active filters +$autoFilter = $spreadsheet->getActiveSheet()->getAutoFilter(); +$helper->log('Set active filters'); +// Filter the Country column on a filter value of countries beginning with the letter U (or Japan) +// We use * as a wildcard, so specify as U* and using a wildcard requires customFilter +$autoFilter->getColumn('C') + ->setFilterType(Column::AUTOFILTER_FILTERTYPE_CUSTOMFILTER) + ->createRule() + ->setRule( + Rule::AUTOFILTER_COLUMN_RULE_EQUAL, + 'u*' + ) + ->setRuleType(Rule::AUTOFILTER_RULETYPE_CUSTOMFILTER); +$autoFilter->getColumn('C') + ->createRule() + ->setRule( + Rule::AUTOFILTER_COLUMN_RULE_EQUAL, + 'japan' + ) + ->setRuleType(Rule::AUTOFILTER_RULETYPE_CUSTOMFILTER); +// Filter the Date column on a filter value of the first day of every period of the current year +// We us a dateGroup ruletype for this, although it is still a standard filter +foreach ($periods as $period) { + $endDate = date('t', mktime(0, 0, 0, $period, 1, (int) $currentYear)); + + $autoFilter->getColumn('D') + ->setFilterType(Column::AUTOFILTER_FILTERTYPE_FILTER) + ->createRule() + ->setRule( + Rule::AUTOFILTER_COLUMN_RULE_EQUAL, + [ + 'year' => $currentYear, + 'month' => $period, + 'day' => $endDate, + ] + ) + ->setRuleType(Rule::AUTOFILTER_RULETYPE_DATEGROUP); +} +// Display only sales values that are blank +// Standard filter, operator equals, and value of NULL +$autoFilter->getColumn('E') + ->setFilterType(Column::AUTOFILTER_FILTERTYPE_FILTER) + ->createRule() + ->setRule( + Rule::AUTOFILTER_COLUMN_RULE_EQUAL, + '' + ); + +// Execute filtering +$helper->log('Execute filtering'); +$autoFilter->showHideRows(); + +// Set active sheet index to the first sheet, so Excel opens this as the first sheet +$spreadsheet->setActiveSheetIndex(0); + +// Display Results of filtering +$helper->log('Display filtered rows'); +foreach ($spreadsheet->getActiveSheet()->getRowIterator() as $row) { + if ($spreadsheet->getActiveSheet()->getRowDimension($row->getRowIndex())->getVisible()) { + $helper->log(' Row number - ' . $row->getRowIndex()); + $helper->log($spreadsheet->getActiveSheet()->getCell('C' . $row->getRowIndex())->getValue()); + $helper->log($spreadsheet->getActiveSheet()->getCell('D' . $row->getRowIndex())->getFormattedValue()); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/01_Simple.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/01_Simple.php new file mode 100644 index 0000000..89aca6d --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/01_Simple.php @@ -0,0 +1,65 @@ +log('Create new Spreadsheet object'); +$spreadsheet = new Spreadsheet(); + +// Set document properties +$helper->log('Set document properties'); +$spreadsheet->getProperties() + ->setCreator('Maarten Balliauw') + ->setLastModifiedBy('Maarten Balliauw') + ->setTitle('PhpSpreadsheet Test Document') + ->setSubject('PhpSpreadsheet Test Document') + ->setDescription('Test document for PhpSpreadsheet, generated using PHP classes.') + ->setKeywords('office PhpSpreadsheet php') + ->setCategory('Test result file'); + +// Add some data +$helper->log('Add some data'); +$spreadsheet->setActiveSheetIndex(0) + ->setCellValue('A1', 'Hello') + ->setCellValue('B2', 'world!') + ->setCellValue('C1', 'Hello') + ->setCellValue('D2', 'world!'); + +// Miscellaneous glyphs, UTF-8 +$spreadsheet->setActiveSheetIndex(0) + ->setCellValue('A4', 'Miscellaneous glyphs') + ->setCellValue('A5', 'éàèùâêîôûëïüÿäöüç'); + +$spreadsheet->getActiveSheet() + ->setCellValue('A8', "Hello\nWorld"); +$spreadsheet->getActiveSheet() + ->getRowDimension(8) + ->setRowHeight(-1); +$spreadsheet->getActiveSheet() + ->getStyle('A8') + ->getAlignment() + ->setWrapText(true); + +$value = "-ValueA\n-Value B\n-Value C"; +$spreadsheet->getActiveSheet() + ->setCellValue('A10', $value); +$spreadsheet->getActiveSheet() + ->getRowDimension(10) + ->setRowHeight(-1); +$spreadsheet->getActiveSheet() + ->getStyle('A10') + ->getAlignment() + ->setWrapText(true); +$spreadsheet->getActiveSheet() + ->getStyle('A10') + ->setQuotePrefix(true); + +// Rename worksheet +$helper->log('Rename worksheet'); +$spreadsheet->getActiveSheet() + ->setTitle('Simple'); + +// Save +$helper->write($spreadsheet, __FILE__); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/01_Simple_download_ods.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/01_Simple_download_ods.php new file mode 100644 index 0000000..0c38a00 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/01_Simple_download_ods.php @@ -0,0 +1,61 @@ +isCli()) { + $helper->log('This example should only be run from a Web Browser' . PHP_EOL); + + return; +} + +// Create new Spreadsheet object +$spreadsheet = new Spreadsheet(); + +// Set document properties +$spreadsheet->getProperties()->setCreator('Maarten Balliauw') + ->setLastModifiedBy('Maarten Balliauw') + ->setTitle('Office 2007 XLSX Test Document') + ->setSubject('Office 2007 XLSX Test Document') + ->setDescription('Test document for Office 2007 XLSX, generated using PHP classes.') + ->setKeywords('office 2007 openxml php') + ->setCategory('Test result file'); + +// Add some data +$spreadsheet->setActiveSheetIndex(0) + ->setCellValue('A1', 'Hello') + ->setCellValue('B2', 'world!') + ->setCellValue('C1', 'Hello') + ->setCellValue('D2', 'world!'); + +// Miscellaneous glyphs, UTF-8 +$spreadsheet->setActiveSheetIndex(0) + ->setCellValue('A4', 'Miscellaneous glyphs') + ->setCellValue('A5', 'éàèùâêîôûëïüÿäöüç'); + +// Rename worksheet +$spreadsheet->getActiveSheet()->setTitle('Simple'); + +// Set active sheet index to the first sheet, so Excel opens this as the first sheet +$spreadsheet->setActiveSheetIndex(0); + +// Redirect output to a client’s web browser (Ods) +header('Content-Type: application/vnd.oasis.opendocument.spreadsheet'); +header('Content-Disposition: attachment;filename="01simple.ods"'); +header('Cache-Control: max-age=0'); +// If you're serving to IE 9, then the following may be needed +header('Cache-Control: max-age=1'); + +// If you're serving to IE over SSL, then the following may be needed +header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); // Date in the past +header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); // always modified +header('Cache-Control: cache, must-revalidate'); // HTTP/1.1 +header('Pragma: public'); // HTTP/1.0 + +$writer = IOFactory::createWriter($spreadsheet, 'Ods'); +$writer->save('php://output'); +exit; diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/01_Simple_download_pdf.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/01_Simple_download_pdf.php new file mode 100644 index 0000000..5f3e71d --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/01_Simple_download_pdf.php @@ -0,0 +1,56 @@ +isCli()) { + $helper->log('This example should only be run from a Web Browser' . PHP_EOL); + + return; +} + +// Create new Spreadsheet object +$spreadsheet = new Spreadsheet(); + +// Set document properties +$spreadsheet->getProperties()->setCreator('Maarten Balliauw') + ->setLastModifiedBy('Maarten Balliauw') + ->setTitle('PDF Test Document') + ->setSubject('PDF Test Document') + ->setDescription('Test document for PDF, generated using PHP classes.') + ->setKeywords('pdf php') + ->setCategory('Test result file'); + +// Add some data +$spreadsheet->setActiveSheetIndex(0) + ->setCellValue('A1', 'Hello') + ->setCellValue('B2', 'world!') + ->setCellValue('C1', 'Hello') + ->setCellValue('D2', 'world!'); + +// Miscellaneous glyphs, UTF-8 +$spreadsheet->setActiveSheetIndex(0) + ->setCellValue('A4', 'Miscellaneous glyphs') + ->setCellValue('A5', 'éàèùâêîôûëïüÿäöüç'); + +// Rename worksheet +$spreadsheet->getActiveSheet()->setTitle('Simple'); +$spreadsheet->getActiveSheet()->setShowGridLines(false); + +// Set active sheet index to the first sheet, so Excel opens this as the first sheet +$spreadsheet->setActiveSheetIndex(0); + +IOFactory::registerWriter('Pdf', \PhpOffice\PhpSpreadsheet\Writer\Pdf\Mpdf::class); + +// Redirect output to a client’s web browser (PDF) +header('Content-Type: application/pdf'); +header('Content-Disposition: attachment;filename="01simple.pdf"'); +header('Cache-Control: max-age=0'); + +$writer = IOFactory::createWriter($spreadsheet, 'Pdf'); +$writer->save('php://output'); +exit; diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/01_Simple_download_xls.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/01_Simple_download_xls.php new file mode 100644 index 0000000..46d1202 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/01_Simple_download_xls.php @@ -0,0 +1,61 @@ +isCli()) { + $helper->log('This example should only be run from a Web Browser' . PHP_EOL); + + return; +} + +// Create new Spreadsheet object +$spreadsheet = new Spreadsheet(); + +// Set document properties +$spreadsheet->getProperties()->setCreator('Maarten Balliauw') + ->setLastModifiedBy('Maarten Balliauw') + ->setTitle('Office 2007 XLSX Test Document') + ->setSubject('Office 2007 XLSX Test Document') + ->setDescription('Test document for Office 2007 XLSX, generated using PHP classes.') + ->setKeywords('office 2007 openxml php') + ->setCategory('Test result file'); + +// Add some data +$spreadsheet->setActiveSheetIndex(0) + ->setCellValue('A1', 'Hello') + ->setCellValue('B2', 'world!') + ->setCellValue('C1', 'Hello') + ->setCellValue('D2', 'world!'); + +// Miscellaneous glyphs, UTF-8 +$spreadsheet->setActiveSheetIndex(0) + ->setCellValue('A4', 'Miscellaneous glyphs') + ->setCellValue('A5', 'éàèùâêîôûëïüÿäöüç'); + +// Rename worksheet +$spreadsheet->getActiveSheet()->setTitle('Simple'); + +// Set active sheet index to the first sheet, so Excel opens this as the first sheet +$spreadsheet->setActiveSheetIndex(0); + +// Redirect output to a client’s web browser (Xls) +header('Content-Type: application/vnd.ms-excel'); +header('Content-Disposition: attachment;filename="01simple.xls"'); +header('Cache-Control: max-age=0'); +// If you're serving to IE 9, then the following may be needed +header('Cache-Control: max-age=1'); + +// If you're serving to IE over SSL, then the following may be needed +header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); // Date in the past +header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); // always modified +header('Cache-Control: cache, must-revalidate'); // HTTP/1.1 +header('Pragma: public'); // HTTP/1.0 + +$writer = IOFactory::createWriter($spreadsheet, 'Xls'); +$writer->save('php://output'); +exit; diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/01_Simple_download_xlsx.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/01_Simple_download_xlsx.php new file mode 100644 index 0000000..93efe73 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/01_Simple_download_xlsx.php @@ -0,0 +1,60 @@ +isCli()) { + $helper->log('This example should only be run from a Web Browser' . PHP_EOL); + + return; +} +// Create new Spreadsheet object +$spreadsheet = new Spreadsheet(); + +// Set document properties +$spreadsheet->getProperties()->setCreator('Maarten Balliauw') + ->setLastModifiedBy('Maarten Balliauw') + ->setTitle('Office 2007 XLSX Test Document') + ->setSubject('Office 2007 XLSX Test Document') + ->setDescription('Test document for Office 2007 XLSX, generated using PHP classes.') + ->setKeywords('office 2007 openxml php') + ->setCategory('Test result file'); + +// Add some data +$spreadsheet->setActiveSheetIndex(0) + ->setCellValue('A1', 'Hello') + ->setCellValue('B2', 'world!') + ->setCellValue('C1', 'Hello') + ->setCellValue('D2', 'world!'); + +// Miscellaneous glyphs, UTF-8 +$spreadsheet->setActiveSheetIndex(0) + ->setCellValue('A4', 'Miscellaneous glyphs') + ->setCellValue('A5', 'éàèùâêîôûëïüÿäöüç'); + +// Rename worksheet +$spreadsheet->getActiveSheet()->setTitle('Simple'); + +// Set active sheet index to the first sheet, so Excel opens this as the first sheet +$spreadsheet->setActiveSheetIndex(0); + +// Redirect output to a client’s web browser (Xlsx) +header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); +header('Content-Disposition: attachment;filename="01simple.xlsx"'); +header('Cache-Control: max-age=0'); +// If you're serving to IE 9, then the following may be needed +header('Cache-Control: max-age=1'); + +// If you're serving to IE over SSL, then the following may be needed +header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); // Date in the past +header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); // always modified +header('Cache-Control: cache, must-revalidate'); // HTTP/1.1 +header('Pragma: public'); // HTTP/1.0 + +$writer = IOFactory::createWriter($spreadsheet, 'Xlsx'); +$writer->save('php://output'); +exit; diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/02_Types.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/02_Types.php new file mode 100644 index 0000000..79f109f --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/02_Types.php @@ -0,0 +1,162 @@ +log('Create new Spreadsheet object'); +$spreadsheet = new Spreadsheet(); + +// Set document properties +$helper->log('Set document properties'); +$spreadsheet->getProperties() + ->setCreator('Maarten Balliauw') + ->setLastModifiedBy('Maarten Balliauw') + ->setTitle('Office 2007 XLSX Test Document') + ->setSubject('Office 2007 XLSX Test Document') + ->setDescription('Test document for Office 2007 XLSX, generated using PHP classes.') + ->setKeywords('office 2007 openxml php') + ->setCategory('Test result file'); + +// Set default font +$helper->log('Set default font'); +$spreadsheet->getDefaultStyle() + ->getFont() + ->setName('Arial') + ->setSize(10); + +// Add some data, resembling some different data types +$helper->log('Add some data'); +$spreadsheet->getActiveSheet() + ->setCellValue('A1', 'String') + ->setCellValue('B1', 'Simple') + ->setCellValue('C1', 'PhpSpreadsheet'); + +$spreadsheet->getActiveSheet() + ->setCellValue('A2', 'String') + ->setCellValue('B2', 'Symbols') + ->setCellValue('C2', '!+&=()~§±æþ'); + +$spreadsheet->getActiveSheet() + ->setCellValue('A3', 'String') + ->setCellValue('B3', 'UTF-8') + ->setCellValue('C3', 'Создать MS Excel Книги из PHP скриптов'); + +$spreadsheet->getActiveSheet() + ->setCellValue('A4', 'Number') + ->setCellValue('B4', 'Integer') + ->setCellValue('C4', 12); + +$spreadsheet->getActiveSheet() + ->setCellValue('A5', 'Number') + ->setCellValue('B5', 'Float') + ->setCellValue('C5', 34.56); + +$spreadsheet->getActiveSheet() + ->setCellValue('A6', 'Number') + ->setCellValue('B6', 'Negative') + ->setCellValue('C6', -7.89); + +$spreadsheet->getActiveSheet() + ->setCellValue('A7', 'Boolean') + ->setCellValue('B7', 'True') + ->setCellValue('C7', true); + +$spreadsheet->getActiveSheet() + ->setCellValue('A8', 'Boolean') + ->setCellValue('B8', 'False') + ->setCellValue('C8', false); + +$dateTimeNow = time(); +$spreadsheet->getActiveSheet() + ->setCellValue('A9', 'Date/Time') + ->setCellValue('B9', 'Date') + ->setCellValue('C9', Date::PHPToExcel($dateTimeNow)); +$spreadsheet->getActiveSheet() + ->getStyle('C9') + ->getNumberFormat() + ->setFormatCode(NumberFormat::FORMAT_DATE_YYYYMMDD2); + +$spreadsheet->getActiveSheet() + ->setCellValue('A10', 'Date/Time') + ->setCellValue('B10', 'Time') + ->setCellValue('C10', Date::PHPToExcel($dateTimeNow)); +$spreadsheet->getActiveSheet() + ->getStyle('C10') + ->getNumberFormat() + ->setFormatCode(NumberFormat::FORMAT_DATE_TIME4); + +$spreadsheet->getActiveSheet() + ->setCellValue('A11', 'Date/Time') + ->setCellValue('B11', 'Date and Time') + ->setCellValue('C11', Date::PHPToExcel($dateTimeNow)); +$spreadsheet->getActiveSheet() + ->getStyle('C11') + ->getNumberFormat() + ->setFormatCode(NumberFormat::FORMAT_DATE_DATETIME); + +$spreadsheet->getActiveSheet() + ->setCellValue('A12', 'NULL') + ->setCellValue('C12', null); + +$richText = new RichText(); +$richText->createText('你好 '); + +$payable = $richText->createTextRun('你 好 吗?'); +$payable->getFont()->setBold(true); +$payable->getFont()->setItalic(true); +$payable->getFont()->setColor(new Color(Color::COLOR_DARKGREEN)); + +$richText->createText(', unless specified otherwise on the invoice.'); + +$spreadsheet->getActiveSheet() + ->setCellValue('A13', 'Rich Text') + ->setCellValue('C13', $richText); + +$richText2 = new RichText(); +$richText2->createText("black text\n"); + +$red = $richText2->createTextRun('red text'); +$red->getFont()->setColor(new Color(Color::COLOR_RED)); + +$spreadsheet->getActiveSheet() + ->getCell('C14') + ->setValue($richText2); +$spreadsheet->getActiveSheet() + ->getStyle('C14') + ->getAlignment()->setWrapText(true); + +$spreadsheet->getActiveSheet()->setCellValue('A17', 'Hyperlink'); + +$spreadsheet->getActiveSheet() + ->setCellValue('C17', 'PhpSpreadsheet Web Site'); +$spreadsheet->getActiveSheet() + ->getCell('C17') + ->getHyperlink() + ->setUrl('https://github.com/PHPOffice/PhpSpreadsheet') + ->setTooltip('Navigate to PhpSpreadsheet website'); + +$spreadsheet->getActiveSheet() + ->setCellValue('C18', '=HYPERLINK("mailto:abc@def.com","abc@def.com")'); + +$spreadsheet->getActiveSheet() + ->getColumnDimension('B') + ->setAutoSize(true); +$spreadsheet->getActiveSheet() + ->getColumnDimension('C') + ->setAutoSize(true); + +// Rename worksheet +$helper->log('Rename worksheet'); +$spreadsheet->getActiveSheet()->setTitle('Datatypes'); + +// Set active sheet index to the first sheet, so Excel opens this as the first sheet +$spreadsheet->setActiveSheetIndex(0); + +// Save +$helper->write($spreadsheet, __FILE__); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/03_Formulas.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/03_Formulas.php new file mode 100644 index 0000000..e453823 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/03_Formulas.php @@ -0,0 +1,81 @@ +log('Create new Spreadsheet object'); +$spreadsheet = new Spreadsheet(); + +// Set document properties +$helper->log('Set document properties'); +$spreadsheet->getProperties()->setCreator('Maarten Balliauw') + ->setLastModifiedBy('Maarten Balliauw') + ->setTitle('Office 2007 XLSX Test Document') + ->setSubject('Office 2007 XLSX Test Document') + ->setDescription('Test document for Office 2007 XLSX, generated using PHP classes.') + ->setKeywords('office 2007 openxml php') + ->setCategory('Test result file'); + +// Add some data, we will use some formulas here +$helper->log('Add some data'); +$spreadsheet->getActiveSheet() + ->setCellValue('A5', 'Sum:'); + +$spreadsheet->getActiveSheet()->setCellValue('B1', 'Range #1') + ->setCellValue('B2', 3) + ->setCellValue('B3', 7) + ->setCellValue('B4', 13) + ->setCellValue('B5', '=SUM(B2:B4)'); +$helper->log('Sum of Range #1 is ' . $spreadsheet->getActiveSheet()->getCell('B5')->getCalculatedValue()); + +$spreadsheet->getActiveSheet()->setCellValue('C1', 'Range #2') + ->setCellValue('C2', 5) + ->setCellValue('C3', 11) + ->setCellValue('C4', 17) + ->setCellValue('C5', '=SUM(C2:C4)'); +$helper->log('Sum of Range #2 is ' . $spreadsheet->getActiveSheet()->getCell('C5')->getCalculatedValue()); + +$spreadsheet->getActiveSheet() + ->setCellValue('A7', 'Total of both ranges:'); +$spreadsheet->getActiveSheet() + ->setCellValue('B7', '=SUM(B5:C5)'); +$helper->log('Sum of both Ranges is ' . $spreadsheet->getActiveSheet()->getCell('B7')->getCalculatedValue()); + +$spreadsheet->getActiveSheet() + ->setCellValue('A8', 'Minimum of both ranges:'); +$spreadsheet->getActiveSheet() + ->setCellValue('B8', '=MIN(B2:C4)'); +$helper->log('Minimum value in either Range is ' . $spreadsheet->getActiveSheet()->getCell('B8')->getCalculatedValue()); + +$spreadsheet->getActiveSheet() + ->setCellValue('A9', 'Maximum of both ranges:'); +$spreadsheet->getActiveSheet() + ->setCellValue('B9', '=MAX(B2:C4)'); +$helper->log('Maximum value in either Range is ' . $spreadsheet->getActiveSheet()->getCell('B9')->getCalculatedValue()); + +$spreadsheet->getActiveSheet() + ->setCellValue('A10', 'Average of both ranges:'); +$spreadsheet->getActiveSheet() + ->setCellValue('B10', '=AVERAGE(B2:C4)'); +$helper->log('Average value of both Ranges is ' . $spreadsheet->getActiveSheet()->getCell('B10')->getCalculatedValue()); +$spreadsheet->getActiveSheet() + ->getColumnDimension('A') + ->setAutoSize(true); + +// Rename worksheet +$helper->log('Rename worksheet'); +$spreadsheet->getActiveSheet() + ->setTitle('Formulas'); + +// +// If we set Pre Calculated Formulas to true then PhpSpreadsheet will calculate all formulae in the +// workbook before saving. This adds time and memory overhead, and can cause some problems with formulae +// using functions or features (such as array formulae) that aren't yet supported by the calculation engine +// If the value is false (the default) for the Xlsx Writer, then MS Excel (or the application used to +// open the file) will need to recalculate values itself to guarantee that the correct results are available. +// +//$writer->setPreCalculateFormulas(true); +// Save +$helper->write($spreadsheet, __FILE__); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/04_Printing.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/04_Printing.php new file mode 100644 index 0000000..5e90fc9 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/04_Printing.php @@ -0,0 +1,64 @@ +log('Create new Spreadsheet object'); +$spreadsheet = new Spreadsheet(); + +// Set document properties +$helper->log('Set document properties'); +$spreadsheet->getProperties()->setCreator('Maarten Balliauw') + ->setLastModifiedBy('Maarten Balliauw') + ->setTitle('Office 2007 XLSX Test Document') + ->setSubject('Office 2007 XLSX Test Document') + ->setDescription('Test document for Office 2007 XLSX, generated using PHP classes.') + ->setKeywords('office 2007 openxml php') + ->setCategory('Test result file'); + +// Add some data, we will use printing features +$helper->log('Add some data'); +for ($i = 1; $i < 200; ++$i) { + $spreadsheet->getActiveSheet()->setCellValue('A' . $i, $i); + $spreadsheet->getActiveSheet()->setCellValue('B' . $i, 'Test value'); +} + +// Set header and footer. When no different headers for odd/even are used, odd header is assumed. +$helper->log('Set header/footer'); +$spreadsheet->getActiveSheet() + ->getHeaderFooter() + ->setOddHeader('&L&G&C&HPlease treat this document as confidential!'); +$spreadsheet->getActiveSheet() + ->getHeaderFooter() + ->setOddFooter('&L&B' . $spreadsheet->getProperties()->getTitle() . '&RPage &P of &N'); + +// Add a drawing to the header +$helper->log('Add a drawing to the header'); +$drawing = new HeaderFooterDrawing(); +$drawing->setName('PhpSpreadsheet logo'); +$drawing->setPath(__DIR__ . '/../images/PhpSpreadsheet_logo.png'); +$drawing->setHeight(36); +$spreadsheet->getActiveSheet() + ->getHeaderFooter() + ->addImage($drawing, HeaderFooter::IMAGE_HEADER_LEFT); + +// Set page orientation and size +$helper->log('Set page orientation and size'); +$spreadsheet->getActiveSheet() + ->getPageSetup() + ->setOrientation(PageSetup::ORIENTATION_LANDSCAPE); +$spreadsheet->getActiveSheet() + ->getPageSetup() + ->setPaperSize(PageSetup::PAPERSIZE_A4); + +// Rename worksheet +$helper->log('Rename worksheet'); +$spreadsheet->getActiveSheet()->setTitle('Printing'); + +// Save +$helper->write($spreadsheet, __FILE__); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/05_Feature_demo.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/05_Feature_demo.php new file mode 100644 index 0000000..a85ebbc --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/05_Feature_demo.php @@ -0,0 +1,7 @@ +write($spreadsheet, __FILE__); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/06_Largescale.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/06_Largescale.php new file mode 100644 index 0000000..2e8a3e6 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/06_Largescale.php @@ -0,0 +1,8 @@ +write($spreadsheet, __FILE__); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/07_Reader.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/07_Reader.php new file mode 100644 index 0000000..4d9bd79 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/07_Reader.php @@ -0,0 +1,19 @@ +getTemporaryFilename(); +$writer = new Xlsx($sampleSpreadsheet); +$writer->save($filename); + +$callStartTime = microtime(true); +$spreadsheet = IOFactory::load($filename); +$helper->logRead('Xlsx', $filename, $callStartTime); + +// Save +$helper->write($spreadsheet, __FILE__); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/08_Conditional_formatting.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/08_Conditional_formatting.php new file mode 100644 index 0000000..2f54863 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/08_Conditional_formatting.php @@ -0,0 +1,115 @@ +log('Create new Spreadsheet object'); +$spreadsheet = new Spreadsheet(); + +// Set document properties +$helper->log('Set document properties'); +$spreadsheet->getProperties()->setCreator('Maarten Balliauw') + ->setLastModifiedBy('Maarten Balliauw') + ->setTitle('Office 2007 XLSX Test Document') + ->setSubject('Office 2007 XLSX Test Document') + ->setDescription('Test document for Office 2007 XLSX, generated using PHP classes.') + ->setKeywords('office 2007 openxml php') + ->setCategory('Test result file'); + +// Create a first sheet, representing sales data +$helper->log('Add some data'); +$spreadsheet->setActiveSheetIndex(0); +$spreadsheet->getActiveSheet()->setCellValue('A1', 'Description') + ->setCellValue('B1', 'Amount'); + +$spreadsheet->getActiveSheet()->setCellValue('A2', 'Paycheck received') + ->setCellValue('B2', 100); + +$spreadsheet->getActiveSheet()->setCellValue('A3', 'Cup of coffee bought') + ->setCellValue('B3', -1.5); + +$spreadsheet->getActiveSheet()->setCellValue('A4', 'Cup of coffee bought') + ->setCellValue('B4', -1.5); + +$spreadsheet->getActiveSheet()->setCellValue('A5', 'Cup of tea bought') + ->setCellValue('B5', -1.2); + +$spreadsheet->getActiveSheet()->setCellValue('A6', 'Found some money') + ->setCellValue('B6', 8); + +$spreadsheet->getActiveSheet()->setCellValue('A7', 'Total:') + ->setCellValue('B7', '=SUM(B2:B6)'); + +// Set column widths +$helper->log('Set column widths'); +$spreadsheet->getActiveSheet()->getColumnDimension('A')->setWidth(30); +$spreadsheet->getActiveSheet()->getColumnDimension('B')->setWidth(12); + +// Add conditional formatting +$helper->log('Add conditional formatting'); +$conditional1 = new Conditional(); +$conditional1->setConditionType(Conditional::CONDITION_CELLIS) + ->setOperatorType(Conditional::OPERATOR_BETWEEN) + ->addCondition('200') + ->addCondition('400'); +$conditional1->getStyle()->getFont()->getColor()->setARGB(Color::COLOR_YELLOW); +$conditional1->getStyle()->getFont()->setBold(true); +$conditional1->getStyle()->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_CURRENCY_EUR_SIMPLE); + +$conditional2 = new Conditional(); +$conditional2->setConditionType(Conditional::CONDITION_CELLIS) + ->setOperatorType(Conditional::OPERATOR_LESSTHAN) + ->addCondition('0'); +$conditional2->getStyle()->getFont()->getColor()->setARGB(Color::COLOR_RED); +$conditional2->getStyle()->getFont()->setItalic(true); +$conditional2->getStyle()->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_CURRENCY_EUR_SIMPLE); + +$conditional3 = new Conditional(); +$conditional3->setConditionType(Conditional::CONDITION_CELLIS) + ->setOperatorType(Conditional::OPERATOR_GREATERTHANOREQUAL) + ->addCondition('0'); +$conditional3->getStyle()->getFont()->getColor()->setARGB(Color::COLOR_GREEN); +$conditional3->getStyle()->getFont()->setItalic(true); +$conditional3->getStyle()->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_CURRENCY_EUR_SIMPLE); + +$conditionalStyles = $spreadsheet->getActiveSheet()->getStyle('B2')->getConditionalStyles(); +$conditionalStyles[] = $conditional1; +$conditionalStyles[] = $conditional2; +$conditionalStyles[] = $conditional3; +$spreadsheet->getActiveSheet()->getStyle('B2')->setConditionalStyles($conditionalStyles); + +// duplicate the conditional styles across a range of cells +$helper->log('Duplicate the conditional formatting across a range of cells'); +$spreadsheet->getActiveSheet()->duplicateConditionalStyle( + $spreadsheet->getActiveSheet()->getStyle('B2')->getConditionalStyles(), + 'B3:B7' +); + +// Set fonts +$helper->log('Set fonts'); +$spreadsheet->getActiveSheet()->getStyle('A1:B1')->getFont()->setBold(true); +//$spreadsheet->getActiveSheet()->getStyle('B1')->getFont()->setBold(true); +$spreadsheet->getActiveSheet()->getStyle('A7:B7')->getFont()->setBold(true); +//$spreadsheet->getActiveSheet()->getStyle('B7')->getFont()->setBold(true); +// Set header and footer. When no different headers for odd/even are used, odd header is assumed. +$helper->log('Set header/footer'); +$spreadsheet->getActiveSheet()->getHeaderFooter()->setOddHeader('&L&BPersonal cash register&RPrinted on &D'); +$spreadsheet->getActiveSheet()->getHeaderFooter()->setOddFooter('&L&B' . $spreadsheet->getProperties()->getTitle() . '&RPage &P of &N'); + +// Set page orientation and size +$helper->log('Set page orientation and size'); +$spreadsheet->getActiveSheet()->getPageSetup()->setOrientation(PageSetup::ORIENTATION_PORTRAIT); +$spreadsheet->getActiveSheet()->getPageSetup()->setPaperSize(PageSetup::PAPERSIZE_A4); + +// Rename worksheet +$helper->log('Rename worksheet'); +$spreadsheet->getActiveSheet()->setTitle('Invoice'); + +// Save +$helper->write($spreadsheet, __FILE__); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/08_Conditional_formatting_2.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/08_Conditional_formatting_2.php new file mode 100644 index 0000000..818cdd9 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/08_Conditional_formatting_2.php @@ -0,0 +1,70 @@ +log('Create new Spreadsheet object'); +$spreadsheet = new Spreadsheet(); + +// Set document properties +$helper->log('Set document properties'); +$spreadsheet->getProperties()->setCreator('Maarten Balliauw') + ->setLastModifiedBy('Maarten Balliauw') + ->setTitle('Office 2007 XLSX Test Document') + ->setSubject('Office 2007 XLSX Test Document') + ->setDescription('Test document for Office 2007 XLSX, generated using PHP classes.') + ->setKeywords('office 2007 openxml php') + ->setCategory('Test result file'); + +// Create a first sheet, representing sales data +$helper->log('Add some data'); +$spreadsheet->setActiveSheetIndex(0); +$spreadsheet->getActiveSheet() + ->setCellValue('A1', '-0.5') + ->setCellValue('A2', '-0.25') + ->setCellValue('A3', '0.0') + ->setCellValue('A4', '0.25') + ->setCellValue('A5', '0.5') + ->setCellValue('A6', '0.75') + ->setCellValue('A7', '1.0') + ->setCellValue('A8', '1.25'); + +$spreadsheet->getActiveSheet()->getStyle('A1:A8') + ->getNumberFormat() + ->setFormatCode( + NumberFormat::FORMAT_PERCENTAGE_00 + ); + +// Add conditional formatting +$helper->log('Add conditional formatting'); +$conditional1 = new Conditional(); +$conditional1->setConditionType(Conditional::CONDITION_CELLIS) + ->setOperatorType(Conditional::OPERATOR_LESSTHAN) + ->addCondition('0'); +$conditional1->getStyle()->getFont()->getColor()->setARGB(Color::COLOR_RED); + +$conditional3 = new Conditional(); +$conditional3->setConditionType(Conditional::CONDITION_CELLIS) + ->setOperatorType(Conditional::OPERATOR_GREATERTHANOREQUAL) + ->addCondition('1'); +$conditional3->getStyle()->getFont()->getColor()->setARGB(Color::COLOR_GREEN); + +$conditionalStyles = $spreadsheet->getActiveSheet()->getStyle('A1')->getConditionalStyles(); +$conditionalStyles[] = $conditional1; +$conditionalStyles[] = $conditional3; +$spreadsheet->getActiveSheet()->getStyle('A1')->setConditionalStyles($conditionalStyles); + +// duplicate the conditional styles across a range of cells +$helper->log('Duplicate the conditional formatting across a range of cells'); +$spreadsheet->getActiveSheet()->duplicateConditionalStyle( + $spreadsheet->getActiveSheet()->getStyle('A1')->getConditionalStyles(), + 'A2:A8' +); + +// Save +$helper->write($spreadsheet, __FILE__); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/09_Pagebreaks.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/09_Pagebreaks.php new file mode 100644 index 0000000..ab99a07 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/09_Pagebreaks.php @@ -0,0 +1,63 @@ +log('Create new Spreadsheet object'); +$spreadsheet = new Spreadsheet(); + +// Set document properties +$helper->log('Set document properties'); +$spreadsheet->getProperties()->setCreator('Maarten Balliauw') + ->setLastModifiedBy('Maarten Balliauw') + ->setTitle('Office 2007 XLSX Test Document') + ->setSubject('Office 2007 XLSX Test Document') + ->setDescription('Test document for Office 2007 XLSX, generated using PHP classes.') + ->setKeywords('office 2007 openxml php') + ->setCategory('Test result file'); + +// Create a first sheet +$helper->log('Add data and page breaks'); +$spreadsheet->setActiveSheetIndex(0); +$spreadsheet->getActiveSheet()->setCellValue('A1', 'Firstname') + ->setCellValue('B1', 'Lastname') + ->setCellValue('C1', 'Phone') + ->setCellValue('D1', 'Fax') + ->setCellValue('E1', 'Is Client ?'); + +// Add data +for ($i = 2; $i <= 50; ++$i) { + $spreadsheet->getActiveSheet()->setCellValue('A' . $i, "FName $i"); + $spreadsheet->getActiveSheet()->setCellValue('B' . $i, "LName $i"); + $spreadsheet->getActiveSheet()->setCellValue('C' . $i, "PhoneNo $i"); + $spreadsheet->getActiveSheet()->setCellValue('D' . $i, "FaxNo $i"); + $spreadsheet->getActiveSheet()->setCellValue('E' . $i, true); + + // Add page breaks every 10 rows + if ($i % 10 == 0) { + // Add a page break + $spreadsheet->getActiveSheet()->setBreak('A' . $i, Worksheet::BREAK_ROW); + } +} + +// Set active sheet index to the first sheet, so Excel opens this as the first sheet +$spreadsheet->setActiveSheetIndex(0); +$spreadsheet->getActiveSheet()->setTitle('Printing Options'); + +// Set print headers +$spreadsheet->getActiveSheet() + ->getHeaderFooter()->setOddHeader('&C&24&K0000FF&B&U&A'); +$spreadsheet->getActiveSheet() + ->getHeaderFooter()->setEvenHeader('&C&24&K0000FF&B&U&A'); + +// Set print footers +$spreadsheet->getActiveSheet() + ->getHeaderFooter()->setOddFooter('&R&D &T&C&F&LPage &P / &N'); +$spreadsheet->getActiveSheet() + ->getHeaderFooter()->setEvenFooter('&L&D &T&C&F&RPage &P / &N'); + +// Save +$helper->write($spreadsheet, __FILE__); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/11_Documentsecurity.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/11_Documentsecurity.php new file mode 100644 index 0000000..ec537ab --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/11_Documentsecurity.php @@ -0,0 +1,48 @@ +log('Create new Spreadsheet object'); +$spreadsheet = new Spreadsheet(); + +// Set document properties +$helper->log('Set document properties'); +$spreadsheet->getProperties()->setCreator('Maarten Balliauw') + ->setLastModifiedBy('Maarten Balliauw') + ->setTitle('Office 2007 XLSX Test Document') + ->setSubject('Office 2007 XLSX Test Document') + ->setDescription('Test document for Office 2007 XLSX, generated using PHP classes.') + ->setKeywords('office 2007 openxml php') + ->setCategory('Test result file'); + +// Add some data +$helper->log('Add some data'); +$spreadsheet->setActiveSheetIndex(0); +$spreadsheet->getActiveSheet()->setCellValue('A1', 'Hello'); +$spreadsheet->getActiveSheet()->setCellValue('B2', 'world!'); +$spreadsheet->getActiveSheet()->setCellValue('C1', 'Hello'); +$spreadsheet->getActiveSheet()->setCellValue('D2', 'world!'); + +// Rename worksheet +$helper->log('Rename worksheet'); +$spreadsheet->getActiveSheet()->setTitle('Simple'); + +// Set document security +$helper->log('Set document security'); +$spreadsheet->getSecurity()->setLockWindows(true); +$spreadsheet->getSecurity()->setLockStructure(true); +$spreadsheet->getSecurity()->setWorkbookPassword('PhpSpreadsheet'); + +// Set sheet security +$helper->log('Set sheet security'); +$spreadsheet->getActiveSheet()->getProtection()->setPassword('PhpSpreadsheet'); +$spreadsheet->getActiveSheet()->getProtection()->setSheet(true); // This should be enabled in order to enable any of the following! +$spreadsheet->getActiveSheet()->getProtection()->setSort(true); +$spreadsheet->getActiveSheet()->getProtection()->setInsertRows(true); +$spreadsheet->getActiveSheet()->getProtection()->setFormatCells(true); + +// Save +$helper->write($spreadsheet, __FILE__); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/12_CellProtection.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/12_CellProtection.php new file mode 100644 index 0000000..8a1b2a0 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/12_CellProtection.php @@ -0,0 +1,47 @@ +log('Create new Spreadsheet object'); +$spreadsheet = new Spreadsheet(); + +// Set document properties +$helper->log('Set document properties'); +$spreadsheet->getProperties()->setCreator('Mark Baker') + ->setLastModifiedBy('Mark Baker') + ->setTitle('Office 2007 XLSX Test Document') + ->setSubject('Office 2007 XLSX Test Document') + ->setDescription('Test document for Office 2007 XLSX, generated using PHP classes.') + ->setKeywords('office 2007 openxml php') + ->setCategory('Test result file'); + +// Add some data +$helper->log('Add some data'); +$spreadsheet->setActiveSheetIndex(0); +$spreadsheet->getActiveSheet()->setCellValue('A1', 'Crouching'); +$spreadsheet->getActiveSheet()->setCellValue('B1', 'Tiger'); +$spreadsheet->getActiveSheet()->setCellValue('A2', 'Hidden'); +$spreadsheet->getActiveSheet()->setCellValue('B2', 'Dragon'); + +// Rename worksheet +$helper->log('Rename worksheet'); +$spreadsheet->getActiveSheet()->setTitle('Simple'); + +// Set document security +$helper->log('Set cell protection'); + +// Set sheet security +$helper->log('Set sheet security'); +$spreadsheet->getActiveSheet()->getProtection()->setSheet(true); +$spreadsheet->getActiveSheet() + ->getStyle('A2:B2') + ->getProtection()->setLocked( + Protection::PROTECTION_UNPROTECTED + ); + +// Save +$helper->write($spreadsheet, __FILE__); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/13_Calculation.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/13_Calculation.php new file mode 100644 index 0000000..087b443 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/13_Calculation.php @@ -0,0 +1,176 @@ +log('List implemented functions'); +$calc = Calculation::getInstance(); +print_r($calc->getImplementedFunctionNames()); + +// Create new Spreadsheet object +$helper->log('Create new Spreadsheet object'); +$spreadsheet = new Spreadsheet(); + +// Add some data, we will use some formulas here +$helper->log('Add some data and formulas'); +$spreadsheet->getActiveSheet()->setCellValue('A14', 'Count:') + ->setCellValue('A15', 'Sum:') + ->setCellValue('A16', 'Max:') + ->setCellValue('A17', 'Min:') + ->setCellValue('A18', 'Average:') + ->setCellValue('A19', 'Median:') + ->setCellValue('A20', 'Mode:'); + +$spreadsheet->getActiveSheet()->setCellValue('A22', 'CountA:') + ->setCellValue('A23', 'MaxA:') + ->setCellValue('A24', 'MinA:'); + +$spreadsheet->getActiveSheet()->setCellValue('A26', 'StDev:') + ->setCellValue('A27', 'StDevA:') + ->setCellValue('A28', 'StDevP:') + ->setCellValue('A29', 'StDevPA:'); + +$spreadsheet->getActiveSheet()->setCellValue('A31', 'DevSq:') + ->setCellValue('A32', 'Var:') + ->setCellValue('A33', 'VarA:') + ->setCellValue('A34', 'VarP:') + ->setCellValue('A35', 'VarPA:'); + +$spreadsheet->getActiveSheet()->setCellValue('A37', 'Date:'); + +$spreadsheet->getActiveSheet()->setCellValue('B1', 'Range 1') + ->setCellValue('B2', 2) + ->setCellValue('B3', 8) + ->setCellValue('B4', 10) + ->setCellValue('B5', true) + ->setCellValue('B6', false) + ->setCellValue('B7', 'Text String') + ->setCellValue('B9', '22') + ->setCellValue('B10', 4) + ->setCellValue('B11', 6) + ->setCellValue('B12', 12); + +$spreadsheet->getActiveSheet()->setCellValue('B14', '=COUNT(B2:B12)') + ->setCellValue('B15', '=SUM(B2:B12)') + ->setCellValue('B16', '=MAX(B2:B12)') + ->setCellValue('B17', '=MIN(B2:B12)') + ->setCellValue('B18', '=AVERAGE(B2:B12)') + ->setCellValue('B19', '=MEDIAN(B2:B12)') + ->setCellValue('B20', '=MODE(B2:B12)'); + +$spreadsheet->getActiveSheet()->setCellValue('B22', '=COUNTA(B2:B12)') + ->setCellValue('B23', '=MAXA(B2:B12)') + ->setCellValue('B24', '=MINA(B2:B12)'); + +$spreadsheet->getActiveSheet()->setCellValue('B26', '=STDEV(B2:B12)') + ->setCellValue('B27', '=STDEVA(B2:B12)') + ->setCellValue('B28', '=STDEVP(B2:B12)') + ->setCellValue('B29', '=STDEVPA(B2:B12)'); + +$spreadsheet->getActiveSheet()->setCellValue('B31', '=DEVSQ(B2:B12)') + ->setCellValue('B32', '=VAR(B2:B12)') + ->setCellValue('B33', '=VARA(B2:B12)') + ->setCellValue('B34', '=VARP(B2:B12)') + ->setCellValue('B35', '=VARPA(B2:B12)'); + +$spreadsheet->getActiveSheet()->setCellValue('B37', '=DATE(2007, 12, 21)') + ->setCellValue('B38', '=DATEDIF( DATE(2007, 12, 21), DATE(2007, 12, 22), "D" )') + ->setCellValue('B39', '=DATEVALUE("01-Feb-2006 10:06 AM")') + ->setCellValue('B40', '=DAY( DATE(2006, 1, 2) )') + ->setCellValue('B41', '=DAYS360( DATE(2002, 2, 3), DATE(2005, 5, 31) )'); + +$spreadsheet->getActiveSheet()->setCellValue('C1', 'Range 2') + ->setCellValue('C2', 1) + ->setCellValue('C3', 2) + ->setCellValue('C4', 2) + ->setCellValue('C5', 3) + ->setCellValue('C6', 3) + ->setCellValue('C7', 3) + ->setCellValue('C8', '0') + ->setCellValue('C9', 4) + ->setCellValue('C10', 4) + ->setCellValue('C11', 4) + ->setCellValue('C12', 4); + +$spreadsheet->getActiveSheet()->setCellValue('C14', '=COUNT(C2:C12)') + ->setCellValue('C15', '=SUM(C2:C12)') + ->setCellValue('C16', '=MAX(C2:C12)') + ->setCellValue('C17', '=MIN(C2:C12)') + ->setCellValue('C18', '=AVERAGE(C2:C12)') + ->setCellValue('C19', '=MEDIAN(C2:C12)') + ->setCellValue('C20', '=MODE(C2:C12)'); + +$spreadsheet->getActiveSheet()->setCellValue('C22', '=COUNTA(C2:C12)') + ->setCellValue('C23', '=MAXA(C2:C12)') + ->setCellValue('C24', '=MINA(C2:C12)'); + +$spreadsheet->getActiveSheet()->setCellValue('C26', '=STDEV(C2:C12)') + ->setCellValue('C27', '=STDEVA(C2:C12)') + ->setCellValue('C28', '=STDEVP(C2:C12)') + ->setCellValue('C29', '=STDEVPA(C2:C12)'); + +$spreadsheet->getActiveSheet()->setCellValue('C31', '=DEVSQ(C2:C12)') + ->setCellValue('C32', '=VAR(C2:C12)') + ->setCellValue('C33', '=VARA(C2:C12)') + ->setCellValue('C34', '=VARP(C2:C12)') + ->setCellValue('C35', '=VARPA(C2:C12)'); + +$spreadsheet->getActiveSheet()->setCellValue('D1', 'Range 3') + ->setCellValue('D2', 2) + ->setCellValue('D3', 3) + ->setCellValue('D4', 4); + +$spreadsheet->getActiveSheet()->setCellValue('D14', '=((D2 * D3) + D4) & " should be 10"'); + +$spreadsheet->getActiveSheet()->setCellValue('E12', 'Other functions') + ->setCellValue('E14', '=PI()') + ->setCellValue('E15', '=RAND()') + ->setCellValue('E16', '=RANDBETWEEN(5, 10)'); + +$spreadsheet->getActiveSheet()->setCellValue('E17', 'Count of both ranges:') + ->setCellValue('F17', '=COUNT(B2:C12)'); + +$spreadsheet->getActiveSheet()->setCellValue('E18', 'Total of both ranges:') + ->setCellValue('F18', '=SUM(B2:C12)'); + +$spreadsheet->getActiveSheet()->setCellValue('E19', 'Maximum of both ranges:') + ->setCellValue('F19', '=MAX(B2:C12)'); + +$spreadsheet->getActiveSheet()->setCellValue('E20', 'Minimum of both ranges:') + ->setCellValue('F20', '=MIN(B2:C12)'); + +$spreadsheet->getActiveSheet()->setCellValue('E21', 'Average of both ranges:') + ->setCellValue('F21', '=AVERAGE(B2:C12)'); + +$spreadsheet->getActiveSheet()->setCellValue('E22', 'Median of both ranges:') + ->setCellValue('F22', '=MEDIAN(B2:C12)'); + +$spreadsheet->getActiveSheet()->setCellValue('E23', 'Mode of both ranges:') + ->setCellValue('F23', '=MODE(B2:C12)'); + +// Calculated data +$helper->log('Calculated data'); +for ($col = 'B'; $col != 'G'; ++$col) { + for ($row = 14; $row <= 41; ++$row) { + if ((($formula = $spreadsheet->getActiveSheet()->getCell($col . $row)->getValue()) !== null) && + ($formula[0] == '=')) { + $helper->log('Value of ' . $col . $row . ' [' . $formula . ']: ' . $spreadsheet->getActiveSheet()->getCell($col . $row)->getCalculatedValue()); + } + } +} + +// +// If we set Pre Calculated Formulas to true then PhpSpreadsheet will calculate all formulae in the +// workbook before saving. This adds time and memory overhead, and can cause some problems with formulae +// using functions or features (such as array formulae) that aren't yet supported by the calculation engine +// If the value is false (the default) for the Xlsx Writer, then MS Excel (or the application used to +// open the file) will need to recalculate values itself to guarantee that the correct results are available. +// +//$writer->setPreCalculateFormulas(true); +// Save +$helper->write($spreadsheet, __FILE__); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/13_CalculationCyclicFormulae.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/13_CalculationCyclicFormulae.php new file mode 100644 index 0000000..26e9784 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/13_CalculationCyclicFormulae.php @@ -0,0 +1,33 @@ +log('Create new Spreadsheet object'); +$spreadsheet = new Spreadsheet(); + +// Add some data, we will use some formulas here +$helper->log('Add some data and formulas'); +$spreadsheet->getActiveSheet()->setCellValue('A1', '=B1') + ->setCellValue('A2', '=B2+1') + ->setCellValue('B1', '=A1+1') + ->setCellValue('B2', '=A2'); + +Calculation::getInstance($spreadsheet)->cyclicFormulaCount = 100; + +// Calculated data +$helper->log('Calculated data'); +for ($row = 1; $row <= 2; ++$row) { + for ($col = 'A'; $col != 'C'; ++$col) { + if ((($formula = $spreadsheet->getActiveSheet()->getCell($col . $row)->getValue()) !== null) && + ($formula[0] == '=')) { + $helper->log('Value of ' . $col . $row . ' [' . $formula . ']: ' . $spreadsheet->getActiveSheet()->getCell($col . $row)->getCalculatedValue()); + } + } +} + +// Save +$helper->write($spreadsheet, __FILE__); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/14_Xls.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/14_Xls.php new file mode 100644 index 0000000..ce27eb8 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/14_Xls.php @@ -0,0 +1,13 @@ +getFilename(__FILE__, 'xls'); +$writer = IOFactory::createWriter($spreadsheet, 'Xls'); + +$callStartTime = microtime(true); +$writer->save($filename); +$helper->logWrite($writer, $filename, $callStartTime); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/15_Datavalidation.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/15_Datavalidation.php new file mode 100644 index 0000000..fb76b4d --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/15_Datavalidation.php @@ -0,0 +1,80 @@ +log('Create new Spreadsheet object'); +$spreadsheet = new Spreadsheet(); + +// Set document properties +$helper->log('Set document properties'); +$spreadsheet->getProperties()->setCreator('Maarten Balliauw') + ->setLastModifiedBy('Maarten Balliauw') + ->setTitle('Office 2007 XLSX Test Document') + ->setSubject('Office 2007 XLSX Test Document') + ->setDescription('Test document for Office 2007 XLSX, generated using PHP classes.') + ->setKeywords('office 2007 openxml php') + ->setCategory('Test result file'); + +// Create a first sheet +$helper->log('Add data'); +$spreadsheet->setActiveSheetIndex(0); +$spreadsheet->getActiveSheet()->setCellValue('A1', 'Cell B3 and B5 contain data validation...') + ->setCellValue('A3', 'Number:') + ->setCellValue('B3', '10') + ->setCellValue('A5', 'List:') + ->setCellValue('B5', 'Item A') + ->setCellValue('A7', 'List #2:') + ->setCellValue('B7', 'Item #2') + ->setCellValue('D2', 'Item #1') + ->setCellValue('D3', 'Item #2') + ->setCellValue('D4', 'Item #3') + ->setCellValue('D5', 'Item #4') + ->setCellValue('D6', 'Item #5'); + +// Set data validation +$helper->log('Set data validation'); +$validation = $spreadsheet->getActiveSheet()->getCell('B3')->getDataValidation(); +$validation->setType(DataValidation::TYPE_WHOLE); +$validation->setErrorStyle(DataValidation::STYLE_STOP); +$validation->setAllowBlank(true); +$validation->setShowInputMessage(true); +$validation->setShowErrorMessage(true); +$validation->setErrorTitle('Input error'); +$validation->setError('Only numbers between 10 and 20 are allowed!'); +$validation->setPromptTitle('Allowed input'); +$validation->setPrompt('Only numbers between 10 and 20 are allowed.'); +$validation->setFormula1(10); +$validation->setFormula2(20); + +$validation = $spreadsheet->getActiveSheet()->getCell('B5')->getDataValidation(); +$validation->setType(DataValidation::TYPE_LIST); +$validation->setErrorStyle(DataValidation::STYLE_INFORMATION); +$validation->setAllowBlank(false); +$validation->setShowInputMessage(true); +$validation->setShowErrorMessage(true); +$validation->setShowDropDown(true); +$validation->setErrorTitle('Input error'); +$validation->setError('Value is not in list.'); +$validation->setPromptTitle('Pick from list'); +$validation->setPrompt('Please pick a value from the drop-down list.'); +$validation->setFormula1('"Item A,Item B,Item C"'); // Make sure to put the list items between " and " if your list is simply a comma-separated list of values !!! + +$validation = $spreadsheet->getActiveSheet()->getCell('B7')->getDataValidation(); +$validation->setType(DataValidation::TYPE_LIST); +$validation->setErrorStyle(DataValidation::STYLE_INFORMATION); +$validation->setAllowBlank(false); +$validation->setShowInputMessage(true); +$validation->setShowErrorMessage(true); +$validation->setShowDropDown(true); +$validation->setErrorTitle('Input error'); +$validation->setError('Value is not in list.'); +$validation->setPromptTitle('Pick from list'); +$validation->setPrompt('Please pick a value from the drop-down list.'); +$validation->setFormula1('$D$2:$D$6'); // Make sure NOT to put a range of cells or a formula between " and " + +// Save +$helper->write($spreadsheet, __FILE__); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/16_Csv.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/16_Csv.php new file mode 100644 index 0000000..de753d5 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/16_Csv.php @@ -0,0 +1,41 @@ +log('Write to CSV format'); +/** @var \PhpOffice\PhpSpreadsheet\Writer\Csv $writer */ +$writer = IOFactory::createWriter($spreadsheet, 'Csv')->setDelimiter(',') + ->setEnclosure('"') + ->setSheetIndex(0); + +$callStartTime = microtime(true); +$filename = $helper->getTemporaryFilename('csv'); +$writer->save($filename); +$helper->logWrite($writer, $filename, $callStartTime); + +$helper->log('Read from CSV format'); + +/** @var \PhpOffice\PhpSpreadsheet\Reader\Csv $reader */ +$reader = IOFactory::createReader('Csv')->setDelimiter(',') + ->setEnclosure('"') + ->setSheetIndex(0); + +$callStartTime = microtime(true); +$spreadsheetFromCSV = $reader->load($filename); +$helper->logRead('Csv', $filename, $callStartTime); + +// Write Xlsx +$helper->write($spreadsheetFromCSV, __FILE__, ['Xlsx']); + +// Write CSV +$filenameCSV = $helper->getFilename(__FILE__, 'csv'); +/** @var \PhpOffice\PhpSpreadsheet\Writer\Csv $writerCSV */ +$writerCSV = IOFactory::createWriter($spreadsheetFromCSV, 'Csv'); +$writerCSV->setExcelCompatibility(true); + +$callStartTime = microtime(true); +$writerCSV->save($filenameCSV); +$helper->logWrite($writerCSV, $filenameCSV, $callStartTime); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/17_Html.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/17_Html.php new file mode 100644 index 0000000..b90b721 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/17_Html.php @@ -0,0 +1,13 @@ +getFilename(__FILE__, 'html'); +$writer = IOFactory::createWriter($spreadsheet, 'Html'); + +$callStartTime = microtime(true); +$writer->save($filename); +$helper->logWrite($writer, $filename, $callStartTime); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/18_Extendedcalculation.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/18_Extendedcalculation.php new file mode 100644 index 0000000..c1ec2c0 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/18_Extendedcalculation.php @@ -0,0 +1,69 @@ +log('List implemented functions'); +$calc = Calculation::getInstance(); +print_r($calc->getImplementedFunctionNames()); + +// Create new Spreadsheet object +$helper->log('Create new Spreadsheet object'); +$spreadsheet = new Spreadsheet(); + +// Add some data, we will use some formulas here +$helper->log('Add some data'); +$spreadsheet->getActiveSheet()->setCellValue('A14', 'Count:'); + +$spreadsheet->getActiveSheet()->setCellValue('B1', 'Range 1'); +$spreadsheet->getActiveSheet()->setCellValue('B2', 2); +$spreadsheet->getActiveSheet()->setCellValue('B3', 8); +$spreadsheet->getActiveSheet()->setCellValue('B4', 10); +$spreadsheet->getActiveSheet()->setCellValue('B5', true); +$spreadsheet->getActiveSheet()->setCellValue('B6', false); +$spreadsheet->getActiveSheet()->setCellValue('B7', 'Text String'); +$spreadsheet->getActiveSheet()->setCellValue('B9', '22'); +$spreadsheet->getActiveSheet()->setCellValue('B10', 4); +$spreadsheet->getActiveSheet()->setCellValue('B11', 6); +$spreadsheet->getActiveSheet()->setCellValue('B12', 12); + +$spreadsheet->getActiveSheet()->setCellValue('B14', '=COUNT(B2:B12)'); + +$spreadsheet->getActiveSheet()->setCellValue('C1', 'Range 2'); +$spreadsheet->getActiveSheet()->setCellValue('C2', 1); +$spreadsheet->getActiveSheet()->setCellValue('C3', 2); +$spreadsheet->getActiveSheet()->setCellValue('C4', 2); +$spreadsheet->getActiveSheet()->setCellValue('C5', 3); +$spreadsheet->getActiveSheet()->setCellValue('C6', 3); +$spreadsheet->getActiveSheet()->setCellValue('C7', 3); +$spreadsheet->getActiveSheet()->setCellValue('C8', '0'); +$spreadsheet->getActiveSheet()->setCellValue('C9', 4); +$spreadsheet->getActiveSheet()->setCellValue('C10', 4); +$spreadsheet->getActiveSheet()->setCellValue('C11', 4); +$spreadsheet->getActiveSheet()->setCellValue('C12', 4); + +$spreadsheet->getActiveSheet()->setCellValue('C14', '=COUNT(C2:C12)'); + +$spreadsheet->getActiveSheet()->setCellValue('D1', 'Range 3'); +$spreadsheet->getActiveSheet()->setCellValue('D2', 2); +$spreadsheet->getActiveSheet()->setCellValue('D3', 3); +$spreadsheet->getActiveSheet()->setCellValue('D4', 4); + +$spreadsheet->getActiveSheet()->setCellValue('D5', '=((D2 * D3) + D4) & " should be 10"'); + +$spreadsheet->getActiveSheet()->setCellValue('E1', 'Other functions'); +$spreadsheet->getActiveSheet()->setCellValue('E2', '=PI()'); +$spreadsheet->getActiveSheet()->setCellValue('E3', '=RAND()'); +$spreadsheet->getActiveSheet()->setCellValue('E4', '=RANDBETWEEN(5, 10)'); + +$spreadsheet->getActiveSheet()->setCellValue('E14', 'Count of both ranges:'); +$spreadsheet->getActiveSheet()->setCellValue('F14', '=COUNT(B2:C12)'); + +// Calculated data +$helper->log('Calculated data'); +$helper->log('Value of B14 [=COUNT(B2:B12)]: ' . $spreadsheet->getActiveSheet()->getCell('B14')->getCalculatedValue()); + +$helper->logEndingNotes(); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/19_Namedrange.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/19_Namedrange.php new file mode 100644 index 0000000..d89e1b0 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/19_Namedrange.php @@ -0,0 +1,70 @@ +log('Create new Spreadsheet object'); +$spreadsheet = new Spreadsheet(); + +// Set document properties +$helper->log('Set document properties'); +$spreadsheet->getProperties()->setCreator('Maarten Balliauw') + ->setLastModifiedBy('Maarten Balliauw') + ->setTitle('Office 2007 XLSX Test Document') + ->setSubject('Office 2007 XLSX Test Document') + ->setDescription('Test document for Office 2007 XLSX, generated using PHP classes.') + ->setKeywords('office 2007 openxml php') + ->setCategory('Test result file'); + +// Add some data +$helper->log('Add some data'); +$spreadsheet->setActiveSheetIndex(0); +$spreadsheet->getActiveSheet()->setCellValue('A1', 'Firstname:') + ->setCellValue('A2', 'Lastname:') + ->setCellValue('A3', 'Fullname:') + ->setCellValue('B1', 'Maarten') + ->setCellValue('B2', 'Balliauw') + ->setCellValue('B3', '=B1 & " " & B2'); + +// Define named ranges +$helper->log('Define named ranges'); +$spreadsheet->addNamedRange(new NamedRange('PersonName', $spreadsheet->getActiveSheet(), 'B1')); +$spreadsheet->addNamedRange(new NamedRange('PersonLN', $spreadsheet->getActiveSheet(), 'B2')); + +// Rename named ranges +$helper->log('Rename named ranges'); +$spreadsheet->getNamedRange('PersonName')->setName('PersonFN'); + +// Rename worksheet +$helper->log('Rename worksheet'); +$spreadsheet->getActiveSheet()->setTitle('Person'); + +// Create a new worksheet, after the default sheet +$helper->log('Create new Worksheet object'); +$spreadsheet->createSheet(); + +// Add some data to the second sheet, resembling some different data types +$helper->log('Add some data'); +$spreadsheet->setActiveSheetIndex(1); +$spreadsheet->getActiveSheet()->setCellValue('A1', 'Firstname:') + ->setCellValue('A2', 'Lastname:') + ->setCellValue('A3', 'Fullname:') + ->setCellValue('B1', '=PersonFN') + ->setCellValue('B2', '=PersonLN') + ->setCellValue('B3', '=PersonFN & " " & PersonLN'); + +// Resolve range +$helper->log('Resolve range'); +$helper->log('Cell B1 {=PersonFN}: ' . $spreadsheet->getActiveSheet()->getCell('B1')->getCalculatedValue()); +$helper->log('Cell B3 {=PersonFN & " " & PersonLN}: ' . $spreadsheet->getActiveSheet()->getCell('B3')->getCalculatedValue()); +$helper->log('Cell Person!B1: ' . $spreadsheet->getActiveSheet()->getCell('Person!B1')->getCalculatedValue()); + +// Rename worksheet +$helper->log('Rename worksheet'); +$spreadsheet->getActiveSheet()->setTitle('Person (cloned)'); + +// Save +$helper->write($spreadsheet, __FILE__); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/20_Read_Excel2003XML.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/20_Read_Excel2003XML.php new file mode 100644 index 0000000..44425e2 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/20_Read_Excel2003XML.php @@ -0,0 +1,13 @@ +logRead('Xml', $filename, $callStartTime); + +// Save +$helper->write($spreadsheet, __FILE__); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/20_Read_Gnumeric.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/20_Read_Gnumeric.php new file mode 100644 index 0000000..2d6ce22 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/20_Read_Gnumeric.php @@ -0,0 +1,13 @@ +logRead('Gnumeric', $filename, $callStartTime); + +// Save +$helper->write($spreadsheet, __FILE__); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/20_Read_Ods.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/20_Read_Ods.php new file mode 100644 index 0000000..64f5482 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/20_Read_Ods.php @@ -0,0 +1,13 @@ +logRead('Ods', $filename, $callStartTime); + +// Save +$helper->write($spreadsheet, __FILE__); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/20_Read_Sylk.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/20_Read_Sylk.php new file mode 100644 index 0000000..1a06459 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/20_Read_Sylk.php @@ -0,0 +1,13 @@ +logRead('Slk', $filename, $callStartTime); + +// Save +$helper->write($spreadsheet, __FILE__); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/20_Read_Xls.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/20_Read_Xls.php new file mode 100644 index 0000000..9e5fa01 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/20_Read_Xls.php @@ -0,0 +1,22 @@ +getTemporaryFilename('xls'); +$writer = IOFactory::createWriter($spreadsheet, 'Xls'); +$callStartTime = microtime(true); +$writer->save($filename); +$helper->logWrite($writer, $filename, $callStartTime); + +// Read Xls file +$callStartTime = microtime(true); +$spreadsheet = IOFactory::load($filename); +$helper->logRead('Xls', $filename, $callStartTime); + +// Save +$helper->write($spreadsheet, __FILE__); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/22_Heavily_formatted.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/22_Heavily_formatted.php new file mode 100644 index 0000000..d7ba861 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/22_Heavily_formatted.php @@ -0,0 +1,48 @@ +log('Create new Spreadsheet object'); +$spreadsheet = new Spreadsheet(); + +// Set document properties +$helper->log('Set document properties'); +$spreadsheet->getProperties()->setCreator('Maarten Balliauw') + ->setLastModifiedBy('Maarten Balliauw') + ->setTitle('Office 2007 XLSX Test Document') + ->setSubject('Office 2007 XLSX Test Document') + ->setDescription('Test document for Office 2007 XLSX, generated using PHP classes.') + ->setKeywords('office 2007 openxml php') + ->setCategory('Test result file'); + +// Add some data +$helper->log('Add some data'); +$spreadsheet->setActiveSheetIndex(0); + +$spreadsheet->getActiveSheet()->getStyle('A1:T100')->applyFromArray( + ['fill' => [ + 'fillType' => Fill::FILL_SOLID, + 'color' => ['argb' => 'FFCCFFCC'], + ], + 'borders' => [ + 'bottom' => ['borderStyle' => Border::BORDER_THIN], + 'right' => ['borderStyle' => Border::BORDER_MEDIUM], + ], + ] +); + +$spreadsheet->getActiveSheet()->getStyle('C5:R95')->applyFromArray( + ['fill' => [ + 'fillType' => Fill::FILL_SOLID, + 'color' => ['argb' => 'FFFFFF00'], + ], + ] +); + +// Save +$helper->write($spreadsheet, __FILE__); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/23_Sharedstyles.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/23_Sharedstyles.php new file mode 100644 index 0000000..b539888 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/23_Sharedstyles.php @@ -0,0 +1,59 @@ +log('Create new Spreadsheet object'); +$spreadsheet = new Spreadsheet(); + +// Set document properties +$helper->log('Set document properties'); +$spreadsheet->getProperties()->setCreator('Maarten Balliauw') + ->setLastModifiedBy('Maarten Balliauw') + ->setTitle('Office 2007 XLSX Test Document') + ->setSubject('Office 2007 XLSX Test Document') + ->setDescription('Test document for Office 2007 XLSX, generated using PHP classes.') + ->setKeywords('office 2007 openxml php') + ->setCategory('Test result file'); + +// Add some data +$helper->log('Add some data'); +$spreadsheet->setActiveSheetIndex(0); + +$sharedStyle1 = new Style(); +$sharedStyle2 = new Style(); + +$sharedStyle1->applyFromArray( + ['fill' => [ + 'fillType' => Fill::FILL_SOLID, + 'color' => ['argb' => 'FFCCFFCC'], + ], + 'borders' => [ + 'bottom' => ['borderStyle' => Border::BORDER_THIN], + 'right' => ['borderStyle' => Border::BORDER_MEDIUM], + ], + ] +); + +$sharedStyle2->applyFromArray( + ['fill' => [ + 'fillType' => Fill::FILL_SOLID, + 'color' => ['argb' => 'FFFFFF00'], + ], + 'borders' => [ + 'bottom' => ['borderStyle' => Border::BORDER_THIN], + 'right' => ['borderStyle' => Border::BORDER_MEDIUM], + ], + ] +); + +$spreadsheet->getActiveSheet()->duplicateStyle($sharedStyle1, 'A1:T100'); +$spreadsheet->getActiveSheet()->duplicateStyle($sharedStyle2, 'C5:R95'); + +// Save +$helper->write($spreadsheet, __FILE__); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/24_Readfilter.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/24_Readfilter.php new file mode 100644 index 0000000..844996f --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/24_Readfilter.php @@ -0,0 +1,41 @@ +getTemporaryFilename(); +$callStartTime = microtime(true); +$writer->save($filename); +$helper->logWrite($writer, $filename, $callStartTime); + +class MyReadFilter implements IReadFilter +{ + public function readCell($column, $row, $worksheetName = '') + { + // Read title row and rows 20 - 30 + if ($row == 1 || ($row >= 20 && $row <= 30)) { + return true; + } + + return false; + } +} + +$helper->log('Load from Xlsx file'); +$reader = IOFactory::createReader('Xlsx'); +$reader->setReadFilter(new MyReadFilter()); +$callStartTime = microtime(true); +$spreadsheet = $reader->load($filename); +$helper->logRead('Xlsx', $filename, $callStartTime); +$helper->log('Remove unnecessary rows'); +$spreadsheet->getActiveSheet()->removeRow(2, 18); + +// Save +$helper->write($spreadsheet, __FILE__); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/25_In_memory_image.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/25_In_memory_image.php new file mode 100644 index 0000000..a897486 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/25_In_memory_image.php @@ -0,0 +1,40 @@ +log('Create new Spreadsheet object'); +$spreadsheet = new Spreadsheet(); + +// Set document properties +$helper->log('Set document properties'); +$spreadsheet->getProperties()->setCreator('Maarten Balliauw') + ->setLastModifiedBy('Maarten Balliauw') + ->setTitle('Office 2007 XLSX Test Document') + ->setSubject('Office 2007 XLSX Test Document') + ->setDescription('Test document for Office 2007 XLSX, generated using PHP classes.') + ->setKeywords('office 2007 openxml php') + ->setCategory('Test result file'); + +// Generate an image +$helper->log('Generate an image'); +$gdImage = @imagecreatetruecolor(120, 20) or die('Cannot Initialize new GD image stream'); +$textColor = imagecolorallocate($gdImage, 255, 255, 255); +imagestring($gdImage, 1, 5, 5, 'Created with PhpSpreadsheet', $textColor); + +// Add a drawing to the worksheet +$helper->log('Add a drawing to the worksheet'); +$drawing = new MemoryDrawing(); +$drawing->setName('Sample image'); +$drawing->setDescription('Sample image'); +$drawing->setImageResource($gdImage); +$drawing->setRenderingFunction(MemoryDrawing::RENDERING_JPEG); +$drawing->setMimeType(MemoryDrawing::MIMETYPE_DEFAULT); +$drawing->setHeight(36); +$drawing->setWorksheet($spreadsheet->getActiveSheet()); + +// Save +$helper->write($spreadsheet, __FILE__, ['Xlsx', 'Html']); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/26_Utf8.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/26_Utf8.php new file mode 100644 index 0000000..52a6450 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/26_Utf8.php @@ -0,0 +1,40 @@ +log('Load Xlsx template file'); +$reader = IOFactory::createReader('Xlsx'); +$spreadsheet = $reader->load(__DIR__ . '/../templates/26template.xlsx'); + +// at this point, we could do some manipulations with the template, but we skip this step +$helper->write($spreadsheet, __FILE__, ['Xlsx', 'Xls', 'Html']); + +// Export to PDF (.pdf) +$helper->log('Write to PDF format'); +IOFactory::registerWriter('Pdf', \PhpOffice\PhpSpreadsheet\Writer\Pdf\Dompdf::class); +$helper->write($spreadsheet, __FILE__, ['Pdf']); + +// Remove first two rows with field headers before exporting to CSV +$helper->log('Removing first two heading rows for CSV export'); +$worksheet = $spreadsheet->getActiveSheet(); +$worksheet->removeRow(1, 2); + +// Export to CSV (.csv) +$helper->log('Write to CSV format'); +/** @var \PhpOffice\PhpSpreadsheet\Writer\Csv $writer */ +$writer = IOFactory::createWriter($spreadsheet, 'Csv'); +$filename = $helper->getFilename(__FILE__, 'csv'); +$callStartTime = microtime(true); +$writer->save($filename); +$helper->logWrite($writer, $filename, $callStartTime); + +// Export to CSV with BOM (.csv) +$filename = str_replace('.csv', '-bom.csv', $filename); +$helper->log('Write to CSV format (with BOM)'); +$writer->setUseBOM(true); +$callStartTime = microtime(true); +$writer->save($filename); +$helper->logWrite($writer, $filename, $callStartTime); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/27_Images_Xls.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/27_Images_Xls.php new file mode 100644 index 0000000..4c20a9a --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/27_Images_Xls.php @@ -0,0 +1,13 @@ +log('Load Xlsx template file'); +$reader = IOFactory::createReader('Xls'); +$spreadsheet = $reader->load(__DIR__ . '/../templates/27template.xls'); + +// Save +$helper->write($spreadsheet, __FILE__); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/28_Iterator.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/28_Iterator.php new file mode 100644 index 0000000..4aec7a9 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/28_Iterator.php @@ -0,0 +1,34 @@ +getTemporaryFilename(); +$writer = new Xlsx($sampleSpreadsheet); +$callStartTime = microtime(true); +$writer->save($filename); +$helper->logWrite($writer, $filename, $callStartTime); + +$callStartTime = microtime(true); +$reader = IOFactory::createReader('Xlsx'); +$spreadsheet = $reader->load($filename); +$helper->logRead('Xlsx', $filename, $callStartTime); +$helper->log('Iterate worksheets'); +foreach ($spreadsheet->getWorksheetIterator() as $worksheet) { + $helper->log('Worksheet - ' . $worksheet->getTitle()); + + foreach ($worksheet->getRowIterator() as $row) { + $helper->log(' Row number - ' . $row->getRowIndex()); + + $cellIterator = $row->getCellIterator(); + $cellIterator->setIterateOnlyExistingCells(false); // Loop all cells, even if it is not set + foreach ($cellIterator as $cell) { + if ($cell !== null) { + $helper->log(' Cell - ' . $cell->getCoordinate() . ' - ' . $cell->getCalculatedValue()); + } + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/29_Advanced_value_binder.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/29_Advanced_value_binder.php new file mode 100644 index 0000000..74c16c2 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/29_Advanced_value_binder.php @@ -0,0 +1,132 @@ +log('Set timezone'); +date_default_timezone_set('UTC'); + +// Set value binder +$helper->log('Set value binder'); +Cell::setValueBinder(new AdvancedValueBinder()); + +// Create new Spreadsheet object +$helper->log('Create new Spreadsheet object'); +$spreadsheet = new Spreadsheet(); + +// Set document properties +$helper->log('Set document properties'); +$spreadsheet->getProperties()->setCreator('Maarten Balliauw') + ->setLastModifiedBy('Maarten Balliauw') + ->setTitle('Office 2007 XLSX Test Document') + ->setSubject('Office 2007 XLSX Test Document') + ->setDescription('Test document for Office 2007 XLSX, generated using PHP classes.') + ->setKeywords('office 2007 openxml php') + ->setCategory('Test result file'); + +// Set default font +$helper->log('Set default font'); +$spreadsheet->getDefaultStyle()->getFont()->setName('Arial'); +$spreadsheet->getDefaultStyle()->getFont()->setSize(10); + +// Set column widths +$helper->log('Set column widths'); +$spreadsheet->getActiveSheet()->getColumnDimension('A')->setAutoSize(true); +$spreadsheet->getActiveSheet()->getColumnDimension('B')->setWidth(14); + +// Add some data, resembling some different data types +$helper->log('Add some data'); +$spreadsheet->getActiveSheet()->setCellValue('A1', 'String value:') + ->setCellValue('B1', 'Mark Baker'); + +$spreadsheet->getActiveSheet()->setCellValue('A2', 'Numeric value #1:') + ->setCellValue('B2', 12345); + +$spreadsheet->getActiveSheet()->setCellValue('A3', 'Numeric value #2:') + ->setCellValue('B3', -12.345); + +$spreadsheet->getActiveSheet()->setCellValue('A4', 'Numeric value #3:') + ->setCellValue('B4', .12345); + +$spreadsheet->getActiveSheet()->setCellValue('A5', 'Numeric value #4:') + ->setCellValue('B5', '12345'); + +$spreadsheet->getActiveSheet()->setCellValue('A6', 'Numeric value #5:') + ->setCellValue('B6', '1.2345'); + +$spreadsheet->getActiveSheet()->setCellValue('A7', 'Numeric value #6:') + ->setCellValue('B7', '.12345'); + +$spreadsheet->getActiveSheet()->setCellValue('A8', 'Numeric value #7:') + ->setCellValue('B8', '1.234e-5'); + +$spreadsheet->getActiveSheet()->setCellValue('A9', 'Numeric value #8:') + ->setCellValue('B9', '-1.234e+5'); + +$spreadsheet->getActiveSheet()->setCellValue('A10', 'Boolean value:') + ->setCellValue('B10', 'TRUE'); + +$spreadsheet->getActiveSheet()->setCellValue('A11', 'Percentage value #1:') + ->setCellValue('B11', '10%'); + +$spreadsheet->getActiveSheet()->setCellValue('A12', 'Percentage value #2:') + ->setCellValue('B12', '12.5%'); + +$spreadsheet->getActiveSheet()->setCellValue('A13', 'Fraction value #1:') + ->setCellValue('B13', '-1/2'); + +$spreadsheet->getActiveSheet()->setCellValue('A14', 'Fraction value #2:') + ->setCellValue('B14', '3 1/2'); + +$spreadsheet->getActiveSheet()->setCellValue('A15', 'Fraction value #3:') + ->setCellValue('B15', '-12 3/4'); + +$spreadsheet->getActiveSheet()->setCellValue('A16', 'Fraction value #4:') + ->setCellValue('B16', '13/4'); + +$spreadsheet->getActiveSheet()->setCellValue('A17', 'Currency value #1:') + ->setCellValue('B17', '$12345'); + +$spreadsheet->getActiveSheet()->setCellValue('A18', 'Currency value #2:') + ->setCellValue('B18', '$12345.67'); + +$spreadsheet->getActiveSheet()->setCellValue('A19', 'Currency value #3:') + ->setCellValue('B19', '$12,345.67'); + +$spreadsheet->getActiveSheet()->setCellValue('A20', 'Date value #1:') + ->setCellValue('B20', '21 December 1983'); + +$spreadsheet->getActiveSheet()->setCellValue('A21', 'Date value #2:') + ->setCellValue('B21', '19-Dec-1960'); + +$spreadsheet->getActiveSheet()->setCellValue('A22', 'Date value #3:') + ->setCellValue('B22', '07/12/1982'); + +$spreadsheet->getActiveSheet()->setCellValue('A23', 'Date value #4:') + ->setCellValue('B23', '24-11-1950'); + +$spreadsheet->getActiveSheet()->setCellValue('A24', 'Date value #5:') + ->setCellValue('B24', '17-Mar'); + +$spreadsheet->getActiveSheet()->setCellValue('A25', 'Time value #1:') + ->setCellValue('B25', '01:30'); + +$spreadsheet->getActiveSheet()->setCellValue('A26', 'Time value #2:') + ->setCellValue('B26', '01:30:15'); + +$spreadsheet->getActiveSheet()->setCellValue('A27', 'Date/Time value:') + ->setCellValue('B27', '19-Dec-1960 01:30'); + +$spreadsheet->getActiveSheet()->setCellValue('A28', 'Formula:') + ->setCellValue('B28', '=SUM(B2:B9)'); + +// Rename worksheet +$helper->log('Rename worksheet'); +$spreadsheet->getActiveSheet()->setTitle('Advanced value binder'); + +// Save +$helper->write($spreadsheet, __FILE__); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/30_Template.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/30_Template.php new file mode 100644 index 0000000..b70c18b --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/30_Template.php @@ -0,0 +1,43 @@ +log('Load from Xls template'); +$reader = IOFactory::createReader('Xls'); +$spreadsheet = $reader->load(__DIR__ . '/../templates/30template.xls'); + +$helper->log('Add new data to the template'); +$data = [['title' => 'Excel for dummies', + 'price' => 17.99, + 'quantity' => 2, + ], + ['title' => 'PHP for dummies', + 'price' => 15.99, + 'quantity' => 1, + ], + ['title' => 'Inside OOP', + 'price' => 12.95, + 'quantity' => 1, + ], +]; + +$spreadsheet->getActiveSheet()->setCellValue('D1', Date::PHPToExcel(time())); + +$baseRow = 5; +foreach ($data as $r => $dataRow) { + $row = $baseRow + $r; + $spreadsheet->getActiveSheet()->insertNewRowBefore($row, 1); + + $spreadsheet->getActiveSheet()->setCellValue('A' . $row, $r + 1) + ->setCellValue('B' . $row, $dataRow['title']) + ->setCellValue('C' . $row, $dataRow['price']) + ->setCellValue('D' . $row, $dataRow['quantity']) + ->setCellValue('E' . $row, '=C' . $row . '*D' . $row); +} +$spreadsheet->getActiveSheet()->removeRow($baseRow - 1, 1); + +// Save +$helper->write($spreadsheet, __FILE__); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/31_Document_properties_write.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/31_Document_properties_write.php new file mode 100644 index 0000000..bdce86d --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/31_Document_properties_write.php @@ -0,0 +1,68 @@ +load($inputFileName); +$helper->logRead($inputFileType, $inputFileName, $callStartTime); + +$helper->log('Adjust properties'); +$spreadsheet->getProperties()->setTitle('Office 2007 XLSX Test Document') + ->setSubject('Office 2007 XLSX Test Document') + ->setDescription('Test XLSX document, generated using PhpSpreadsheet') + ->setKeywords('office 2007 openxml php'); + +// Save Excel 2007 file +$filename = $helper->getFilename(__FILE__); +$writer = IOFactory::createWriter($spreadsheet, 'Xlsx'); +$callStartTime = microtime(true); +$writer->save($filename); +$helper->logWrite($writer, $filename, $callStartTime); + +$helper->logEndingNotes(); + +// Reread File +$helper->log('Reread Xlsx file'); +$spreadsheetRead = IOFactory::load($filename); + +// Set properties +$helper->log('Get properties'); + +$helper->log('Core Properties:'); +$helper->log(' Created by - ' . $spreadsheet->getProperties()->getCreator()); +$helper->log(' Created on - ' . date('d-M-Y' . $spreadsheet->getProperties()->getCreated()) . ' at ' . date('H:i:s' . $spreadsheet->getProperties()->getCreated())); +$helper->log(' Last Modified by - ' . $spreadsheet->getProperties()->getLastModifiedBy()); +$helper->log(' Last Modified on - ' . date('d-M-Y' . $spreadsheet->getProperties()->getModified()) . ' at ' . date('H:i:s' . $spreadsheet->getProperties()->getModified())); +$helper->log(' Title - ' . $spreadsheet->getProperties()->getTitle()); +$helper->log(' Subject - ' . $spreadsheet->getProperties()->getSubject()); +$helper->log(' Description - ' . $spreadsheet->getProperties()->getDescription()); +$helper->log(' Keywords: - ' . $spreadsheet->getProperties()->getKeywords()); + +$helper->log('Extended (Application) Properties:'); +$helper->log(' Category - ' . $spreadsheet->getProperties()->getCategory()); +$helper->log(' Company - ' . $spreadsheet->getProperties()->getCompany()); +$helper->log(' Manager - ' . $spreadsheet->getProperties()->getManager()); + +$helper->log('Custom Properties:'); +$customProperties = $spreadsheet->getProperties()->getCustomProperties(); +foreach ($customProperties as $customProperty) { + $propertyValue = $spreadsheet->getProperties()->getCustomPropertyValue($customProperty); + $propertyType = $spreadsheet->getProperties()->getCustomPropertyType($customProperty); + if ($propertyType == Properties::PROPERTY_TYPE_DATE) { + $formattedValue = date('d-M-Y H:i:s', (int) $propertyValue); + } elseif ($propertyType == Properties::PROPERTY_TYPE_BOOLEAN) { + $formattedValue = $propertyValue ? 'TRUE' : 'FALSE'; + } else { + $formattedValue = $propertyValue; + } + $helper->log(' ' . $customProperty . ' - (' . $propertyType . ') - ' . $formattedValue); +} + +$helper->logEndingNotes(); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/31_Document_properties_write_xls.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/31_Document_properties_write_xls.php new file mode 100644 index 0000000..f3a48f9 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/31_Document_properties_write_xls.php @@ -0,0 +1,68 @@ +load($inputFileName); +$helper->logRead($inputFileType, $inputFileName, $callStartTime); + +$helper->log('Adjust properties'); +$spreadsheet->getProperties()->setTitle('Office 95 XLS Test Document') + ->setSubject('Office 95 XLS Test Document') + ->setDescription('Test XLS document, generated using PhpSpreadsheet') + ->setKeywords('office 95 biff php'); + +// Save Excel 95 file +$filename = $helper->getFilename(__FILE__, 'xls'); +$writer = IOFactory::createWriter($spreadsheet, 'Xls'); +$callStartTime = microtime(true); +$writer->save($filename); +$helper->logWrite($writer, $filename, $callStartTime); + +$helper->logEndingNotes(); + +// Reread File +$helper->log('Reread Xls file'); +$spreadsheetRead = IOFactory::load($filename); + +// Set properties +$helper->log('Get properties'); + +$helper->log('Core Properties:'); +$helper->log(' Created by - ' . $spreadsheet->getProperties()->getCreator()); +$helper->log(' Created on - ' . date('d-M-Y' . $spreadsheet->getProperties()->getCreated()) . ' at ' . date('H:i:s' . $spreadsheet->getProperties()->getCreated())); +$helper->log(' Last Modified by - ' . $spreadsheet->getProperties()->getLastModifiedBy()); +$helper->log(' Last Modified on - ' . date('d-M-Y' . $spreadsheet->getProperties()->getModified()) . ' at ' . date('H:i:s' . $spreadsheet->getProperties()->getModified())); +$helper->log(' Title - ' . $spreadsheet->getProperties()->getTitle()); +$helper->log(' Subject - ' . $spreadsheet->getProperties()->getSubject()); +$helper->log(' Description - ' . $spreadsheet->getProperties()->getDescription()); +$helper->log(' Keywords: - ' . $spreadsheet->getProperties()->getKeywords()); + +$helper->log('Extended (Application) Properties:'); +$helper->log(' Category - ' . $spreadsheet->getProperties()->getCategory()); +$helper->log(' Company - ' . $spreadsheet->getProperties()->getCompany()); +$helper->log(' Manager - ' . $spreadsheet->getProperties()->getManager()); + +$helper->log('Custom Properties:'); +$customProperties = $spreadsheet->getProperties()->getCustomProperties(); +foreach ($customProperties as $customProperty) { + $propertyValue = $spreadsheet->getProperties()->getCustomPropertyValue($customProperty); + $propertyType = $spreadsheet->getProperties()->getCustomPropertyType($customProperty); + if ($propertyType == Properties::PROPERTY_TYPE_DATE) { + $formattedValue = date('d-M-Y H:i:s', (int) $propertyValue); + } elseif ($propertyType == Properties::PROPERTY_TYPE_BOOLEAN) { + $formattedValue = $propertyValue ? 'TRUE' : 'FALSE'; + } else { + $formattedValue = $propertyValue; + } + $helper->log(' ' . $customProperty . ' - (' . $propertyType . ') - ' . $formattedValue); +} + +$helper->logEndingNotes(); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/37_Page_layout_view.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/37_Page_layout_view.php new file mode 100644 index 0000000..d9bac80 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/37_Page_layout_view.php @@ -0,0 +1,32 @@ +log('Create new Spreadsheet object'); +$spreadsheet = new Spreadsheet(); + +// Set document properties +$helper->log('Set document properties'); +$spreadsheet->getProperties()->setCreator('PHPOffice') + ->setLastModifiedBy('PHPOffice') + ->setTitle('PhpSpreadsheet Test Document') + ->setSubject('PhpSpreadsheet Test Document') + ->setDescription('Test document for PhpSpreadsheet, generated using PHP classes.') + ->setKeywords('Office PhpSpreadsheet php') + ->setCategory('Test result file'); + +// Add some data +$helper->log('Add some data'); +$spreadsheet->setActiveSheetIndex(0) + ->setCellValue('A1', 'Hello') + ->setCellValue('B2', 'world!'); + +// Set the page layout view as page layout +$spreadsheet->getActiveSheet()->getSheetView()->setView(SheetView::SHEETVIEW_PAGE_LAYOUT); + +// Save +$helper->write($spreadsheet, __FILE__); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/38_Clone_worksheet.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/38_Clone_worksheet.php new file mode 100644 index 0000000..83f2d9c --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/38_Clone_worksheet.php @@ -0,0 +1,57 @@ +log('Create new Spreadsheet object'); +$spreadsheet = new Spreadsheet(); + +// Set document properties +$helper->log('Set document properties'); +$spreadsheet->getProperties()->setCreator('Maarten Balliauw') + ->setLastModifiedBy('Maarten Balliauw') + ->setTitle('PhpSpreadsheet Test Document') + ->setSubject('PhpSpreadsheet Test Document') + ->setDescription('Test document for PhpSpreadsheet, generated using PHP classes.') + ->setKeywords('office PhpSpreadsheet php') + ->setCategory('Test result file'); + +// Add some data +$helper->log('Add some data'); +$spreadsheet->setActiveSheetIndex(0) + ->setCellValue('A1', 'Hello') + ->setCellValue('B2', 'world!') + ->setCellValue('C1', 'Hello') + ->setCellValue('D2', 'world!'); + +// Miscellaneous glyphs, UTF-8 +$spreadsheet->setActiveSheetIndex(0) + ->setCellValue('A4', 'Miscellaneous glyphs') + ->setCellValue('A5', 'éàèùâêîôûëïüÿäöüç'); + +$spreadsheet->getActiveSheet()->setCellValue('A8', "Hello\nWorld"); +$spreadsheet->getActiveSheet()->getRowDimension(8)->setRowHeight(-1); +$spreadsheet->getActiveSheet()->getStyle('A8')->getAlignment()->setWrapText(true); + +// Rename worksheet +$helper->log('Rename worksheet'); +$spreadsheet->getActiveSheet()->setTitle('Simple'); + +// Clone worksheet +$helper->log('Clone worksheet'); +$clonedSheet = clone $spreadsheet->getActiveSheet(); +$clonedSheet + ->setCellValue('A1', 'Goodbye') + ->setCellValue('A2', 'cruel') + ->setCellValue('C1', 'Goodbye') + ->setCellValue('C2', 'cruel'); + +// Rename cloned worksheet +$helper->log('Rename cloned worksheet'); +$clonedSheet->setTitle('Simple Clone'); +$spreadsheet->addSheet($clonedSheet); + +// Save +$helper->write($spreadsheet, __FILE__); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/39_Dropdown.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/39_Dropdown.php new file mode 100644 index 0000000..e34d73e --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/39_Dropdown.php @@ -0,0 +1,129 @@ +log('Create new Spreadsheet object'); +$spreadsheet = new Spreadsheet(); + +// Set document properties +$helper->log('Set document properties'); +$spreadsheet->getProperties() + ->setCreator('PHPOffice') + ->setLastModifiedBy('PHPOffice') + ->setTitle('PhpSpreadsheet Test Document') + ->setSubject('PhpSpreadsheet Test Document') + ->setDescription('Test document for PhpSpreadsheet, generated using PHP classes.') + ->setKeywords('Office PhpSpreadsheet php') + ->setCategory('Test result file'); +function transpose($value) +{ + return [$value]; +} + +// Add some data +$continentColumn = 'D'; +$column = 'F'; + +// Set data for dropdowns +$continents = glob(__DIR__ . '/data/continents/*'); +foreach ($continents as $key => $filename) { + $continent = pathinfo($filename, PATHINFO_FILENAME); + $helper->log("Loading $continent"); + $continent = str_replace(' ', '_', $continent); + $countries = file($filename, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + $countryCount = count($countries); + + // Transpose $countries from a row to a column array + $countries = array_map('transpose', $countries); + $spreadsheet->getActiveSheet() + ->fromArray($countries, null, $column . '1'); + $spreadsheet->addNamedRange( + new NamedRange( + $continent, + $spreadsheet->getActiveSheet(), + $column . '1:' . $column . $countryCount + ) + ); + $spreadsheet->getActiveSheet() + ->getColumnDimension($column) + ->setVisible(false); + + $spreadsheet->getActiveSheet() + ->setCellValue($continentColumn . ($key + 1), $continent); + + ++$column; +} + +// Hide the dropdown data +$spreadsheet->getActiveSheet() + ->getColumnDimension($continentColumn) + ->setVisible(false); + +$spreadsheet->addNamedRange( + new NamedRange( + 'Continents', + $spreadsheet->getActiveSheet(), + $continentColumn . '1:' . $continentColumn . count($continents) + ) +); + +// Set selection cells +$spreadsheet->getActiveSheet() + ->setCellValue('A1', 'Continent:'); +$spreadsheet->getActiveSheet() + ->setCellValue('B1', 'Select continent'); +$spreadsheet->getActiveSheet() + ->setCellValue('B3', '=' . $column . 1); +$spreadsheet->getActiveSheet() + ->setCellValue('B3', 'Select country'); +$spreadsheet->getActiveSheet() + ->getStyle('A1:A3') + ->getFont()->setBold(true); + +// Set linked validators +$validation = $spreadsheet->getActiveSheet() + ->getCell('B1') + ->getDataValidation(); +$validation->setType(DataValidation::TYPE_LIST) + ->setErrorStyle(DataValidation::STYLE_INFORMATION) + ->setAllowBlank(false) + ->setShowInputMessage(true) + ->setShowErrorMessage(true) + ->setShowDropDown(true) + ->setErrorTitle('Input error') + ->setError('Continent is not in the list.') + ->setPromptTitle('Pick from the list') + ->setPrompt('Please pick a continent from the drop-down list.') + ->setFormula1('=Continents'); + +$spreadsheet->getActiveSheet() + ->setCellValue('A3', 'Country:'); +$spreadsheet->getActiveSheet() + ->getStyle('A3') + ->getFont()->setBold(true); + +$validation = $spreadsheet->getActiveSheet() + ->getCell('B3') + ->getDataValidation(); +$validation->setType(DataValidation::TYPE_LIST) + ->setErrorStyle(DataValidation::STYLE_INFORMATION) + ->setAllowBlank(false) + ->setShowInputMessage(true) + ->setShowErrorMessage(true) + ->setShowDropDown(true) + ->setErrorTitle('Input error') + ->setError('Country is not in the list.') + ->setPromptTitle('Pick from the list') + ->setPrompt('Please pick a country from the drop-down list.') + ->setFormula1('=INDIRECT($B$1)'); + +$spreadsheet->getActiveSheet()->getColumnDimension('A')->setWidth(12); +$spreadsheet->getActiveSheet()->getColumnDimension('B')->setWidth(30); + +// Save +$helper->write($spreadsheet, __FILE__); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/40_Duplicate_style.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/40_Duplicate_style.php new file mode 100644 index 0000000..0366703 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/40_Duplicate_style.php @@ -0,0 +1,36 @@ +log('Create new Spreadsheet object'); +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +$helper->log('Create styles array'); +$styles = []; +for ($i = 0; $i < 10; ++$i) { + $style = new Style(); + $style->getFont()->setSize($i + 4); + $styles[] = $style; +} + +$helper->log('Add data (begin)'); +$t = microtime(true); +for ($col = 1; $col <= 50; ++$col) { + for ($row = 0; $row < 100; ++$row) { + $str = ($row + $col); + $style = $styles[$row % 10]; + $coord = Coordinate::stringFromColumnIndex($col) . ($row + 1); + $worksheet->setCellValue($coord, $str); + $worksheet->duplicateStyle($style, $coord); + } +} +$d = microtime(true) - $t; +$helper->log('Add data (end) . time: ' . round((string) ($d . 2)) . ' s'); + +// Save +$helper->write($spreadsheet, __FILE__); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/41_Password.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/41_Password.php new file mode 100644 index 0000000..9aa8e6d --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/41_Password.php @@ -0,0 +1,12 @@ +getSecurity()->setLockWindows(true); +$spreadsheet->getSecurity()->setLockStructure(true); +$spreadsheet->getSecurity()->setWorkbookPassword('secret'); + +// Save +$helper->write($spreadsheet, __FILE__); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/42_RichText.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/42_RichText.php new file mode 100644 index 0000000..43b35a6 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/42_RichText.php @@ -0,0 +1,98 @@ +log('Create new Spreadsheet object'); +$spreadsheet = new Spreadsheet(); + +// Set document properties +$helper->log('Set document properties'); +$spreadsheet->getProperties()->setCreator('Maarten Balliauw') + ->setLastModifiedBy('Maarten Balliauw') + ->setTitle('PhpSpreadsheet Test Document') + ->setSubject('PhpSpreadsheet Test Document') + ->setDescription('Test document for PhpSpreadsheet, generated using PHP classes.') + ->setKeywords('office PhpSpreadsheet php') + ->setCategory('Test result file'); + +// Add some data +$helper->log('Add some data'); + +$html1 = ' +

    My very first example of rich text
    generated from html markup

    +

    + +This block contains an italicized word; +while this block uses an underline. + +

    +

    +I want to eat healthy food pizza. + +'; + +$html2 = '

    + + 100°C is a hot temperature + +
    + + 10°F is cold + +

    '; + +$html3 = '23 equals 8'; + +$html4 = 'H2SO4 is the chemical formula for Sulphuric acid'; + +$html5 = 'bold, italic, bold+italic'; + +$wizard = new HtmlHelper(); +$richText = $wizard->toRichTextObject($html1); + +$spreadsheet->getActiveSheet() + ->setCellValue('A1', $richText); + +$spreadsheet->getActiveSheet() + ->getColumnDimension('A') + ->setWidth(48); +$spreadsheet->getActiveSheet() + ->getRowDimension(1) + ->setRowHeight(-1); +$spreadsheet->getActiveSheet()->getStyle('A1') + ->getAlignment() + ->setWrapText(true); + +$richText = $wizard->toRichTextObject($html2); + +$spreadsheet->getActiveSheet() + ->setCellValue('A2', $richText); + +$spreadsheet->getActiveSheet() + ->getRowDimension(1) + ->setRowHeight(-1); +$spreadsheet->getActiveSheet() + ->getStyle('A2') + ->getAlignment() + ->setWrapText(true); + +$spreadsheet->setActiveSheetIndex(0) + ->setCellValue('A3', $wizard->toRichTextObject($html3)); + +$spreadsheet->setActiveSheetIndex(0) + ->setCellValue('A4', $wizard->toRichTextObject($html4)); + +$spreadsheet->setActiveSheetIndex(0) + ->setCellValue('A5', $wizard->toRichTextObject($html5)); + +// Rename worksheet +$helper->log('Rename worksheet'); +$spreadsheet->getActiveSheet() + ->setTitle('Rich Text Examples'); + +// Save +$helper->write($spreadsheet, __FILE__); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/43_Merge_workbooks.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/43_Merge_workbooks.php new file mode 100644 index 0000000..86314b3 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/43_Merge_workbooks.php @@ -0,0 +1,26 @@ +log('Load MergeBook1 from Xlsx file'); +$filename1 = __DIR__ . '/../templates/43mergeBook1.xlsx'; +$callStartTime = microtime(true); +$spreadsheet1 = IOFactory::load($filename1); +$helper->logRead('Xlsx', $filename1, $callStartTime); + +$helper->log('Load MergeBook2 from Xlsx file'); +$filename2 = __DIR__ . '/../templates/43mergeBook2.xlsx'; +$callStartTime = microtime(true); +$spreadsheet2 = IOFactory::load($filename2); +$helper->logRead('Xlsx', $filename2, $callStartTime); + +foreach ($spreadsheet2->getSheetNames() as $sheetName) { + $sheet = $spreadsheet2->getSheetByName($sheetName); + $sheet->setTitle($sheet->getTitle() . ' copied'); + $spreadsheet1->addExternalSheet($sheet); +} + +// Save +$helper->write($spreadsheet1, __FILE__); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/44_Worksheet_info.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/44_Worksheet_info.php new file mode 100644 index 0000000..33c0cd0 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/44_Worksheet_info.php @@ -0,0 +1,26 @@ +getTemporaryFilename(); +$writer = new Xlsx($sampleSpreadsheet); +$writer->save($filename); + +$inputFileType = IOFactory::identify($filename); +$reader = IOFactory::createReader($inputFileType); +$sheetList = $reader->listWorksheetNames($filename); +$sheetInfo = $reader->listWorksheetInfo($filename); + +$helper->log('File Type:'); +var_dump($inputFileType); + +$helper->log('Worksheet Names:'); +var_dump($sheetList); + +$helper->log('Worksheet Names:'); +var_dump($sheetInfo); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/45_Quadratic_equation_solver.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/45_Quadratic_equation_solver.php new file mode 100644 index 0000000..a59a0ce --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/45_Quadratic_equation_solver.php @@ -0,0 +1,43 @@ + +
    + Enter the coefficients for the Ax2 + Bx + C = 0 + + + + + + + + + + +
    +
    + If A=0, the equation is not quadratic. +
    + +log('The equation is not quadratic'); + } else { + // Calculate and Display the results + $helper->log('
    Roots:
    '); + + $discriminantFormula = '=POWER(' . $_POST['B'] . ',2) - (4 * ' . $_POST['A'] . ' * ' . $_POST['C'] . ')'; + $discriminant = Calculation::getInstance()->calculateFormula($discriminantFormula); + + $r1Formula = '=IMDIV(IMSUM(-' . $_POST['B'] . ',IMSQRT(' . $discriminant . ')),2 * ' . $_POST['A'] . ')'; + $r2Formula = '=IF(' . $discriminant . '=0,"Only one root",IMDIV(IMSUB(-' . $_POST['B'] . ',IMSQRT(' . $discriminant . ')),2 * ' . $_POST['A'] . '))'; + + $helper->log(Calculation::getInstance()->calculateFormula($r1Formula)); + $helper->log(Calculation::getInstance()->calculateFormula($r2Formula)); + $callEndTime = microtime(true); + $helper->logEndingNotes(); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/46_ReadHtml.php b/vendor/phpoffice/phpspreadsheet/samples/Basic/46_ReadHtml.php new file mode 100644 index 0000000..bd37af9 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/46_ReadHtml.php @@ -0,0 +1,19 @@ +load($html); + +$helper->logRead('Html', $html, $callStartTime); + +// Save +$helper->write($objPHPExcel, __FILE__); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/data/continents/Africa.txt b/vendor/phpoffice/phpspreadsheet/samples/Basic/data/continents/Africa.txt new file mode 100644 index 0000000..407fa76 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/data/continents/Africa.txt @@ -0,0 +1,54 @@ +Algeria +Angola +Benin +Botswana +Burkina +Burundi +Cameroon +Cape Verde +Central African Republic +Chad +Comoros +Congo +Congo, Democratic Republic of +Djibouti +Egypt +Equatorial Guinea +Eritrea +Ethiopia +Gabon +Gambia +Ghana +Guinea +Guinea-Bissau +Ivory Coast +Kenya +Lesotho +Liberia +Libya +Madagascar +Malawi +Mali +Mauritania +Mauritius +Morocco +Mozambique +Namibia +Niger +Nigeria +Rwanda +Sao Tome and Principe +Senegal +Seychelles +Sierra Leone +Somalia +South Africa +South Sudan +Sudan +Swaziland +Tanzania +Togo +Tunisia +Uganda +Zambia +Zimbabwe diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/data/continents/Asia.txt b/vendor/phpoffice/phpspreadsheet/samples/Basic/data/continents/Asia.txt new file mode 100644 index 0000000..9ce006c --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/data/continents/Asia.txt @@ -0,0 +1,44 @@ +Afghanistan +Bahrain +Bangladesh +Bhutan +Brunei +Burma (Myanmar) +Cambodia +China +East Timor +India +Indonesia +Iran +Iraq +Israel +Japan +Jordan +Kazakhstan +Korea, North +Korea, South +Kuwait +Kyrgyzstan +Laos +Lebanon +Malaysia +Maldives +Mongolia +Nepal +Oman +Pakistan +Philippines +Qatar +Russian Federation +Saudi Arabia +Singapore +Sri Lanka +Syria +Tajikistan +Thailand +Turkey +Turkmenistan +United Arab Emirates +Uzbekistan +Vietnam +Yemen diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/data/continents/Europe.txt b/vendor/phpoffice/phpspreadsheet/samples/Basic/data/continents/Europe.txt new file mode 100644 index 0000000..70c1160 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/data/continents/Europe.txt @@ -0,0 +1,47 @@ +Albania +Andorra +Armenia +Austria +Azerbaijan +Belarus +Belgium +Bosnia and Herzegovina +Bulgaria +Croatia +Cyprus +Czech Republic +Denmark +Estonia +Finland +France +Georgia +Germany +Greece +Hungary +Iceland +Ireland +Italy +Latvia +Liechtenstein +Lithuania +Luxembourg +Macedonia +Malta +Moldova +Monaco +Montenegro +Netherlands +Norway +Poland +Portugal +Romania +San Marino +Serbia +Slovakia +Slovenia +Spain +Sweden +Switzerland +Ukraine +United Kingdom +Vatican City diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/data/continents/North America.txt b/vendor/phpoffice/phpspreadsheet/samples/Basic/data/continents/North America.txt new file mode 100644 index 0000000..5881ae1 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/data/continents/North America.txt @@ -0,0 +1,23 @@ +Antigua and Barbuda +Bahamas +Barbados +Belize +Canada +Costa Rica +Cuba +Dominica +Dominican Republic +El Salvador +Grenada +Guatemala +Haiti +Honduras +Jamaica +Mexico +Nicaragua +Panama +Saint Kitts and Nevis +Saint Lucia +Saint Vincent and the Grenadines +Trinidad and Tobago +United States diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/data/continents/Oceania.txt b/vendor/phpoffice/phpspreadsheet/samples/Basic/data/continents/Oceania.txt new file mode 100644 index 0000000..cbdc896 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/data/continents/Oceania.txt @@ -0,0 +1,14 @@ +Australia +Fiji +Kiribati +Marshall Islands +Micronesia +Nauru +New Zealand +Palau +Papua New Guinea +Samoa +Solomon Islands +Tonga +Tuvalu +Vanuatu diff --git a/vendor/phpoffice/phpspreadsheet/samples/Basic/data/continents/South America.txt b/vendor/phpoffice/phpspreadsheet/samples/Basic/data/continents/South America.txt new file mode 100644 index 0000000..777ffbf --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Basic/data/continents/South America.txt @@ -0,0 +1,12 @@ +Argentina +Bolivia +Brazil +Chile +Colombia +Ecuador +Guyana +Paraguay +Peru +Suriname +Uruguay +Venezuela diff --git a/vendor/phpoffice/phpspreadsheet/samples/Calculations/Database/DAVERAGE.php b/vendor/phpoffice/phpspreadsheet/samples/Calculations/Database/DAVERAGE.php new file mode 100644 index 0000000..92d8401 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Calculations/Database/DAVERAGE.php @@ -0,0 +1,56 @@ +log('Returns the average of selected database entries.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$database = [['Tree', 'Height', 'Age', 'Yield', 'Profit'], + ['Apple', 18, 20, 14, 105.00], + ['Pear', 12, 12, 10, 96.00], + ['Cherry', 13, 14, 9, 105.00], + ['Apple', 14, 15, 10, 75.00], + ['Pear', 9, 8, 8, 76.80], + ['Apple', 8, 9, 6, 45.00], +]; +$criteria = [['Tree', 'Height', 'Age', 'Yield', 'Profit', 'Height'], + ['="=Apple"', '>10', null, null, null, '<16'], + ['="=Pear"', null, null, null, null, null], +]; + +$worksheet->fromArray($criteria, null, 'A1'); +$worksheet->fromArray($database, null, 'A4'); + +$worksheet->setCellValue('A12', 'The Average yield of Apple trees over 10\' in height'); +$worksheet->setCellValue('B12', '=DAVERAGE(A4:E10,"Yield",A1:B2)'); + +$worksheet->setCellValue('A13', 'The Average age of all Apple and Pear trees in the orchard'); +$worksheet->setCellValue('B13', '=DAVERAGE(A4:E10,3,A1:A3)'); + +$helper->log('Database'); + +$databaseData = $worksheet->rangeToArray('A4:E10', null, true, true, true); +var_dump($databaseData); + +// Test the formulae +$helper->log('Criteria'); + +$criteriaData = $worksheet->rangeToArray('A1:B2', null, true, true, true); +var_dump($criteriaData); + +$helper->log($worksheet->getCell('A12')->getValue()); +$helper->log('DAVERAGE() Result is ' . $worksheet->getCell('B12')->getCalculatedValue()); + +$helper->log('Criteria'); + +$criteriaData = $worksheet->rangeToArray('A1:A3', null, true, true, true); +var_dump($criteriaData); + +$helper->log($worksheet->getCell('A13')->getValue()); +$helper->log('DAVERAGE() Result is ' . $worksheet->getCell('B13')->getCalculatedValue()); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Calculations/Database/DCOUNT.php b/vendor/phpoffice/phpspreadsheet/samples/Calculations/Database/DCOUNT.php new file mode 100644 index 0000000..d869a4b --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Calculations/Database/DCOUNT.php @@ -0,0 +1,55 @@ +log('Counts the cells that contain numbers in a database.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$database = [['Tree', 'Height', 'Age', 'Yield', 'Profit'], + ['Apple', 18, 20, 14, 105.00], + ['Pear', 12, 12, 10, 96.00], + ['Cherry', 13, 14, 9, 105.00], + ['Apple', 14, 15, 10, 75.00], + ['Pear', 9, 8, 8, 76.80], + ['Apple', 8, 9, 6, 45.00], +]; +$criteria = [['Tree', 'Height', 'Age', 'Yield', 'Profit', 'Height'], + ['="=Apple"', '>10', null, null, null, '<16'], + ['="=Pear"', null, null, null, null, null], +]; + +$worksheet->fromArray($criteria, null, 'A1'); +$worksheet->fromArray($database, null, 'A4'); + +$worksheet->setCellValue('A12', 'The Number of Apple trees over 10\' in height'); +$worksheet->setCellValue('B12', '=DCOUNT(A4:E10,"Yield",A1:B2)'); + +$worksheet->setCellValue('A13', 'The Number of Apple and Pear trees in the orchard'); +$worksheet->setCellValue('B13', '=DCOUNT(A4:E10,3,A1:A3)'); + +$helper->log('Database'); + +$databaseData = $worksheet->rangeToArray('A4:E10', null, true, true, true); +var_dump($databaseData); + +// Test the formulae +$helper->log('Criteria'); + +$criteriaData = $worksheet->rangeToArray('A1:B2', null, true, true, true); +var_dump($criteriaData); + +$helper->log($worksheet->getCell('A12')->getValue()); +$helper->log('DCOUNT() Result is ' . $worksheet->getCell('B12')->getCalculatedValue()); + +$helper->log('Criteria'); + +$criteriaData = $worksheet->rangeToArray('A1:A3', null, true, true, true); +var_dump($criteriaData); + +$helper->log($worksheet->getCell('A13')->getValue()); +$helper->log('DCOUNT() Result is ' . $worksheet->getCell('B13')->getCalculatedValue()); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Calculations/Database/DGET.php b/vendor/phpoffice/phpspreadsheet/samples/Calculations/Database/DGET.php new file mode 100644 index 0000000..9f543c9 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Calculations/Database/DGET.php @@ -0,0 +1,52 @@ +log('Extracts a single value from a column of a list or database that matches conditions that you specify.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$database = [['Tree', 'Height', 'Age', 'Yield', 'Profit'], + ['Apple', 18, 20, 14, 105.00], + ['Pear', 12, 12, 10, 96.00], + ['Cherry', 13, 14, 9, 105.00], + ['Apple', 14, 15, 10, 75.00], + ['Pear', 9, 8, 8, 76.80], + ['Apple', 8, 9, 6, 45.00], +]; +$criteria = [['Tree', 'Height', 'Age', 'Yield', 'Profit', 'Height'], + ['="=Apple"', '>10', null, null, null, '<16'], + ['="=Pear"', null, null, null, null, null], +]; + +$worksheet->fromArray($criteria, null, 'A1'); +$worksheet->fromArray($database, null, 'A4'); + +$worksheet->setCellValue('A12', 'The height of the Apple tree between 10\' and 16\' tall'); +$worksheet->setCellValue('B12', '=DGET(A4:E10,"Height",A1:F2)'); + +$helper->log('Database'); + +$databaseData = $worksheet->rangeToArray('A4:E10', null, true, true, true); +var_dump($databaseData); + +// Test the formulae +$helper->log('Criteria'); + +$helper->log('ALL'); + +$helper->log($worksheet->getCell('A12')->getValue()); +$helper->log('DMAX() Result is ' . $worksheet->getCell('B12')->getCalculatedValue()); + +$helper->log('Criteria'); + +$criteriaData = $worksheet->rangeToArray('A1:A2', null, true, true, true); +var_dump($criteriaData); + +$helper->log($worksheet->getCell('A13')->getValue()); +$helper->log('DMAX() Result is ' . $worksheet->getCell('B13')->getCalculatedValue()); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Calculations/Database/DMAX.php b/vendor/phpoffice/phpspreadsheet/samples/Calculations/Database/DMAX.php new file mode 100644 index 0000000..c48928d --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Calculations/Database/DMAX.php @@ -0,0 +1,55 @@ +log('Returns the maximum value from selected database entries.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$database = [['Tree', 'Height', 'Age', 'Yield', 'Profit'], + ['Apple', 18, 20, 14, 105.00], + ['Pear', 12, 12, 10, 96.00], + ['Cherry', 13, 14, 9, 105.00], + ['Apple', 14, 15, 10, 75.00], + ['Pear', 9, 8, 8, 76.80], + ['Apple', 8, 9, 6, 45.00], +]; +$criteria = [['Tree', 'Height', 'Age', 'Yield', 'Profit', 'Height'], + ['="=Apple"', '>10', null, null, null, '<16'], + ['="=Pear"', null, null, null, null, null], +]; + +$worksheet->fromArray($criteria, null, 'A1'); +$worksheet->fromArray($database, null, 'A4'); + +$worksheet->setCellValue('A12', 'The tallest tree in the orchard'); +$worksheet->setCellValue('B12', '=DMAX(A4:E10,"Height",A4:E10)'); + +$worksheet->setCellValue('A13', 'The Oldest apple tree in the orchard'); +$worksheet->setCellValue('B13', '=DMAX(A4:E10,3,A1:A2)'); + +$helper->log('Database'); + +$databaseData = $worksheet->rangeToArray('A4:E10', null, true, true, true); +var_dump($databaseData); + +// Test the formulae +$helper->log('Criteria'); + +$helper->log('ALL'); + +$helper->log($worksheet->getCell('A12')->getValue()); +$helper->log('DMAX() Result is ' . $worksheet->getCell('B12')->getCalculatedValue()); + +$helper->log('Criteria'); + +$criteriaData = $worksheet->rangeToArray('A1:A2', null, true, true, true); +var_dump($criteriaData); + +$helper->log($worksheet->getCell('A13')->getValue()); +$helper->log('DMAX() Result is ' . $worksheet->getCell('B13')->getCalculatedValue()); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Calculations/Database/DMIN.php b/vendor/phpoffice/phpspreadsheet/samples/Calculations/Database/DMIN.php new file mode 100644 index 0000000..7bcaa20 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Calculations/Database/DMIN.php @@ -0,0 +1,55 @@ +log('Returns the minimum value from selected database entries.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$database = [['Tree', 'Height', 'Age', 'Yield', 'Profit'], + ['Apple', 18, 20, 14, 105.00], + ['Pear', 12, 12, 10, 96.00], + ['Cherry', 13, 14, 9, 105.00], + ['Apple', 14, 15, 10, 75.00], + ['Pear', 9, 8, 8, 76.80], + ['Apple', 8, 9, 6, 45.00], +]; +$criteria = [['Tree', 'Height', 'Age', 'Yield', 'Profit', 'Height'], + ['="=Apple"', '>10', null, null, null, '<16'], + ['="=Pear"', null, null, null, null, null], +]; + +$worksheet->fromArray($criteria, null, 'A1'); +$worksheet->fromArray($database, null, 'A4'); + +$worksheet->setCellValue('A12', 'The shortest tree in the orchard'); +$worksheet->setCellValue('B12', '=DMIN(A4:E10,"Height",A4:E10)'); + +$worksheet->setCellValue('A13', 'The Youngest apple tree in the orchard'); +$worksheet->setCellValue('B13', '=DMIN(A4:E10,3,A1:A2)'); + +$helper->log('Database'); + +$databaseData = $worksheet->rangeToArray('A4:E10', null, true, true, true); +var_dump($databaseData); + +// Test the formulae +$helper->log('Criteria'); + +$helper->log('ALL'); + +$helper->log($worksheet->getCell('A12')->getValue()); +$helper->log('DMIN() Result is ' . $worksheet->getCell('B12')->getCalculatedValue()); + +$helper->log('Criteria'); + +$criteriaData = $worksheet->rangeToArray('A1:A2', null, true, true, true); +var_dump($criteriaData); + +$helper->log($worksheet->getCell('A13')->getValue()); +$helper->log('DMIN() Result is ' . $worksheet->getCell('B13')->getCalculatedValue()); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Calculations/Database/DPRODUCT.php b/vendor/phpoffice/phpspreadsheet/samples/Calculations/Database/DPRODUCT.php new file mode 100644 index 0000000..7c14ded --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Calculations/Database/DPRODUCT.php @@ -0,0 +1,52 @@ +log('Multiplies the values in a column of a list or database that match conditions that you specify.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$database = [['Tree', 'Height', 'Age', 'Yield', 'Profit'], + ['Apple', 18, 20, 14, 105.00], + ['Pear', 12, 12, 10, 96.00], + ['Cherry', 13, 14, 9, 105.00], + ['Apple', 14, 15, 10, 75.00], + ['Pear', 9, 8, 8, 76.80], + ['Apple', 8, 9, 6, 45.00], +]; +$criteria = [['Tree', 'Height', 'Age', 'Yield', 'Profit', 'Height'], + ['="=Apple"', '>10', null, null, null, '<16'], + ['="=Pear"', null, null, null, null, null], +]; + +$worksheet->fromArray($criteria, null, 'A1'); +$worksheet->fromArray($database, null, 'A4'); + +$worksheet->setCellValue('A12', 'The product of the yields of all Apple trees over 10\' in the orchard'); +$worksheet->setCellValue('B12', '=DPRODUCT(A4:E10,"Yield",A1:B2)'); + +$helper->log('Database'); + +$databaseData = $worksheet->rangeToArray('A4:E10', null, true, true, true); +var_dump($databaseData); + +// Test the formulae +$helper->log('Criteria'); + +$helper->log('ALL'); + +$helper->log($worksheet->getCell('A12')->getValue()); +$helper->log('DMAX() Result is ' . $worksheet->getCell('B12')->getCalculatedValue()); + +$helper->log('Criteria'); + +$criteriaData = $worksheet->rangeToArray('A1:A2', null, true, true, true); +var_dump($criteriaData); + +$helper->log($worksheet->getCell('A13')->getValue()); +$helper->log('DMAX() Result is ' . $worksheet->getCell('B13')->getCalculatedValue()); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Calculations/Database/DSTDEV.php b/vendor/phpoffice/phpspreadsheet/samples/Calculations/Database/DSTDEV.php new file mode 100644 index 0000000..7f09fa5 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Calculations/Database/DSTDEV.php @@ -0,0 +1,56 @@ +log('Estimates the standard deviation based on a sample of selected database entries.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$database = [['Tree', 'Height', 'Age', 'Yield', 'Profit'], + ['Apple', 18, 20, 14, 105.00], + ['Pear', 12, 12, 10, 96.00], + ['Cherry', 13, 14, 9, 105.00], + ['Apple', 14, 15, 10, 75.00], + ['Pear', 9, 8, 8, 76.80], + ['Apple', 8, 9, 6, 45.00], +]; +$criteria = [['Tree', 'Height', 'Age', 'Yield', 'Profit', 'Height'], + ['="=Apple"', '>10', null, null, null, '<16'], + ['="=Pear"', null, null, null, null, null], +]; + +$worksheet->fromArray($criteria, null, 'A1'); +$worksheet->fromArray($database, null, 'A4'); + +$worksheet->setCellValue('A12', 'The estimated standard deviation in the yield of Apple and Pear trees'); +$worksheet->setCellValue('B12', '=DSTDEV(A4:E10,"Yield",A1:A3)'); + +$worksheet->setCellValue('A13', 'The estimated standard deviation in height of Apple and Pear trees'); +$worksheet->setCellValue('B13', '=DSTDEV(A4:E10,2,A1:A3)'); + +$helper->log('Database'); + +$databaseData = $worksheet->rangeToArray('A4:E10', null, true, true, true); +var_dump($databaseData); + +// Test the formulae +$helper->log('Criteria'); + +$criteriaData = $worksheet->rangeToArray('A1:A3', null, true, true, true); +var_dump($criteriaData); + +$helper->log($worksheet->getCell('A12')->getValue()); +$helper->log('DSTDEV() Result is ' . $worksheet->getCell('B12')->getCalculatedValue()); + +$helper->log('Criteria'); + +$criteriaData = $worksheet->rangeToArray('A1:A3', null, true, true, true); +var_dump($criteriaData); + +$helper->log($worksheet->getCell('A13')->getValue()); +$helper->log('DSTDEV() Result is ' . $worksheet->getCell('B13')->getCalculatedValue()); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Calculations/Database/DSTDEVP.php b/vendor/phpoffice/phpspreadsheet/samples/Calculations/Database/DSTDEVP.php new file mode 100644 index 0000000..9e999a8 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Calculations/Database/DSTDEVP.php @@ -0,0 +1,55 @@ +log('Calculates the standard deviation based on the entire population of selected database entries.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$database = [['Tree', 'Height', 'Age', 'Yield', 'Profit'], + ['Apple', 18, 20, 14, 105.00], + ['Pear', 12, 12, 10, 96.00], + ['Cherry', 13, 14, 9, 105.00], + ['Apple', 14, 15, 10, 75.00], + ['Pear', 9, 8, 8, 76.80], + ['Apple', 8, 9, 6, 45.00], +]; +$criteria = [['Tree', 'Height', 'Age', 'Yield', 'Profit', 'Height'], + ['="=Apple"', '>10', null, null, null, '<16'], + ['="=Pear"', null, null, null, null, null], +]; + +$worksheet->fromArray($criteria, null, 'A1'); +$worksheet->fromArray($database, null, 'A4'); + +$worksheet->setCellValue('A12', 'The standard deviation in the yield of Apple and Pear trees'); +$worksheet->setCellValue('B12', '=DSTDEVP(A4:E10,"Yield",A1:A3)'); + +$worksheet->setCellValue('A13', 'The standard deviation in height of Apple and Pear trees'); +$worksheet->setCellValue('B13', '=DSTDEVP(A4:E10,2,A1:A3)'); + +$helper->log('Database'); + +$databaseData = $worksheet->rangeToArray('A4:E10', null, true, true, true); +var_dump($databaseData); + +// Test the formulae +$helper->log('Criteria'); + +$criteriaData = $worksheet->rangeToArray('A1:A3', null, true, true, true); +var_dump($criteriaData); + +$helper->log($worksheet->getCell('A12')->getValue()); +$helper->log('DSTDEVP() Result is ' . $worksheet->getCell('B12')->getCalculatedValue()); + +$helper->log('Criteria'); + +$criteriaData = $worksheet->rangeToArray('A1:A3', null, true, true, true); +var_dump($criteriaData); + +$helper->log($worksheet->getCell('A13')->getValue()); +$helper->log('DSTDEVP() Result is ' . $worksheet->getCell('B13')->getCalculatedValue()); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Calculations/Database/DVAR.php b/vendor/phpoffice/phpspreadsheet/samples/Calculations/Database/DVAR.php new file mode 100644 index 0000000..2a5f874 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Calculations/Database/DVAR.php @@ -0,0 +1,55 @@ +log('Estimates variance based on a sample from selected database entries.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$database = [['Tree', 'Height', 'Age', 'Yield', 'Profit'], + ['Apple', 18, 20, 14, 105.00], + ['Pear', 12, 12, 10, 96.00], + ['Cherry', 13, 14, 9, 105.00], + ['Apple', 14, 15, 10, 75.00], + ['Pear', 9, 8, 8, 76.80], + ['Apple', 8, 9, 6, 45.00], +]; +$criteria = [['Tree', 'Height', 'Age', 'Yield', 'Profit', 'Height'], + ['="=Apple"', '>10', null, null, null, '<16'], + ['="=Pear"', null, null, null, null, null], +]; + +$worksheet->fromArray($criteria, null, 'A1'); +$worksheet->fromArray($database, null, 'A4'); + +$worksheet->setCellValue('A12', 'The estimated variance in the yield of Apple and Pear trees'); +$worksheet->setCellValue('B12', '=DVAR(A4:E10,"Yield",A1:A3)'); + +$worksheet->setCellValue('A13', 'The estimated variance in height of Apple and Pear trees'); +$worksheet->setCellValue('B13', '=DVAR(A4:E10,2,A1:A3)'); + +$helper->log('Database'); + +$databaseData = $worksheet->rangeToArray('A4:E10', null, true, true, true); +var_dump($databaseData); + +// Test the formulae +$helper->log('Criteria'); + +$criteriaData = $worksheet->rangeToArray('A1:A3', null, true, true, true); +var_dump($criteriaData); + +$helper->log($worksheet->getCell('A12')->getValue()); +$helper->log('DVAR() Result is ' . $worksheet->getCell('B12')->getCalculatedValue()); + +$helper->log('Criteria'); + +$criteriaData = $worksheet->rangeToArray('A1:A3', null, true, true, true); +var_dump($criteriaData); + +$helper->log($worksheet->getCell('A13')->getValue()); +$helper->log('DVAR() Result is ' . $worksheet->getCell('B13')->getCalculatedValue()); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Calculations/Database/DVARP.php b/vendor/phpoffice/phpspreadsheet/samples/Calculations/Database/DVARP.php new file mode 100644 index 0000000..4f57113 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Calculations/Database/DVARP.php @@ -0,0 +1,56 @@ +log('Calculates variance based on the entire population of selected database entries,'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$database = [['Tree', 'Height', 'Age', 'Yield', 'Profit'], + ['Apple', 18, 20, 14, 105.00], + ['Pear', 12, 12, 10, 96.00], + ['Cherry', 13, 14, 9, 105.00], + ['Apple', 14, 15, 10, 75.00], + ['Pear', 9, 8, 8, 76.80], + ['Apple', 8, 9, 6, 45.00], +]; +$criteria = [['Tree', 'Height', 'Age', 'Yield', 'Profit', 'Height'], + ['="=Apple"', '>10', null, null, null, '<16'], + ['="=Pear"', null, null, null, null, null], +]; + +$worksheet->fromArray($criteria, null, 'A1'); +$worksheet->fromArray($database, null, 'A4'); + +$worksheet->setCellValue('A12', 'The variance in the yield of Apple and Pear trees'); +$worksheet->setCellValue('B12', '=DVARP(A4:E10,"Yield",A1:A3)'); + +$worksheet->setCellValue('A13', 'The variance in height of Apple and Pear trees'); +$worksheet->setCellValue('B13', '=DVARP(A4:E10,2,A1:A3)'); + +$helper->log('Database'); + +$databaseData = $worksheet->rangeToArray('A4:E10', null, true, true, true); +var_dump($databaseData); + +// Test the formulae +$helper->log('Criteria'); + +$criteriaData = $worksheet->rangeToArray('A1:A3', null, true, true, true); +var_dump($criteriaData); + +$helper->log($worksheet->getCell('A12')->getValue()); +$helper->log('DVARP() Result is ' . $worksheet->getCell('B12')->getCalculatedValue()); + +$helper->log('Criteria'); + +$criteriaData = $worksheet->rangeToArray('A1:A3', null, true, true, true); +var_dump($criteriaData); + +$helper->log($worksheet->getCell('A13')->getValue()); +$helper->log('DVARP() Result is ' . $worksheet->getCell('B13')->getCalculatedValue()); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Calculations/DateTime/DATE.php b/vendor/phpoffice/phpspreadsheet/samples/Calculations/DateTime/DATE.php new file mode 100644 index 0000000..5d758f7 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Calculations/DateTime/DATE.php @@ -0,0 +1,41 @@ +log('Returns the serial number of a particular date.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testDates = [[2012, 3, 26], [2012, 2, 29], [2012, 4, 1], [2012, 12, 25], + [2012, 10, 31], [2012, 11, 5], [2012, 1, 1], [2012, 3, 17], + [2011, 2, 29], [7, 5, 3], [2012, 13, 1], [2012, 11, 45], + [2012, 0, 0], [2012, 1, 0], [2012, 0, 1], + [2012, -2, 2], [2012, 2, -2], [2012, -2, -2], +]; +$testDateCount = count($testDates); + +$worksheet->fromArray($testDates, null, 'A1', true); + +for ($row = 1; $row <= $testDateCount; ++$row) { + $worksheet->setCellValue('D' . $row, '=DATE(A' . $row . ',B' . $row . ',C' . $row . ')'); + $worksheet->setCellValue('E' . $row, '=D' . $row); +} +$worksheet->getStyle('E1:E' . $testDateCount) + ->getNumberFormat() + ->setFormatCode('yyyy-mmm-dd'); + +// Test the formulae +for ($row = 1; $row <= $testDateCount; ++$row) { + $helper->log('Year: ' . $worksheet->getCell('A' . $row)->getFormattedValue()); + $helper->log('Month: ' . $worksheet->getCell('B' . $row)->getFormattedValue()); + $helper->log('Day: ' . $worksheet->getCell('C' . $row)->getFormattedValue()); + $helper->log('Formula: ' . $worksheet->getCell('D' . $row)->getValue()); + $helper->log('Excel DateStamp: ' . $worksheet->getCell('D' . $row)->getFormattedValue()); + $helper->log('Formatted DateStamp: ' . $worksheet->getCell('E' . $row)->getFormattedValue()); + $helper->log(''); +} diff --git a/vendor/phpoffice/phpspreadsheet/samples/Calculations/DateTime/DATEVALUE.php b/vendor/phpoffice/phpspreadsheet/samples/Calculations/DateTime/DATEVALUE.php new file mode 100644 index 0000000..5cdb936 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Calculations/DateTime/DATEVALUE.php @@ -0,0 +1,39 @@ +log('Converts a date in the form of text to a serial number.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testDates = ['26 March 2012', '29 Feb 2012', 'April 1, 2012', '25/12/2012', + '2012-Oct-31', '5th November', 'January 1st', 'April 2012', + '17-03', '03-2012', '29 Feb 2011', '03-05-07', + '03-MAY-07', '03-13-07', +]; +$testDateCount = count($testDates); + +for ($row = 1; $row <= $testDateCount; ++$row) { + $worksheet->setCellValue('A' . $row, $testDates[$row - 1]); + $worksheet->setCellValue('B' . $row, '=DATEVALUE(A' . $row . ')'); + $worksheet->setCellValue('C' . $row, '=B' . $row); +} + +$worksheet->getStyle('C1:C' . $testDateCount) + ->getNumberFormat() + ->setFormatCode('yyyy-mmm-dd'); + +// Test the formulae +$helper->log('Warning: The PhpSpreadsheet DATEVALUE() function accepts a wider range of date formats than MS Excel DATEFORMAT() function.'); +for ($row = 1; $row <= $testDateCount; ++$row) { + $helper->log('Date String: ' . $worksheet->getCell('A' . $row)->getFormattedValue()); + $helper->log('Formula: ' . $worksheet->getCell('B' . $row)->getValue()); + $helper->log('Excel DateStamp: ' . $worksheet->getCell('B' . $row)->getFormattedValue()); + $helper->log('Formatted DateStamp' . $worksheet->getCell('C' . $row)->getFormattedValue()); + $helper->log(''); +} diff --git a/vendor/phpoffice/phpspreadsheet/samples/Calculations/DateTime/TIME.php b/vendor/phpoffice/phpspreadsheet/samples/Calculations/DateTime/TIME.php new file mode 100644 index 0000000..3d4208a --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Calculations/DateTime/TIME.php @@ -0,0 +1,39 @@ +log('Returns the serial number of a particular time.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testDates = [[3, 15], [13, 15], [15, 15, 15], [3, 15, 30], + [15, 15, 15], [5], [9, 15, 0], [9, 15, -1], + [13, -14, -15], [0, 0, -1], +]; +$testDateCount = count($testDates); + +$worksheet->fromArray($testDates, null, 'A1', true); + +for ($row = 1; $row <= $testDateCount; ++$row) { + $worksheet->setCellValue('D' . $row, '=TIME(A' . $row . ',B' . $row . ',C' . $row . ')'); + $worksheet->setCellValue('E' . $row, '=D' . $row); +} +$worksheet->getStyle('E1:E' . $testDateCount) + ->getNumberFormat() + ->setFormatCode('hh:mm:ss'); + +// Test the formulae +for ($row = 1; $row <= $testDateCount; ++$row) { + $helper->log('Hour: ' . $worksheet->getCell('A' . $row)->getFormattedValue()); + $helper->log('Minute: ' . $worksheet->getCell('B' . $row)->getFormattedValue()); + $helper->log('Second: ' . $worksheet->getCell('C' . $row)->getFormattedValue()); + $helper->log('Formula: ' . $worksheet->getCell('D' . $row)->getValue()); + $helper->log('Excel TimeStamp: ' . $worksheet->getCell('D' . $row)->getFormattedValue()); + $helper->log('Formatted TimeStamp: ' . $worksheet->getCell('E' . $row)->getFormattedValue()); + $helper->log(''); +} diff --git a/vendor/phpoffice/phpspreadsheet/samples/Calculations/DateTime/TIMEVALUE.php b/vendor/phpoffice/phpspreadsheet/samples/Calculations/DateTime/TIMEVALUE.php new file mode 100644 index 0000000..f75393c --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Calculations/DateTime/TIMEVALUE.php @@ -0,0 +1,35 @@ +log('Converts a time in the form of text to a serial number.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testDates = ['3:15', '13:15', '15:15:15', '3:15 AM', '3:15 PM', '5PM', '9:15AM', '13:15AM', +]; +$testDateCount = count($testDates); + +for ($row = 1; $row <= $testDateCount; ++$row) { + $worksheet->setCellValue('A' . $row, $testDates[$row - 1]); + $worksheet->setCellValue('B' . $row, '=TIMEVALUE(A' . $row . ')'); + $worksheet->setCellValue('C' . $row, '=B' . $row); +} + +$worksheet->getStyle('C1:C' . $testDateCount) + ->getNumberFormat() + ->setFormatCode('hh:mm:ss'); + +// Test the formulae +for ($row = 1; $row <= $testDateCount; ++$row) { + $helper->log('Time String: ' . $worksheet->getCell('A' . $row)->getFormattedValue()); + $helper->log('Formula: ' . $worksheet->getCell('B' . $row)->getValue()); + $helper->log('Excel TimeStamp: ' . $worksheet->getCell('B' . $row)->getFormattedValue()); + $helper->log('Formatted TimeStamp: ' . $worksheet->getCell('C' . $row)->getFormattedValue()); + $helper->log(''); +} diff --git a/vendor/phpoffice/phpspreadsheet/samples/Chart/32_Chart_read_write.php b/vendor/phpoffice/phpspreadsheet/samples/Chart/32_Chart_read_write.php new file mode 100644 index 0000000..ba711c0 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Chart/32_Chart_read_write.php @@ -0,0 +1,83 @@ + 1)) { + $inputFileNames = []; + for ($i = 1; $i < $argc; ++$i) { + $inputFileNames[] = __DIR__ . '/../templates/' . $argv[$i]; + } +} else { + $inputFileNames = glob($inputFileNames); +} +foreach ($inputFileNames as $inputFileName) { + $inputFileNameShort = basename($inputFileName); + + if (!file_exists($inputFileName)) { + $helper->log('File ' . $inputFileNameShort . ' does not exist'); + + continue; + } + $reader = IOFactory::createReader($inputFileType); + $reader->setIncludeCharts(true); + $callStartTime = microtime(true); + $spreadsheet = $reader->load($inputFileName); + $helper->logRead($inputFileType, $inputFileName, $callStartTime); + + $helper->log('Iterate worksheets looking at the charts'); + foreach ($spreadsheet->getWorksheetIterator() as $worksheet) { + $sheetName = $worksheet->getTitle(); + $helper->log('Worksheet: ' . $sheetName); + + $chartNames = $worksheet->getChartNames(); + if (empty($chartNames)) { + $helper->log(' There are no charts in this worksheet'); + } else { + natsort($chartNames); + foreach ($chartNames as $i => $chartName) { + $chart = $worksheet->getChartByName($chartName); + if ($chart->getTitle() !== null) { + $caption = '"' . implode(' ', $chart->getTitle()->getCaption()) . '"'; + } else { + $caption = 'Untitled'; + } + $helper->log(' ' . $chartName . ' - ' . $caption); + $indentation = str_repeat(' ', strlen($chartName) + 3); + $groupCount = $chart->getPlotArea()->getPlotGroupCount(); + if ($groupCount == 1) { + $chartType = $chart->getPlotArea()->getPlotGroupByIndex(0)->getPlotType(); + $helper->log($indentation . ' ' . $chartType); + } else { + $chartTypes = []; + for ($i = 0; $i < $groupCount; ++$i) { + $chartTypes[] = $chart->getPlotArea()->getPlotGroupByIndex($i)->getPlotType(); + } + $chartTypes = array_unique($chartTypes); + if (count($chartTypes) == 1) { + $chartType = 'Multiple Plot ' . array_pop($chartTypes); + $helper->log($indentation . ' ' . $chartType); + } elseif (count($chartTypes) == 0) { + $helper->log($indentation . ' *** Type not yet implemented'); + } else { + $helper->log($indentation . ' Combination Chart'); + } + } + } + } + } + + $outputFileName = $helper->getFilename($inputFileName); + $writer = IOFactory::createWriter($spreadsheet, 'Xlsx'); + $writer->setIncludeCharts(true); + $callStartTime = microtime(true); + $writer->save($outputFileName); + $helper->logWrite($writer, $outputFileName, $callStartTime); + + $spreadsheet->disconnectWorksheets(); + unset($spreadsheet); +} diff --git a/vendor/phpoffice/phpspreadsheet/samples/Chart/32_Chart_read_write_HTML.php b/vendor/phpoffice/phpspreadsheet/samples/Chart/32_Chart_read_write_HTML.php new file mode 100644 index 0000000..5febbf9 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Chart/32_Chart_read_write_HTML.php @@ -0,0 +1,89 @@ + 1)) { + $inputFileNames = []; + for ($i = 1; $i < $argc; ++$i) { + $inputFileNames[] = __DIR__ . '/../templates/' . $argv[$i]; + } +} else { + $inputFileNames = glob($inputFileNames); +} +foreach ($inputFileNames as $inputFileName) { + $inputFileNameShort = basename($inputFileName); + + if (!file_exists($inputFileName)) { + $helper->log('File ' . $inputFileNameShort . ' does not exist'); + + continue; + } + + $helper->log("Load Test from $inputFileType file " . $inputFileNameShort); + + $reader = IOFactory::createReader($inputFileType); + $reader->setIncludeCharts(true); + $spreadsheet = $reader->load($inputFileName); + + $helper->log('Iterate worksheets looking at the charts'); + foreach ($spreadsheet->getWorksheetIterator() as $worksheet) { + $sheetName = $worksheet->getTitle(); + $helper->log('Worksheet: ' . $sheetName); + + $chartNames = $worksheet->getChartNames(); + if (empty($chartNames)) { + $helper->log(' There are no charts in this worksheet'); + } else { + natsort($chartNames); + foreach ($chartNames as $i => $chartName) { + $chart = $worksheet->getChartByName($chartName); + if ($chart->getTitle() !== null) { + $caption = '"' . implode(' ', $chart->getTitle()->getCaption()) . '"'; + } else { + $caption = 'Untitled'; + } + $helper->log(' ' . $chartName . ' - ' . $caption); + $helper->log(str_repeat(' ', strlen($chartName) + 3)); + $groupCount = $chart->getPlotArea()->getPlotGroupCount(); + if ($groupCount == 1) { + $chartType = $chart->getPlotArea()->getPlotGroupByIndex(0)->getPlotType(); + $helper->log(' ' . $chartType); + } else { + $chartTypes = []; + for ($i = 0; $i < $groupCount; ++$i) { + $chartTypes[] = $chart->getPlotArea()->getPlotGroupByIndex($i)->getPlotType(); + } + $chartTypes = array_unique($chartTypes); + if (count($chartTypes) == 1) { + $chartType = 'Multiple Plot ' . array_pop($chartTypes); + $helper->log(' ' . $chartType); + } elseif (count($chartTypes) == 0) { + $helper->log(' *** Type not yet implemented'); + } else { + $helper->log(' Combination Chart'); + } + } + } + } + } + + // Save + $filename = $helper->getFilename($inputFileName, 'html'); + $writer = IOFactory::createWriter($spreadsheet, 'Html'); + $writer->setIncludeCharts(true); + $callStartTime = microtime(true); + $writer->save($filename); + $helper->logWrite($writer, $filename, $callStartTime); + + $spreadsheet->disconnectWorksheets(); + unset($spreadsheet); +} diff --git a/vendor/phpoffice/phpspreadsheet/samples/Chart/32_Chart_read_write_PDF.php b/vendor/phpoffice/phpspreadsheet/samples/Chart/32_Chart_read_write_PDF.php new file mode 100644 index 0000000..ee3ad0e --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Chart/32_Chart_read_write_PDF.php @@ -0,0 +1,91 @@ + 1)) { + $inputFileNames = []; + for ($i = 1; $i < $argc; ++$i) { + $inputFileNames[] = __DIR__ . '/../templates/' . $argv[$i]; + } +} else { + $inputFileNames = glob($inputFileNames); +} +foreach ($inputFileNames as $inputFileName) { + $inputFileNameShort = basename($inputFileName); + + if (!file_exists($inputFileName)) { + $helper->log('File ' . $inputFileNameShort . ' does not exist'); + + continue; + } + + $helper->log("Load Test from $inputFileType file " . $inputFileNameShort); + + $reader = IOFactory::createReader($inputFileType); + $reader->setIncludeCharts(true); + $spreadsheet = $reader->load($inputFileName); + + $helper->log('Iterate worksheets looking at the charts'); + foreach ($spreadsheet->getWorksheetIterator() as $worksheet) { + $sheetName = $worksheet->getTitle(); + $helper->log('Worksheet: ' . $sheetName); + + $chartNames = $worksheet->getChartNames(); + if (empty($chartNames)) { + $helper->log(' There are no charts in this worksheet'); + } else { + natsort($chartNames); + foreach ($chartNames as $i => $chartName) { + $chart = $worksheet->getChartByName($chartName); + if ($chart->getTitle() !== null) { + $caption = '"' . implode(' ', $chart->getTitle()->getCaption()) . '"'; + } else { + $caption = 'Untitled'; + } + $helper->log(' ' . $chartName . ' - ' . $caption); + $helper->log(str_repeat(' ', strlen($chartName) + 3)); + $groupCount = $chart->getPlotArea()->getPlotGroupCount(); + if ($groupCount == 1) { + $chartType = $chart->getPlotArea()->getPlotGroupByIndex(0)->getPlotType(); + $helper->log(' ' . $chartType); + } else { + $chartTypes = []; + for ($i = 0; $i < $groupCount; ++$i) { + $chartTypes[] = $chart->getPlotArea()->getPlotGroupByIndex($i)->getPlotType(); + } + $chartTypes = array_unique($chartTypes); + if (count($chartTypes) == 1) { + $chartType = 'Multiple Plot ' . array_pop($chartTypes); + $helper->log(' ' . $chartType); + } elseif (count($chartTypes) == 0) { + $helper->log(' *** Type not yet implemented'); + } else { + $helper->log(' Combination Chart'); + } + } + } + } + } + + // Save + $filename = $helper->getFilename($inputFileName, 'pdf'); + $writer = IOFactory::createWriter($spreadsheet, 'Pdf'); + $writer->setIncludeCharts(true); + $callStartTime = microtime(true); + $writer->save($filename); + $helper->logWrite($writer, $filename, $callStartTime); + + $spreadsheet->disconnectWorksheets(); + unset($spreadsheet); +} diff --git a/vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_area.php b/vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_area.php new file mode 100644 index 0000000..4478d2d --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_area.php @@ -0,0 +1,104 @@ +getActiveSheet(); +$worksheet->fromArray( + [ + ['', 2010, 2011, 2012], + ['Q1', 12, 15, 21], + ['Q2', 56, 73, 86], + ['Q3', 52, 61, 69], + ['Q4', 30, 32, 0], + ] +); + +// Set the Labels for each data series we want to plot +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$dataSeriesLabels = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$B$1', null, 1), // 2010 + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$C$1', null, 1), // 2011 + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$D$1', null, 1), // 2012 +]; +// Set the X-Axis Labels +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$xAxisTickValues = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$A$2:$A$5', null, 4), // Q1 to Q4 +]; +// Set the Data values for each data series we want to plot +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$dataSeriesValues = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$B$2:$B$5', null, 4), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$C$2:$C$5', null, 4), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$D$2:$D$5', null, 4), +]; + +// Build the dataseries +$series = new DataSeries( + DataSeries::TYPE_AREACHART, // plotType + DataSeries::GROUPING_PERCENT_STACKED, // plotGrouping + range(0, count($dataSeriesValues) - 1), // plotOrder + $dataSeriesLabels, // plotLabel + $xAxisTickValues, // plotCategory + $dataSeriesValues // plotValues +); + +// Set the series in the plot area +$plotArea = new PlotArea(null, [$series]); +// Set the chart legend +$legend = new Legend(Legend::POSITION_TOPRIGHT, null, false); + +$title = new Title('Test %age-Stacked Area Chart'); +$yAxisLabel = new Title('Value ($k)'); + +// Create the chart +$chart = new Chart( + 'chart1', // name + $title, // title + $legend, // legend + $plotArea, // plotArea + true, // plotVisibleOnly + 0, // displayBlanksAs + null, // xAxisLabel + $yAxisLabel // yAxisLabel +); + +// Set the position where the chart should appear in the worksheet +$chart->setTopLeftPosition('A7'); +$chart->setBottomRightPosition('H20'); + +// Add the chart to the worksheet +$worksheet->addChart($chart); + +// Save Excel 2007 file +$filename = $helper->getFilename(__FILE__); +$writer = IOFactory::createWriter($spreadsheet, 'Xlsx'); +$writer->setIncludeCharts(true); +$callStartTime = microtime(true); +$writer->save($filename); +$helper->logWrite($writer, $filename, $callStartTime); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_bar.php b/vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_bar.php new file mode 100644 index 0000000..a05cf92 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_bar.php @@ -0,0 +1,15 @@ +getFilename(__FILE__); +$writer = IOFactory::createWriter($spreadsheet, 'Xlsx'); +$writer->setIncludeCharts(true); +$callStartTime = microtime(true); +$writer->save($filename); +$helper->logWrite($writer, $filename, $callStartTime); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_bar_stacked.php b/vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_bar_stacked.php new file mode 100644 index 0000000..7ba4d8d --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_bar_stacked.php @@ -0,0 +1,107 @@ +getActiveSheet(); +$worksheet->fromArray( + [ + ['', 2010, 2011, 2012], + ['Q1', 12, 15, 21], + ['Q2', 56, 73, 86], + ['Q3', 52, 61, 69], + ['Q4', 30, 32, 0], + ] +); + +// Set the Labels for each data series we want to plot +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$dataSeriesLabels = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$B$1', null, 1), // 2010 + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$C$1', null, 1), // 2011 + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$D$1', null, 1), // 2012 +]; +// Set the X-Axis Labels +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$xAxisTickValues = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$A$2:$A$5', null, 4), // Q1 to Q4 +]; +// Set the Data values for each data series we want to plot +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$dataSeriesValues = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$B$2:$B$5', null, 4), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$C$2:$C$5', null, 4), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$D$2:$D$5', null, 4), +]; + +// Build the dataseries +$series = new DataSeries( + DataSeries::TYPE_BARCHART, // plotType + DataSeries::GROUPING_STACKED, // plotGrouping + range(0, count($dataSeriesValues) - 1), // plotOrder + $dataSeriesLabels, // plotLabel + $xAxisTickValues, // plotCategory + $dataSeriesValues // plotValues +); +// Set additional dataseries parameters +// Make it a horizontal bar rather than a vertical column graph +$series->setPlotDirection(DataSeries::DIRECTION_BAR); + +// Set the series in the plot area +$plotArea = new PlotArea(null, [$series]); +// Set the chart legend +$legend = new Legend(Legend::POSITION_RIGHT, null, false); + +$title = new Title('Test Chart'); +$yAxisLabel = new Title('Value ($k)'); + +// Create the chart +$chart = new Chart( + 'chart1', // name + $title, // title + $legend, // legend + $plotArea, // plotArea + true, // plotVisibleOnly + 0, // displayBlanksAs + null, // xAxisLabel + $yAxisLabel // yAxisLabel +); + +// Set the position where the chart should appear in the worksheet +$chart->setTopLeftPosition('A7'); +$chart->setBottomRightPosition('H20'); + +// Add the chart to the worksheet +$worksheet->addChart($chart); + +// Save Excel 2007 file +$filename = $helper->getFilename(__FILE__); +$writer = IOFactory::createWriter($spreadsheet, 'Xlsx'); +$writer->setIncludeCharts(true); +$callStartTime = microtime(true); +$writer->save($filename); +$helper->logWrite($writer, $filename, $callStartTime); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_column.php b/vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_column.php new file mode 100644 index 0000000..9ffe9d3 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_column.php @@ -0,0 +1,107 @@ +getActiveSheet(); +$worksheet->fromArray( + [ + ['', 2010, 2011, 2012], + ['Q1', 12, 15, 21], + ['Q2', 56, 73, 86], + ['Q3', 52, 61, 69], + ['Q4', 30, 32, 0], + ] +); + +// Set the Labels for each data series we want to plot +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$dataSeriesLabels = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$B$1', null, 1), // 2010 + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$C$1', null, 1), // 2011 + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$D$1', null, 1), // 2012 +]; +// Set the X-Axis Labels +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$xAxisTickValues = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$A$2:$A$5', null, 4), // Q1 to Q4 +]; +// Set the Data values for each data series we want to plot +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$dataSeriesValues = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$B$2:$B$5', null, 4), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$C$2:$C$5', null, 4), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$D$2:$D$5', null, 4), +]; + +// Build the dataseries +$series = new DataSeries( + DataSeries::TYPE_BARCHART, // plotType + DataSeries::GROUPING_STANDARD, // plotGrouping + range(0, count($dataSeriesValues) - 1), // plotOrder + $dataSeriesLabels, // plotLabel + $xAxisTickValues, // plotCategory + $dataSeriesValues // plotValues +); +// Set additional dataseries parameters +// Make it a vertical column rather than a horizontal bar graph +$series->setPlotDirection(DataSeries::DIRECTION_COL); + +// Set the series in the plot area +$plotArea = new PlotArea(null, [$series]); +// Set the chart legend +$legend = new Legend(Legend::POSITION_RIGHT, null, false); + +$title = new Title('Test Column Chart'); +$yAxisLabel = new Title('Value ($k)'); + +// Create the chart +$chart = new Chart( + 'chart1', // name + $title, // title + $legend, // legend + $plotArea, // plotArea + true, // plotVisibleOnly + 0, // displayBlanksAs + null, // xAxisLabel + $yAxisLabel // yAxisLabel +); + +// Set the position where the chart should appear in the worksheet +$chart->setTopLeftPosition('A7'); +$chart->setBottomRightPosition('H20'); + +// Add the chart to the worksheet +$worksheet->addChart($chart); + +// Save Excel 2007 file +$filename = $helper->getFilename(__FILE__); +$writer = IOFactory::createWriter($spreadsheet, 'Xlsx'); +$writer->setIncludeCharts(true); +$callStartTime = microtime(true); +$writer->save($filename); +$helper->logWrite($writer, $filename, $callStartTime); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_column_2.php b/vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_column_2.php new file mode 100644 index 0000000..bba9210 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_column_2.php @@ -0,0 +1,116 @@ +getActiveSheet(); +$worksheet->fromArray( + [ + ['', '', 'Budget', 'Forecast', 'Actual'], + ['2010', 'Q1', 47, 44, 43], + ['', 'Q2', 56, 53, 50], + ['', 'Q3', 52, 46, 45], + ['', 'Q4', 45, 40, 40], + ['2011', 'Q1', 51, 42, 46], + ['', 'Q2', 53, 58, 56], + ['', 'Q3', 64, 66, 69], + ['', 'Q4', 54, 55, 56], + ['2012', 'Q1', 49, 52, 58], + ['', 'Q2', 68, 73, 86], + ['', 'Q3', 72, 78, 0], + ['', 'Q4', 50, 60, 0], + ] +); + +// Set the Labels for each data series we want to plot +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$dataSeriesLabels = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$C$1', null, 1), // 'Budget' + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$D$1', null, 1), // 'Forecast' + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$E$1', null, 1), // 'Actual' +]; +// Set the X-Axis Labels +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$xAxisTickValues = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$A$2:$B$13', null, 12), // Q1 to Q4 for 2010 to 2012 +]; +// Set the Data values for each data series we want to plot +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$dataSeriesValues = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$C$2:$C$13', null, 12), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$D$2:$D$13', null, 12), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$E$2:$E$13', null, 12), +]; + +// Build the dataseries +$series = new DataSeries( + DataSeries::TYPE_BARCHART, // plotType + DataSeries::GROUPING_CLUSTERED, // plotGrouping + range(0, count($dataSeriesValues) - 1), // plotOrder + $dataSeriesLabels, // plotLabel + $xAxisTickValues, // plotCategory + $dataSeriesValues // plotValues +); +// Set additional dataseries parameters +// Make it a vertical column rather than a horizontal bar graph +$series->setPlotDirection(DataSeries::DIRECTION_COL); + +// Set the series in the plot area +$plotArea = new PlotArea(null, [$series]); +// Set the chart legend +$legend = new Legend(Legend::POSITION_BOTTOM, null, false); + +$title = new Title('Test Grouped Column Chart'); +$xAxisLabel = new Title('Financial Period'); +$yAxisLabel = new Title('Value ($k)'); + +// Create the chart +$chart = new Chart( + 'chart1', // name + $title, // title + $legend, // legend + $plotArea, // plotArea + true, // plotVisibleOnly + 0, // displayBlanksAs + $xAxisLabel, // xAxisLabel + $yAxisLabel // yAxisLabel +); + +// Set the position where the chart should appear in the worksheet +$chart->setTopLeftPosition('G2'); +$chart->setBottomRightPosition('P20'); + +// Add the chart to the worksheet +$worksheet->addChart($chart); + +// Save Excel 2007 file +$filename = $helper->getFilename(__FILE__); +$writer = IOFactory::createWriter($spreadsheet, 'Xlsx'); +$writer->setIncludeCharts(true); +$callStartTime = microtime(true); +$writer->save($filename); +$helper->logWrite($writer, $filename, $callStartTime); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_composite.php b/vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_composite.php new file mode 100644 index 0000000..83dc34a --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_composite.php @@ -0,0 +1,160 @@ +getActiveSheet(); +$worksheet->fromArray( + [ + ['', 'Rainfall (mm)', 'Temperature (°F)', 'Humidity (%)'], + ['Jan', 78, 52, 61], + ['Feb', 64, 54, 62], + ['Mar', 62, 57, 63], + ['Apr', 21, 62, 59], + ['May', 11, 75, 60], + ['Jun', 1, 75, 57], + ['Jul', 1, 79, 56], + ['Aug', 1, 79, 59], + ['Sep', 10, 75, 60], + ['Oct', 40, 68, 63], + ['Nov', 69, 62, 64], + ['Dec', 89, 57, 66], + ] +); + +// Set the Labels for each data series we want to plot +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$dataSeriesLabels1 = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$B$1', null, 1), // Temperature +]; +$dataSeriesLabels2 = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$C$1', null, 1), // Rainfall +]; +$dataSeriesLabels3 = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$D$1', null, 1), // Humidity +]; + +// Set the X-Axis Labels +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$xAxisTickValues = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$A$2:$A$13', null, 12), // Jan to Dec +]; + +// Set the Data values for each data series we want to plot +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$dataSeriesValues1 = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$B$2:$B$13', null, 12), +]; + +// Build the dataseries +$series1 = new DataSeries( + DataSeries::TYPE_BARCHART, // plotType + DataSeries::GROUPING_CLUSTERED, // plotGrouping + range(0, count($dataSeriesValues1) - 1), // plotOrder + $dataSeriesLabels1, // plotLabel + $xAxisTickValues, // plotCategory + $dataSeriesValues1 // plotValues +); +// Set additional dataseries parameters +// Make it a vertical column rather than a horizontal bar graph +$series1->setPlotDirection(DataSeries::DIRECTION_COL); + +// Set the Data values for each data series we want to plot +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$dataSeriesValues2 = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$C$2:$C$13', null, 12), +]; + +// Build the dataseries +$series2 = new DataSeries( + DataSeries::TYPE_LINECHART, // plotType + DataSeries::GROUPING_STANDARD, // plotGrouping + range(0, count($dataSeriesValues2) - 1), // plotOrder + $dataSeriesLabels2, // plotLabel + [], // plotCategory + $dataSeriesValues2 // plotValues +); + +// Set the Data values for each data series we want to plot +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$dataSeriesValues3 = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$D$2:$D$13', null, 12), +]; + +// Build the dataseries +$series3 = new DataSeries( + DataSeries::TYPE_AREACHART, // plotType + DataSeries::GROUPING_STANDARD, // plotGrouping + range(0, count($dataSeriesValues2) - 1), // plotOrder + $dataSeriesLabels3, // plotLabel + [], // plotCategory + $dataSeriesValues3 // plotValues +); + +// Set the series in the plot area +$plotArea = new PlotArea(null, [$series1, $series2, $series3]); +// Set the chart legend +$legend = new Legend(Legend::POSITION_RIGHT, null, false); + +$title = new Title('Average Weather Chart for Crete'); + +// Create the chart +$chart = new Chart( + 'chart1', // name + $title, // title + $legend, // legend + $plotArea, // plotArea + true, // plotVisibleOnly + 0, // displayBlanksAs + null, // xAxisLabel + null // yAxisLabel +); + +// Set the position where the chart should appear in the worksheet +$chart->setTopLeftPosition('F2'); +$chart->setBottomRightPosition('O16'); + +// Add the chart to the worksheet +$worksheet->addChart($chart); + +// Save Excel 2007 file +$filename = $helper->getFilename(__FILE__); +$writer = IOFactory::createWriter($spreadsheet, 'Xlsx'); +$writer->setIncludeCharts(true); +$callStartTime = microtime(true); +$writer->save($filename); +$helper->logWrite($writer, $filename, $callStartTime); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_line.php b/vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_line.php new file mode 100644 index 0000000..bdaf011 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_line.php @@ -0,0 +1,105 @@ +getActiveSheet(); +$worksheet->fromArray( + [ + ['', 2010, 2011, 2012], + ['Q1', 12, 15, 21], + ['Q2', 56, 73, 86], + ['Q3', 52, 61, 69], + ['Q4', 30, 32, 0], + ] +); + +// Set the Labels for each data series we want to plot +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$dataSeriesLabels = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$B$1', null, 1), // 2010 + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$C$1', null, 1), // 2011 + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$D$1', null, 1), // 2012 +]; +// Set the X-Axis Labels +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$xAxisTickValues = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$A$2:$A$5', null, 4), // Q1 to Q4 +]; +// Set the Data values for each data series we want to plot +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$dataSeriesValues = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$B$2:$B$5', null, 4), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$C$2:$C$5', null, 4), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$D$2:$D$5', null, 4), +]; +$dataSeriesValues[2]->setLineWidth(60000); + +// Build the dataseries +$series = new DataSeries( + DataSeries::TYPE_LINECHART, // plotType + DataSeries::GROUPING_STACKED, // plotGrouping + range(0, count($dataSeriesValues) - 1), // plotOrder + $dataSeriesLabels, // plotLabel + $xAxisTickValues, // plotCategory + $dataSeriesValues // plotValues +); + +// Set the series in the plot area +$plotArea = new PlotArea(null, [$series]); +// Set the chart legend +$legend = new Legend(Legend::POSITION_TOPRIGHT, null, false); + +$title = new Title('Test Stacked Line Chart'); +$yAxisLabel = new Title('Value ($k)'); + +// Create the chart +$chart = new Chart( + 'chart1', // name + $title, // title + $legend, // legend + $plotArea, // plotArea + true, // plotVisibleOnly + 0, // displayBlanksAs + null, // xAxisLabel + $yAxisLabel // yAxisLabel +); + +// Set the position where the chart should appear in the worksheet +$chart->setTopLeftPosition('A7'); +$chart->setBottomRightPosition('H20'); + +// Add the chart to the worksheet +$worksheet->addChart($chart); + +// Save Excel 2007 file +$filename = $helper->getFilename(__FILE__); +$writer = IOFactory::createWriter($spreadsheet, 'Xlsx'); +$writer->setIncludeCharts(true); +$callStartTime = microtime(true); +$writer->save($filename); +$helper->logWrite($writer, $filename, $callStartTime); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_multiple_charts.php b/vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_multiple_charts.php new file mode 100644 index 0000000..10a11e1 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_multiple_charts.php @@ -0,0 +1,179 @@ +getActiveSheet(); +$worksheet->fromArray( + [ + ['', 2010, 2011, 2012], + ['Q1', 12, 15, 21], + ['Q2', 56, 73, 86], + ['Q3', 52, 61, 69], + ['Q4', 30, 32, 0], + ] +); + +// Set the Labels for each data series we want to plot +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$dataSeriesLabels1 = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$B$1', null, 1), // 2010 + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$C$1', null, 1), // 2011 + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$D$1', null, 1), // 2012 +]; +// Set the X-Axis Labels +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$xAxisTickValues1 = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$A$2:$A$5', null, 4), // Q1 to Q4 +]; +// Set the Data values for each data series we want to plot +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$dataSeriesValues1 = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$B$2:$B$5', null, 4), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$C$2:$C$5', null, 4), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$D$2:$D$5', null, 4), +]; + +// Build the dataseries +$series1 = new DataSeries( + DataSeries::TYPE_AREACHART, // plotType + DataSeries::GROUPING_PERCENT_STACKED, // plotGrouping + range(0, count($dataSeriesValues1) - 1), // plotOrder + $dataSeriesLabels1, // plotLabel + $xAxisTickValues1, // plotCategory + $dataSeriesValues1 // plotValues +); + +// Set the series in the plot area +$plotArea1 = new PlotArea(null, [$series1]); +// Set the chart legend +$legend1 = new Legend(Legend::POSITION_TOPRIGHT, null, false); + +$title1 = new Title('Test %age-Stacked Area Chart'); +$yAxisLabel1 = new Title('Value ($k)'); + +// Create the chart +$chart1 = new Chart( + 'chart1', // name + $title1, // title + $legend1, // legend + $plotArea1, // plotArea + true, // plotVisibleOnly + 0, // displayBlanksAs + null, // xAxisLabel + $yAxisLabel1 // yAxisLabel +); + +// Set the position where the chart should appear in the worksheet +$chart1->setTopLeftPosition('A7'); +$chart1->setBottomRightPosition('H20'); + +// Add the chart to the worksheet +$worksheet->addChart($chart1); + +// Set the Labels for each data series we want to plot +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$dataSeriesLabels2 = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$B$1', null, 1), // 2010 + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$C$1', null, 1), // 2011 + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$D$1', null, 1), // 2012 +]; +// Set the X-Axis Labels +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$xAxisTickValues2 = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$A$2:$A$5', null, 4), // Q1 to Q4 +]; +// Set the Data values for each data series we want to plot +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$dataSeriesValues2 = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$B$2:$B$5', null, 4), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$C$2:$C$5', null, 4), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$D$2:$D$5', null, 4), +]; + +// Build the dataseries +$series2 = new DataSeries( + DataSeries::TYPE_BARCHART, // plotType + DataSeries::GROUPING_STANDARD, // plotGrouping + range(0, count($dataSeriesValues2) - 1), // plotOrder + $dataSeriesLabels2, // plotLabel + $xAxisTickValues2, // plotCategory + $dataSeriesValues2 // plotValues +); +// Set additional dataseries parameters +// Make it a vertical column rather than a horizontal bar graph +$series2->setPlotDirection(DataSeries::DIRECTION_COL); + +// Set the series in the plot area +$plotArea2 = new PlotArea(null, [$series2]); +// Set the chart legend +$legend2 = new Legend(Legend::POSITION_RIGHT, null, false); + +$title2 = new Title('Test Column Chart'); +$yAxisLabel2 = new Title('Value ($k)'); + +// Create the chart +$chart2 = new Chart( + 'chart2', // name + $title2, // title + $legend2, // legend + $plotArea2, // plotArea + true, // plotVisibleOnly + 0, // displayBlanksAs + null, // xAxisLabel + $yAxisLabel2 // yAxisLabel +); + +// Set the position where the chart should appear in the worksheet +$chart2->setTopLeftPosition('I7'); +$chart2->setBottomRightPosition('P20'); + +// Add the chart to the worksheet +$worksheet->addChart($chart2); + +// Save Excel 2007 file +$filename = $helper->getFilename(__FILE__); +$writer = IOFactory::createWriter($spreadsheet, 'Xlsx'); +$writer->setIncludeCharts(true); +$callStartTime = microtime(true); +$writer->save($filename); +$helper->logWrite($writer, $filename, $callStartTime); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_pie.php b/vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_pie.php new file mode 100644 index 0000000..d4ec075 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_pie.php @@ -0,0 +1,175 @@ +getActiveSheet(); +$worksheet->fromArray( + [ + ['', 2010, 2011, 2012], + ['Q1', 12, 15, 21], + ['Q2', 56, 73, 86], + ['Q3', 52, 61, 69], + ['Q4', 30, 32, 0], + ] +); + +// Set the Labels for each data series we want to plot +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$dataSeriesLabels1 = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$C$1', null, 1), // 2011 +]; +// Set the X-Axis Labels +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$xAxisTickValues1 = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$A$2:$A$5', null, 4), // Q1 to Q4 +]; +// Set the Data values for each data series we want to plot +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$dataSeriesValues1 = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$C$2:$C$5', null, 4), +]; + +// Build the dataseries +$series1 = new DataSeries( + DataSeries::TYPE_PIECHART, // plotType + null, // plotGrouping (Pie charts don't have any grouping) + range(0, count($dataSeriesValues1) - 1), // plotOrder + $dataSeriesLabels1, // plotLabel + $xAxisTickValues1, // plotCategory + $dataSeriesValues1 // plotValues +); + +// Set up a layout object for the Pie chart +$layout1 = new Layout(); +$layout1->setShowVal(true); +$layout1->setShowPercent(true); + +// Set the series in the plot area +$plotArea1 = new PlotArea($layout1, [$series1]); +// Set the chart legend +$legend1 = new Legend(Legend::POSITION_RIGHT, null, false); + +$title1 = new Title('Test Pie Chart'); + +// Create the chart +$chart1 = new Chart( + 'chart1', // name + $title1, // title + $legend1, // legend + $plotArea1, // plotArea + true, // plotVisibleOnly + 0, // displayBlanksAs + null, // xAxisLabel + null // yAxisLabel - Pie charts don't have a Y-Axis +); + +// Set the position where the chart should appear in the worksheet +$chart1->setTopLeftPosition('A7'); +$chart1->setBottomRightPosition('H20'); + +// Add the chart to the worksheet +$worksheet->addChart($chart1); + +// Set the Labels for each data series we want to plot +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$dataSeriesLabels2 = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$C$1', null, 1), // 2011 +]; +// Set the X-Axis Labels +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$xAxisTickValues2 = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$A$2:$A$5', null, 4), // Q1 to Q4 +]; +// Set the Data values for each data series we want to plot +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$dataSeriesValues2 = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$C$2:$C$5', null, 4), +]; + +// Build the dataseries +$series2 = new DataSeries( + DataSeries::TYPE_DONUTCHART, // plotType + null, // plotGrouping (Donut charts don't have any grouping) + range(0, count($dataSeriesValues2) - 1), // plotOrder + $dataSeriesLabels2, // plotLabel + $xAxisTickValues2, // plotCategory + $dataSeriesValues2 // plotValues +); + +// Set up a layout object for the Pie chart +$layout2 = new Layout(); +$layout2->setShowVal(true); +$layout2->setShowCatName(true); + +// Set the series in the plot area +$plotArea2 = new PlotArea($layout2, [$series2]); + +$title2 = new Title('Test Donut Chart'); + +// Create the chart +$chart2 = new Chart( + 'chart2', // name + $title2, // title + null, // legend + $plotArea2, // plotArea + true, // plotVisibleOnly + 0, // displayBlanksAs + null, // xAxisLabel + null // yAxisLabel - Like Pie charts, Donut charts don't have a Y-Axis +); + +// Set the position where the chart should appear in the worksheet +$chart2->setTopLeftPosition('I7'); +$chart2->setBottomRightPosition('P20'); + +// Add the chart to the worksheet +$worksheet->addChart($chart2); + +// Save Excel 2007 file +$filename = $helper->getFilename(__FILE__); +$writer = IOFactory::createWriter($spreadsheet, 'Xlsx'); +$writer->setIncludeCharts(true); +$callStartTime = microtime(true); +$writer->save($filename); +$helper->logWrite($writer, $filename, $callStartTime); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_pie_custom_colors.php b/vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_pie_custom_colors.php new file mode 100644 index 0000000..727a0cd --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_pie_custom_colors.php @@ -0,0 +1,183 @@ +getActiveSheet(); +$worksheet->fromArray( + [ + ['', 2010, 2011, 2012], + ['Q1', 12, 15, 21], + ['Q2', 56, 73, 86], + ['Q3', 52, 61, 69], + ['Q4', 30, 32, 0], + ] +); + +// Custom colors for dataSeries (gray, blue, red, orange) +$colors = [ + 'cccccc', '00abb8', 'b8292f', 'eb8500', +]; + +// Set the Labels for each data series we want to plot +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$dataSeriesLabels1 = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$C$1', null, 1), // 2011 +]; +// Set the X-Axis Labels +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$xAxisTickValues1 = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$A$2:$A$5', null, 4), // Q1 to Q4 +]; +// Set the Data values for each data series we want to plot +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +// Custom colors +$dataSeriesValues1 = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$C$2:$C$5', null, 4, [], null, $colors), +]; + +// Build the dataseries +$series1 = new DataSeries( + DataSeries::TYPE_PIECHART, // plotType + null, // plotGrouping (Pie charts don't have any grouping) + range(0, count($dataSeriesValues1) - 1), // plotOrder + $dataSeriesLabels1, // plotLabel + $xAxisTickValues1, // plotCategory + $dataSeriesValues1 // plotValues +); + +// Set up a layout object for the Pie chart +$layout1 = new Layout(); +$layout1->setShowVal(true); +$layout1->setShowPercent(true); + +// Set the series in the plot area +$plotArea1 = new PlotArea($layout1, [$series1]); +// Set the chart legend +$legend1 = new Legend(Legend::POSITION_RIGHT, null, false); + +$title1 = new Title('Test Pie Chart'); + +// Create the chart +$chart1 = new Chart( + 'chart1', // name + $title1, // title + $legend1, // legend + $plotArea1, // plotArea + true, // plotVisibleOnly + 0, // displayBlanksAs + null, // xAxisLabel + null // yAxisLabel - Pie charts don't have a Y-Axis +); + +// Set the position where the chart should appear in the worksheet +$chart1->setTopLeftPosition('A7'); +$chart1->setBottomRightPosition('H20'); + +// Add the chart to the worksheet +$worksheet->addChart($chart1); + +// Set the Labels for each data series we want to plot +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$dataSeriesLabels2 = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$C$1', null, 1), // 2011 +]; +// Set the X-Axis Labels +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$xAxisTickValues2 = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$A$2:$A$5', null, 4), // Q1 to Q4 +]; +// Set the Data values for each data series we want to plot +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +// Custom colors +$dataSeriesValues2 = [ + $dataSeriesValues2Element = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$C$2:$C$5', null, 4), +]; +$dataSeriesValues2Element->setFillColor($colors); + +// Build the dataseries +$series2 = new DataSeries( + DataSeries::TYPE_DONUTCHART, // plotType + null, // plotGrouping (Donut charts don't have any grouping) + range(0, count($dataSeriesValues2) - 1), // plotOrder + $dataSeriesLabels2, // plotLabel + $xAxisTickValues2, // plotCategory + $dataSeriesValues2 // plotValues +); + +// Set up a layout object for the Pie chart +$layout2 = new Layout(); +$layout2->setShowVal(true); +$layout2->setShowCatName(true); + +// Set the series in the plot area +$plotArea2 = new PlotArea($layout2, [$series2]); + +$title2 = new Title('Test Donut Chart'); + +// Create the chart +$chart2 = new Chart( + 'chart2', // name + $title2, // title + null, // legend + $plotArea2, // plotArea + true, // plotVisibleOnly + 0, // displayBlanksAs + null, // xAxisLabel + null // yAxisLabel - Like Pie charts, Donut charts don't have a Y-Axis +); + +// Set the position where the chart should appear in the worksheet +$chart2->setTopLeftPosition('I7'); +$chart2->setBottomRightPosition('P20'); + +// Add the chart to the worksheet +$worksheet->addChart($chart2); + +// Save Excel 2007 file +$filename = $helper->getFilename(__FILE__); +$writer = IOFactory::createWriter($spreadsheet, 'Xlsx'); +$writer->setIncludeCharts(true); +$callStartTime = microtime(true); +$writer->save($filename); +$helper->logWrite($writer, $filename, $callStartTime); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_radar.php b/vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_radar.php new file mode 100644 index 0000000..e57914a --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_radar.php @@ -0,0 +1,117 @@ +getActiveSheet(); +$worksheet->fromArray( + [ + ['', 2010, 2011, 2012], + ['Jan', 47, 45, 71], + ['Feb', 56, 73, 86], + ['Mar', 52, 61, 69], + ['Apr', 40, 52, 60], + ['May', 42, 55, 71], + ['Jun', 58, 63, 76], + ['Jul', 53, 61, 89], + ['Aug', 46, 69, 85], + ['Sep', 62, 75, 81], + ['Oct', 51, 70, 96], + ['Nov', 55, 66, 89], + ['Dec', 68, 62, 0], + ] +); + +// Set the Labels for each data series we want to plot +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$dataSeriesLabels = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$C$1', null, 1), // 2011 + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$D$1', null, 1), // 2012 +]; +// Set the X-Axis Labels +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$xAxisTickValues = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$A$2:$A$13', null, 12), // Jan to Dec + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$A$2:$A$13', null, 12), // Jan to Dec +]; +// Set the Data values for each data series we want to plot +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$dataSeriesValues = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$C$2:$C$13', null, 12), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$D$2:$D$13', null, 12), +]; + +// Build the dataseries +$series = new DataSeries( + DataSeries::TYPE_RADARCHART, // plotType + null, // plotGrouping (Radar charts don't have any grouping) + range(0, count($dataSeriesValues) - 1), // plotOrder + $dataSeriesLabels, // plotLabel + $xAxisTickValues, // plotCategory + $dataSeriesValues, // plotValues + null, // plotDirection + null, // smooth line + DataSeries::STYLE_MARKER // plotStyle +); + +// Set up a layout object for the Pie chart +$layout = new Layout(); + +// Set the series in the plot area +$plotArea = new PlotArea($layout, [$series]); +// Set the chart legend +$legend = new Legend(Legend::POSITION_RIGHT, null, false); + +$title = new Title('Test Radar Chart'); + +// Create the chart +$chart = new Chart( + 'chart1', // name + $title, // title + $legend, // legend + $plotArea, // plotArea + true, // plotVisibleOnly + 0, // displayBlanksAs + null, // xAxisLabel + null // yAxisLabel - Radar charts don't have a Y-Axis +); + +// Set the position where the chart should appear in the worksheet +$chart->setTopLeftPosition('F2'); +$chart->setBottomRightPosition('M15'); + +// Add the chart to the worksheet +$worksheet->addChart($chart); + +// Save Excel 2007 file +$filename = $helper->getFilename(__FILE__); +$writer = IOFactory::createWriter($spreadsheet, 'Xlsx'); +$writer->setIncludeCharts(true); +$callStartTime = microtime(true); +$writer->save($filename); +$helper->logWrite($writer, $filename, $callStartTime); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_scatter.php b/vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_scatter.php new file mode 100644 index 0000000..12fc2bd --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_scatter.php @@ -0,0 +1,101 @@ +getActiveSheet(); +$worksheet->fromArray( + [ + ['', 2010, 2011, 2012], + ['Q1', 12, 15, 21], + ['Q2', 56, 73, 86], + ['Q3', 52, 61, 69], + ['Q4', 30, 32, 0], + ] +); + +// Set the Labels for each data series we want to plot +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$dataSeriesLabels = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$B$1', null, 1), // 2010 + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$C$1', null, 1), // 2011 + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$D$1', null, 1), // 2012 +]; +// Set the X-Axis Labels +$xAxisTickValues = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$A$2:$A$5', null, 4), // Q1 to Q4 +]; +// Set the Data values for each data series we want to plot +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$dataSeriesValues = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$B$2:$B$5', null, 4), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$C$2:$C$5', null, 4), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$D$2:$D$5', null, 4), +]; + +// Build the dataseries +$series = new DataSeries( + DataSeries::TYPE_SCATTERCHART, // plotType + null, // plotGrouping (Scatter charts don't have any grouping) + range(0, count($dataSeriesValues) - 1), // plotOrder + $dataSeriesLabels, // plotLabel + $xAxisTickValues, // plotCategory + $dataSeriesValues, // plotValues + null, // plotDirection + null, // smooth line + DataSeries::STYLE_LINEMARKER // plotStyle +); + +// Set the series in the plot area +$plotArea = new PlotArea(null, [$series]); +// Set the chart legend +$legend = new Legend(Legend::POSITION_TOPRIGHT, null, false); + +$title = new Title('Test Scatter Chart'); +$yAxisLabel = new Title('Value ($k)'); + +// Create the chart +$chart = new Chart( + 'chart1', // name + $title, // title + $legend, // legend + $plotArea, // plotArea + true, // plotVisibleOnly + 0, // displayBlanksAs + null, // xAxisLabel + $yAxisLabel // yAxisLabel +); + +// Set the position where the chart should appear in the worksheet +$chart->setTopLeftPosition('A7'); +$chart->setBottomRightPosition('H20'); + +// Add the chart to the worksheet +$worksheet->addChart($chart); + +// Save Excel 2007 file +$filename = $helper->getFilename(__FILE__); +$writer = IOFactory::createWriter($spreadsheet, 'Xlsx'); +$writer->setIncludeCharts(true); +$callStartTime = microtime(true); +$writer->save($filename); +$helper->logWrite($writer, $filename, $callStartTime); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_stock.php b/vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_stock.php new file mode 100644 index 0000000..7a9f727 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Chart/33_Chart_create_stock.php @@ -0,0 +1,113 @@ +getActiveSheet(); +$worksheet->fromArray( + [ + ['Counts', 'Max', 'Min', 'Min Threshold', 'Max Threshold'], + [10, 10, 5, 0, 50], + [30, 20, 10, 0, 50], + [20, 30, 15, 0, 50], + [40, 10, 0, 0, 50], + [100, 40, 5, 0, 50], + ], + null, + 'A1', + true +); +$worksheet->getStyle('B2:E6')->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_NUMBER_00); + +// Set the Labels for each data series we want to plot +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$dataSeriesLabels = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$B$1', null, 1), //Max / Open + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$C$1', null, 1), //Min / Close + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$D$1', null, 1), //Min Threshold / Min + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$E$1', null, 1), //Max Threshold / Max +]; +// Set the X-Axis Labels +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$xAxisTickValues = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$A$2:$A$6', null, 5), // Counts +]; +// Set the Data values for each data series we want to plot +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$dataSeriesValues = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$B$2:$B$6', null, 5), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$C$2:$C$6', null, 5), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$D$2:$D$6', null, 5), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$E$2:$E$6', null, 5), +]; + +// Build the dataseries +$series = new DataSeries( + DataSeries::TYPE_STOCKCHART, // plotType + null, // plotGrouping - if we set this to not null, then xlsx throws error + range(0, count($dataSeriesValues) - 1), // plotOrder + $dataSeriesLabels, // plotLabel + $xAxisTickValues, // plotCategory + $dataSeriesValues // plotValues +); + +// Set the series in the plot area +$plotArea = new PlotArea(null, [$series]); +// Set the chart legend +$legend = new Legend(Legend::POSITION_RIGHT, null, false); + +$title = new Title('Test Stock Chart'); +$xAxisLabel = new Title('Counts'); +$yAxisLabel = new Title('Values'); + +// Create the chart +$chart = new Chart( + 'stock-chart', // name + $title, // title + $legend, // legend + $plotArea, // plotArea + true, // plotVisibleOnly + 0, // displayBlanksAs + $xAxisLabel, // xAxisLabel + $yAxisLabel // yAxisLabel +); + +// Set the position where the chart should appear in the worksheet +$chart->setTopLeftPosition('A7'); +$chart->setBottomRightPosition('H20'); + +// Add the chart to the worksheet +$worksheet->addChart($chart); + +// Save Excel 2007 file +$filename = $helper->getFilename(__FILE__); +$writer = IOFactory::createWriter($spreadsheet, 'Xlsx'); +$writer->setIncludeCharts(true); +$callStartTime = microtime(true); +$writer->save($filename); +$helper->logWrite($writer, $filename, $callStartTime); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Chart/34_Chart_update.php b/vendor/phpoffice/phpspreadsheet/samples/Chart/34_Chart_update.php new file mode 100644 index 0000000..638d2e0 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Chart/34_Chart_update.php @@ -0,0 +1,38 @@ +getTemporaryFilename(); +$writer = new Xlsx($sampleSpreadsheet); +$writer->save($filename); + +$helper->log('Load from Xlsx file'); +$reader = IOFactory::createReader('Xlsx'); +$reader->setIncludeCharts(true); +$spreadsheet = $reader->load($filename); + +$helper->log('Update cell data values that are displayed in the chart'); +$worksheet = $spreadsheet->getActiveSheet(); +$worksheet->fromArray( + [ + [50 - 12, 50 - 15, 50 - 21], + [50 - 56, 50 - 73, 50 - 86], + [50 - 52, 50 - 61, 50 - 69], + [50 - 30, 50 - 32, 50], + ], + null, + 'B2' +); + +// Save Excel 2007 file +$filename = $helper->getFilename(__FILE__); +$writer = IOFactory::createWriter($spreadsheet, 'Xlsx'); +$writer->setIncludeCharts(true); +$callStartTime = microtime(true); +$writer->save($filename); +$helper->logWrite($writer, $filename, $callStartTime); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Chart/35_Chart_render.php b/vendor/phpoffice/phpspreadsheet/samples/Chart/35_Chart_render.php new file mode 100644 index 0000000..9638c67 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Chart/35_Chart_render.php @@ -0,0 +1,75 @@ + 1)) { + $inputFileNames = []; + for ($i = 1; $i < $argc; ++$i) { + $inputFileNames[] = __DIR__ . '/../templates/' . $argv[$i]; + } +} else { + $inputFileNames = glob($inputFileNames); +} +foreach ($inputFileNames as $inputFileName) { + $inputFileNameShort = basename($inputFileName); + + if (!file_exists($inputFileName)) { + $helper->log('File ' . $inputFileNameShort . ' does not exist'); + + continue; + } + + $helper->log("Load Test from $inputFileType file " . $inputFileNameShort); + + $reader = IOFactory::createReader($inputFileType); + $reader->setIncludeCharts(true); + $spreadsheet = $reader->load($inputFileName); + + $helper->log('Iterate worksheets looking at the charts'); + foreach ($spreadsheet->getWorksheetIterator() as $worksheet) { + $sheetName = $worksheet->getTitle(); + $helper->log('Worksheet: ' . $sheetName); + + $chartNames = $worksheet->getChartNames(); + if (empty($chartNames)) { + $helper->log(' There are no charts in this worksheet'); + } else { + natsort($chartNames); + foreach ($chartNames as $i => $chartName) { + $chart = $worksheet->getChartByName($chartName); + if ($chart->getTitle() !== null) { + $caption = '"' . implode(' ', $chart->getTitle()->getCaption()) . '"'; + } else { + $caption = 'Untitled'; + } + $helper->log(' ' . $chartName . ' - ' . $caption); + + $jpegFile = $helper->getFilename('35-' . $inputFileNameShort, 'png'); + if (file_exists($jpegFile)) { + unlink($jpegFile); + } + + try { + $chart->render($jpegFile); + $helper->log('Rendered image: ' . $jpegFile); + } catch (Exception $e) { + $helper->log('Error rendering chart: ' . $e->getMessage()); + } + } + } + } + + $spreadsheet->disconnectWorksheets(); + unset($spreadsheet); +} + +$helper->log('Done rendering charts as images'); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Header.php b/vendor/phpoffice/phpspreadsheet/samples/Header.php new file mode 100644 index 0000000..fb8bd98 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Header.php @@ -0,0 +1,64 @@ +isCli()) { + return; +} +?> + + + <?php echo $helper->getPageTitle(); ?> + + + + + + + + + + +
    + + getPageHeading(); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Pdf/21_Pdf_Domdf.php b/vendor/phpoffice/phpspreadsheet/samples/Pdf/21_Pdf_Domdf.php new file mode 100644 index 0000000..aea4c96 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Pdf/21_Pdf_Domdf.php @@ -0,0 +1,20 @@ +log('Hide grid lines'); +$spreadsheet->getActiveSheet()->setShowGridLines(false); + +$helper->log('Set orientation to landscape'); +$spreadsheet->getActiveSheet()->getPageSetup()->setOrientation(PageSetup::ORIENTATION_LANDSCAPE); + +$className = \PhpOffice\PhpSpreadsheet\Writer\Pdf\Dompdf::class; +$helper->log("Write to PDF format using {$className}"); +IOFactory::registerWriter('Pdf', $className); + +// Save +$helper->write($spreadsheet, __FILE__, ['Pdf']); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Pdf/21_Pdf_TCPDF.php b/vendor/phpoffice/phpspreadsheet/samples/Pdf/21_Pdf_TCPDF.php new file mode 100644 index 0000000..9a8593e --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Pdf/21_Pdf_TCPDF.php @@ -0,0 +1,20 @@ +log('Hide grid lines'); +$spreadsheet->getActiveSheet()->setShowGridLines(false); + +$helper->log('Set orientation to landscape'); +$spreadsheet->getActiveSheet()->getPageSetup()->setOrientation(PageSetup::ORIENTATION_LANDSCAPE); + +$className = \PhpOffice\PhpSpreadsheet\Writer\Pdf\Tcpdf::class; +$helper->log("Write to PDF format using {$className}"); +IOFactory::registerWriter('Pdf', $className); + +// Save +$helper->write($spreadsheet, __FILE__, ['Pdf']); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Pdf/21_Pdf_mPDF.php b/vendor/phpoffice/phpspreadsheet/samples/Pdf/21_Pdf_mPDF.php new file mode 100644 index 0000000..b99c225 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Pdf/21_Pdf_mPDF.php @@ -0,0 +1,20 @@ +log('Hide grid lines'); +$spreadsheet->getActiveSheet()->setShowGridLines(false); + +$helper->log('Set orientation to landscape'); +$spreadsheet->getActiveSheet()->getPageSetup()->setOrientation(PageSetup::ORIENTATION_LANDSCAPE); + +$className = \PhpOffice\PhpSpreadsheet\Writer\Pdf\Mpdf::class; +$helper->log("Write to PDF format using {$className}"); +IOFactory::registerWriter('Pdf', $className); + +// Save +$helper->write($spreadsheet, __FILE__, ['Pdf']); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Reader/01_Simple_file_reader_using_IOFactory.php b/vendor/phpoffice/phpspreadsheet/samples/Reader/01_Simple_file_reader_using_IOFactory.php new file mode 100644 index 0000000..584fd5b --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Reader/01_Simple_file_reader_using_IOFactory.php @@ -0,0 +1,11 @@ +log('Loading file ' . pathinfo($inputFileName, PATHINFO_BASENAME) . ' using IOFactory to identify the format'); +$spreadsheet = IOFactory::load($inputFileName); +$sheetData = $spreadsheet->getActiveSheet()->toArray(null, true, true, true); +var_dump($sheetData); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Reader/02_Simple_file_reader_using_a_specified_reader.php b/vendor/phpoffice/phpspreadsheet/samples/Reader/02_Simple_file_reader_using_a_specified_reader.php new file mode 100644 index 0000000..9a70512 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Reader/02_Simple_file_reader_using_a_specified_reader.php @@ -0,0 +1,13 @@ +log('Loading file ' . pathinfo($inputFileName, PATHINFO_BASENAME) . ' using ' . Xls::class); +$reader = new Xls(); +$spreadsheet = $reader->load($inputFileName); + +$sheetData = $spreadsheet->getActiveSheet()->toArray(null, true, true, true); +var_dump($sheetData); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Reader/03_Simple_file_reader_using_the_IOFactory_to_return_a_reader.php b/vendor/phpoffice/phpspreadsheet/samples/Reader/03_Simple_file_reader_using_the_IOFactory_to_return_a_reader.php new file mode 100644 index 0000000..305651d --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Reader/03_Simple_file_reader_using_the_IOFactory_to_return_a_reader.php @@ -0,0 +1,15 @@ +log('Loading file ' . pathinfo($inputFileName, PATHINFO_BASENAME) . ' using IOFactory with a defined reader type of ' . $inputFileType); +$reader = IOFactory::createReader($inputFileType); +$spreadsheet = $reader->load($inputFileName); + +$sheetData = $spreadsheet->getActiveSheet()->toArray(null, true, true, true); +var_dump($sheetData); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Reader/04_Simple_file_reader_using_the_IOFactory_to_identify_a_reader_to_use.php b/vendor/phpoffice/phpspreadsheet/samples/Reader/04_Simple_file_reader_using_the_IOFactory_to_identify_a_reader_to_use.php new file mode 100644 index 0000000..98aabfc --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Reader/04_Simple_file_reader_using_the_IOFactory_to_identify_a_reader_to_use.php @@ -0,0 +1,17 @@ +log('File ' . pathinfo($inputFileName, PATHINFO_BASENAME) . ' has been identified as an ' . $inputFileType . ' file'); + +$helper->log('Loading file ' . pathinfo($inputFileName, PATHINFO_BASENAME) . ' using IOFactory with the identified reader type'); +$reader = IOFactory::createReader($inputFileType); +$spreadsheet = $reader->load($inputFileName); + +$sheetData = $spreadsheet->getActiveSheet()->toArray(null, true, true, true); +var_dump($sheetData); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Reader/05_Simple_file_reader_using_the_read_data_only_option.php b/vendor/phpoffice/phpspreadsheet/samples/Reader/05_Simple_file_reader_using_the_read_data_only_option.php new file mode 100644 index 0000000..d3ce9d8 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Reader/05_Simple_file_reader_using_the_read_data_only_option.php @@ -0,0 +1,17 @@ +log('Loading file ' . pathinfo($inputFileName, PATHINFO_BASENAME) . ' using IOFactory with a defined reader type of ' . $inputFileType); +$reader = IOFactory::createReader($inputFileType); +$helper->log('Turning Formatting off for Load'); +$reader->setReadDataOnly(true); +$spreadsheet = $reader->load($inputFileName); + +$sheetData = $spreadsheet->getActiveSheet()->toArray(null, true, true, true); +var_dump($sheetData); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Reader/06_Simple_file_reader_loading_all_worksheets.php b/vendor/phpoffice/phpspreadsheet/samples/Reader/06_Simple_file_reader_loading_all_worksheets.php new file mode 100644 index 0000000..5507c52 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Reader/06_Simple_file_reader_loading_all_worksheets.php @@ -0,0 +1,20 @@ +log('Loading file ' . pathinfo($inputFileName, PATHINFO_BASENAME) . ' using IOFactory with a defined reader type of ' . $inputFileType); +$reader = IOFactory::createReader($inputFileType); +$helper->log('Loading all WorkSheets'); +$reader->setLoadAllSheets(); +$spreadsheet = $reader->load($inputFileName); + +$helper->log($spreadsheet->getSheetCount() . ' worksheet' . (($spreadsheet->getSheetCount() == 1) ? '' : 's') . ' loaded'); +$loadedSheetNames = $spreadsheet->getSheetNames(); +foreach ($loadedSheetNames as $sheetIndex => $loadedSheetName) { + $helper->log($sheetIndex . ' -> ' . $loadedSheetName); +} diff --git a/vendor/phpoffice/phpspreadsheet/samples/Reader/07_Simple_file_reader_loading_a_single_named_worksheet.php b/vendor/phpoffice/phpspreadsheet/samples/Reader/07_Simple_file_reader_loading_a_single_named_worksheet.php new file mode 100644 index 0000000..142a17f --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Reader/07_Simple_file_reader_loading_a_single_named_worksheet.php @@ -0,0 +1,21 @@ +log('Loading file ' . pathinfo($inputFileName, PATHINFO_BASENAME) . ' using IOFactory with a defined reader type of ' . $inputFileType); +$reader = IOFactory::createReader($inputFileType); +$helper->log('Loading Sheet "' . $sheetname . '" only'); +$reader->setLoadSheetsOnly($sheetname); +$spreadsheet = $reader->load($inputFileName); + +$helper->log($spreadsheet->getSheetCount() . ' worksheet' . (($spreadsheet->getSheetCount() == 1) ? '' : 's') . ' loaded'); +$loadedSheetNames = $spreadsheet->getSheetNames(); +foreach ($loadedSheetNames as $sheetIndex => $loadedSheetName) { + $helper->log($sheetIndex . ' -> ' . $loadedSheetName); +} diff --git a/vendor/phpoffice/phpspreadsheet/samples/Reader/08_Simple_file_reader_loading_several_named_worksheets.php b/vendor/phpoffice/phpspreadsheet/samples/Reader/08_Simple_file_reader_loading_several_named_worksheets.php new file mode 100644 index 0000000..66efc3e --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Reader/08_Simple_file_reader_loading_several_named_worksheets.php @@ -0,0 +1,21 @@ +log('Loading file ' . pathinfo($inputFileName, PATHINFO_BASENAME) . ' using IOFactory with a defined reader type of ' . $inputFileType); +$reader = IOFactory::createReader($inputFileType); +$helper->log('Loading Sheet' . ((count($sheetnames) == 1) ? '' : 's') . ' "' . implode('" and "', $sheetnames) . '" only'); +$reader->setLoadSheetsOnly($sheetnames); +$spreadsheet = $reader->load($inputFileName); + +$helper->log($spreadsheet->getSheetCount() . ' worksheet' . (($spreadsheet->getSheetCount() == 1) ? '' : 's') . ' loaded'); +$loadedSheetNames = $spreadsheet->getSheetNames(); +foreach ($loadedSheetNames as $sheetIndex => $loadedSheetName) { + $helper->log($sheetIndex . ' -> ' . $loadedSheetName); +} diff --git a/vendor/phpoffice/phpspreadsheet/samples/Reader/09_Simple_file_reader_using_a_read_filter.php b/vendor/phpoffice/phpspreadsheet/samples/Reader/09_Simple_file_reader_using_a_read_filter.php new file mode 100644 index 0000000..6e0eda1 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Reader/09_Simple_file_reader_using_a_read_filter.php @@ -0,0 +1,40 @@ += 1 && $row <= 7) { + if (in_array($column, range('A', 'E'))) { + return true; + } + } + + return false; + } +} + +$filterSubset = new MyReadFilter(); + +$helper->log('Loading file ' . pathinfo($inputFileName, PATHINFO_BASENAME) . ' using IOFactory with a defined reader type of ' . $inputFileType); +$reader = IOFactory::createReader($inputFileType); +$helper->log('Loading Sheet "' . $sheetname . '" only'); +$reader->setLoadSheetsOnly($sheetname); +$helper->log('Loading Sheet using filter'); +$reader->setReadFilter($filterSubset); +$spreadsheet = $reader->load($inputFileName); + +$sheetData = $spreadsheet->getActiveSheet()->toArray(null, true, true, true); +var_dump($sheetData); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Reader/10_Simple_file_reader_using_a_configurable_read_filter.php b/vendor/phpoffice/phpspreadsheet/samples/Reader/10_Simple_file_reader_using_a_configurable_read_filter.php new file mode 100644 index 0000000..7b3fc44 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Reader/10_Simple_file_reader_using_a_configurable_read_filter.php @@ -0,0 +1,52 @@ +startRow = $startRow; + $this->endRow = $endRow; + $this->columns = $columns; + } + + public function readCell($column, $row, $worksheetName = '') + { + if ($row >= $this->startRow && $row <= $this->endRow) { + if (in_array($column, $this->columns)) { + return true; + } + } + + return false; + } +} + +$filterSubset = new MyReadFilter(9, 15, range('G', 'K')); + +$helper->log('Loading file ' . pathinfo($inputFileName, PATHINFO_BASENAME) . ' using IOFactory with a defined reader type of ' . $inputFileType); +$reader = IOFactory::createReader($inputFileType); +$helper->log('Loading Sheet "' . $sheetname . '" only'); +$reader->setLoadSheetsOnly($sheetname); +$helper->log('Loading Sheet using configurable filter'); +$reader->setReadFilter($filterSubset); +$spreadsheet = $reader->load($inputFileName); + +$sheetData = $spreadsheet->getActiveSheet()->toArray(null, true, true, true); +var_dump($sheetData); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Reader/11_Reading_a_workbook_in_chunks_using_a_configurable_read_filter_(version_1).php b/vendor/phpoffice/phpspreadsheet/samples/Reader/11_Reading_a_workbook_in_chunks_using_a_configurable_read_filter_(version_1).php new file mode 100644 index 0000000..1856221 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Reader/11_Reading_a_workbook_in_chunks_using_a_configurable_read_filter_(version_1).php @@ -0,0 +1,64 @@ +startRow = $startRow; + $this->endRow = $startRow + $chunkSize; + } + + public function readCell($column, $row, $worksheetName = '') + { + // Only read the heading row, and the rows that were configured in the constructor + if (($row == 1) || ($row >= $this->startRow && $row < $this->endRow)) { + return true; + } + + return false; + } +} + +$helper->log('Loading file ' . pathinfo($inputFileName, PATHINFO_BASENAME) . ' using IOFactory with a defined reader type of ' . $inputFileType); +// Create a new Reader of the type defined in $inputFileType +$reader = IOFactory::createReader($inputFileType); + +// Define how many rows we want for each "chunk" +$chunkSize = 20; + +// Loop to read our worksheet in "chunk size" blocks +for ($startRow = 2; $startRow <= 240; $startRow += $chunkSize) { + $helper->log('Loading WorkSheet using configurable filter for headings row 1 and for rows ' . $startRow . ' to ' . ($startRow + $chunkSize - 1)); + // Create a new Instance of our Read Filter, passing in the limits on which rows we want to read + $chunkFilter = new ChunkReadFilter($startRow, $chunkSize); + // Tell the Reader that we want to use the new Read Filter that we've just Instantiated + $reader->setReadFilter($chunkFilter); + // Load only the rows that match our filter from $inputFileName to a PhpSpreadsheet Object + $spreadsheet = $reader->load($inputFileName); + + // Do some processing here + + $sheetData = $spreadsheet->getActiveSheet()->toArray(null, true, true, true); + var_dump($sheetData); +} diff --git a/vendor/phpoffice/phpspreadsheet/samples/Reader/12_Reading_a_workbook_in_chunks_using_a_configurable_read_filter_(version_2).php b/vendor/phpoffice/phpspreadsheet/samples/Reader/12_Reading_a_workbook_in_chunks_using_a_configurable_read_filter_(version_2).php new file mode 100644 index 0000000..1f39ec4 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Reader/12_Reading_a_workbook_in_chunks_using_a_configurable_read_filter_(version_2).php @@ -0,0 +1,67 @@ +startRow = $startRow; + $this->endRow = $startRow + $chunkSize; + } + + public function readCell($column, $row, $worksheetName = '') + { + // Only read the heading row, and the rows that are configured in $this->_startRow and $this->_endRow + if (($row == 1) || ($row >= $this->startRow && $row < $this->endRow)) { + return true; + } + + return false; + } +} + +$helper->log('Loading file ' . pathinfo($inputFileName, PATHINFO_BASENAME) . ' using IOFactory with a defined reader type of ' . $inputFileType); +// Create a new Reader of the type defined in $inputFileType +$reader = IOFactory::createReader($inputFileType); + +// Define how many rows we want to read for each "chunk" +$chunkSize = 20; +// Create a new Instance of our Read Filter +$chunkFilter = new ChunkReadFilter(); + +// Tell the Reader that we want to use the Read Filter that we've Instantiated +$reader->setReadFilter($chunkFilter); + +// Loop to read our worksheet in "chunk size" blocks +for ($startRow = 2; $startRow <= 240; $startRow += $chunkSize) { + $helper->log('Loading WorkSheet using configurable filter for headings row 1 and for rows ' . $startRow . ' to ' . ($startRow + $chunkSize - 1)); + // Tell the Read Filter, the limits on which rows we want to read this iteration + $chunkFilter->setRows($startRow, $chunkSize); + // Load only the rows that match our filter from $inputFileName to a PhpSpreadsheet Object + $spreadsheet = $reader->load($inputFileName); + + // Do some processing here + + $sheetData = $spreadsheet->getActiveSheet()->toArray(null, true, true, true); + var_dump($sheetData); +} diff --git a/vendor/phpoffice/phpspreadsheet/samples/Reader/13_Simple_file_reader_for_multiple_CSV_files.php b/vendor/phpoffice/phpspreadsheet/samples/Reader/13_Simple_file_reader_for_multiple_CSV_files.php new file mode 100644 index 0000000..d4817e3 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Reader/13_Simple_file_reader_for_multiple_CSV_files.php @@ -0,0 +1,29 @@ +log('Loading file ' . pathinfo($inputFileName, PATHINFO_BASENAME) . ' into WorkSheet #1 using IOFactory with a defined reader type of ' . $inputFileType); +$spreadsheet = $reader->load($inputFileName); +$spreadsheet->getActiveSheet()->setTitle(pathinfo($inputFileName, PATHINFO_BASENAME)); +foreach ($inputFileNames as $sheet => $inputFileName) { + $helper->log('Loading file ' . pathinfo($inputFileName, PATHINFO_BASENAME) . ' into WorkSheet #' . ($sheet + 2) . ' using IOFactory with a defined reader type of ' . $inputFileType); + $reader->setSheetIndex($sheet + 1); + $reader->loadIntoExisting($inputFileName, $spreadsheet); + $spreadsheet->getActiveSheet()->setTitle(pathinfo($inputFileName, PATHINFO_BASENAME)); +} + +$helper->log($spreadsheet->getSheetCount() . ' worksheet' . (($spreadsheet->getSheetCount() == 1) ? '' : 's') . ' loaded'); +$loadedSheetNames = $spreadsheet->getSheetNames(); +foreach ($loadedSheetNames as $sheetIndex => $loadedSheetName) { + $helper->log('Worksheet #' . $sheetIndex . ' -> ' . $loadedSheetName . ''); + $spreadsheet->setActiveSheetIndexByName($loadedSheetName); + $sheetData = $spreadsheet->getActiveSheet()->toArray(null, true, true, true); + var_dump($sheetData); +} diff --git a/vendor/phpoffice/phpspreadsheet/samples/Reader/14_Reading_a_large_CSV_file_in_chunks_to_split_across_multiple_worksheets.php b/vendor/phpoffice/phpspreadsheet/samples/Reader/14_Reading_a_large_CSV_file_in_chunks_to_split_across_multiple_worksheets.php new file mode 100644 index 0000000..efe6858 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Reader/14_Reading_a_large_CSV_file_in_chunks_to_split_across_multiple_worksheets.php @@ -0,0 +1,86 @@ +startRow = $startRow; + $this->endRow = $startRow + $chunkSize; + } + + public function readCell($column, $row, $worksheetName = '') + { + // Only read the heading row, and the rows that are configured in $this->_startRow and $this->_endRow + if (($row == 1) || ($row >= $this->startRow && $row < $this->endRow)) { + return true; + } + + return false; + } +} + +$helper->log('Loading file ' . pathinfo($inputFileName, PATHINFO_BASENAME) . ' using IOFactory with a defined reader type of ' . $inputFileType); +// Create a new Reader of the type defined in $inputFileType +$reader = IOFactory::createReader($inputFileType); + +// Define how many rows we want to read for each "chunk" +$chunkSize = 100; +// Create a new Instance of our Read Filter +$chunkFilter = new ChunkReadFilter(); + +// Tell the Reader that we want to use the Read Filter that we've Instantiated +// and that we want to store it in contiguous rows/columns +$reader->setReadFilter($chunkFilter) + ->setContiguous(true); + +// Instantiate a new PhpSpreadsheet object manually +$spreadsheet = new Spreadsheet(); + +// Set a sheet index +$sheet = 0; +// Loop to read our worksheet in "chunk size" blocks +/** $startRow is set to 2 initially because we always read the headings in row #1 * */ +for ($startRow = 2; $startRow <= 240; $startRow += $chunkSize) { + $helper->log('Loading WorkSheet #' . ($sheet + 1) . ' using configurable filter for headings row 1 and for rows ' . $startRow . ' to ' . ($startRow + $chunkSize - 1)); + // Tell the Read Filter, the limits on which rows we want to read this iteration + $chunkFilter->setRows($startRow, $chunkSize); + + // Increment the worksheet index pointer for the Reader + $reader->setSheetIndex($sheet); + // Load only the rows that match our filter into a new worksheet in the PhpSpreadsheet Object + $reader->loadIntoExisting($inputFileName, $spreadsheet); + // Set the worksheet title (to reference the "sheet" of data that we've loaded) + // and increment the sheet index as well + $spreadsheet->getActiveSheet()->setTitle('Country Data #' . (++$sheet)); +} + +$helper->log($spreadsheet->getSheetCount() . ' worksheet' . (($spreadsheet->getSheetCount() == 1) ? '' : 's') . ' loaded'); +$loadedSheetNames = $spreadsheet->getSheetNames(); +foreach ($loadedSheetNames as $sheetIndex => $loadedSheetName) { + $helper->log('Worksheet #' . $sheetIndex . ' -> ' . $loadedSheetName . ''); + $spreadsheet->setActiveSheetIndexByName($loadedSheetName); + $sheetData = $spreadsheet->getActiveSheet()->toArray(null, false, false, true); + var_dump($sheetData); +} diff --git a/vendor/phpoffice/phpspreadsheet/samples/Reader/15_Simple_file_reader_for_tab_separated_value_file_using_the_Advanced_Value_Binder.php b/vendor/phpoffice/phpspreadsheet/samples/Reader/15_Simple_file_reader_for_tab_separated_value_file_using_the_Advanced_Value_Binder.php new file mode 100644 index 0000000..8213678 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Reader/15_Simple_file_reader_for_tab_separated_value_file_using_the_Advanced_Value_Binder.php @@ -0,0 +1,41 @@ +log('Loading file ' . pathinfo($inputFileName, PATHINFO_BASENAME) . ' into WorkSheet #1 using IOFactory with a defined reader type of ' . $inputFileType); +$reader->setDelimiter("\t"); +$spreadsheet = $reader->load($inputFileName); +$spreadsheet->getActiveSheet()->setTitle(pathinfo($inputFileName, PATHINFO_BASENAME)); + +$helper->log($spreadsheet->getSheetCount() . ' worksheet' . (($spreadsheet->getSheetCount() == 1) ? '' : 's') . ' loaded'); +$loadedSheetNames = $spreadsheet->getSheetNames(); +foreach ($loadedSheetNames as $sheetIndex => $loadedSheetName) { + $helper->log('Worksheet #' . $sheetIndex . ' -> ' . $loadedSheetName . ' (Formatted)'); + $spreadsheet->setActiveSheetIndexByName($loadedSheetName); + $sheetData = $spreadsheet->getActiveSheet()->toArray(null, true, true, true); + var_dump($sheetData); +} + +foreach ($loadedSheetNames as $sheetIndex => $loadedSheetName) { + $helper->log('Worksheet #' . $sheetIndex . ' -> ' . $loadedSheetName . ' (Unformatted)'); + $spreadsheet->setActiveSheetIndexByName($loadedSheetName); + $sheetData = $spreadsheet->getActiveSheet()->toArray(null, true, false, true); + var_dump($sheetData); +} + +foreach ($loadedSheetNames as $sheetIndex => $loadedSheetName) { + $helper->log('Worksheet #' . $sheetIndex . ' -> ' . $loadedSheetName . ' (Raw)'); + $spreadsheet->setActiveSheetIndexByName($loadedSheetName); + $sheetData = $spreadsheet->getActiveSheet()->toArray(null, false, false, true); + var_dump($sheetData); +} diff --git a/vendor/phpoffice/phpspreadsheet/samples/Reader/16_Handling_loader_exceptions_using_TryCatch.php b/vendor/phpoffice/phpspreadsheet/samples/Reader/16_Handling_loader_exceptions_using_TryCatch.php new file mode 100644 index 0000000..80bb371 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Reader/16_Handling_loader_exceptions_using_TryCatch.php @@ -0,0 +1,14 @@ +log('Loading file ' . pathinfo($inputFileName, PATHINFO_BASENAME) . ' using IOFactory to identify the format'); + +try { + $spreadsheet = IOFactory::load($inputFileName); +} catch (InvalidArgumentException $e) { + $helper->log('Error loading file "' . pathinfo($inputFileName, PATHINFO_BASENAME) . '": ' . $e->getMessage()); +} diff --git a/vendor/phpoffice/phpspreadsheet/samples/Reader/17_Simple_file_reader_loading_several_named_worksheets.php b/vendor/phpoffice/phpspreadsheet/samples/Reader/17_Simple_file_reader_loading_several_named_worksheets.php new file mode 100644 index 0000000..db30bff --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Reader/17_Simple_file_reader_loading_several_named_worksheets.php @@ -0,0 +1,20 @@ +log('Loading file ' . pathinfo($inputFileName, PATHINFO_BASENAME) . ' using IOFactory with a defined reader type of ' . $inputFileType); +$reader = IOFactory::createReader($inputFileType); + +// Read the list of Worksheet Names from the Workbook file +$helper->log('Read the list of Worksheets in the WorkBook'); +$worksheetNames = $reader->listWorksheetNames($inputFileName); + +$helper->log('There are ' . count($worksheetNames) . ' worksheet' . ((count($worksheetNames) == 1) ? '' : 's') . ' in the workbook'); +foreach ($worksheetNames as $worksheetName) { + $helper->log($worksheetName); +} diff --git a/vendor/phpoffice/phpspreadsheet/samples/Reader/18_Reading_list_of_worksheets_without_loading_entire_file.php b/vendor/phpoffice/phpspreadsheet/samples/Reader/18_Reading_list_of_worksheets_without_loading_entire_file.php new file mode 100644 index 0000000..bb58a2d --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Reader/18_Reading_list_of_worksheets_without_loading_entire_file.php @@ -0,0 +1,20 @@ +log('Loading file ' . pathinfo($inputFileName, PATHINFO_BASENAME) . ' information using IOFactory with a defined reader type of ' . $inputFileType); + +$reader = IOFactory::createReader($inputFileType); +$worksheetNames = $reader->listWorksheetNames($inputFileName); + +$helper->log('

    Worksheet Names

    '); +$helper->log('
      '); +foreach ($worksheetNames as $worksheetName) { + $helper->log('
    1. ' . $worksheetName . '
    2. '); +} +$helper->log('
    '); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Reader/19_Reading_worksheet_information_without_loading_entire_file.php b/vendor/phpoffice/phpspreadsheet/samples/Reader/19_Reading_worksheet_information_without_loading_entire_file.php new file mode 100644 index 0000000..5cdc498 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Reader/19_Reading_worksheet_information_without_loading_entire_file.php @@ -0,0 +1,23 @@ +log('Loading file ' . pathinfo($inputFileName, PATHINFO_BASENAME) . ' information using IOFactory with a defined reader type of ' . $inputFileType); + +$reader = IOFactory::createReader($inputFileType); +$worksheetData = $reader->listWorksheetInfo($inputFileName); + +$helper->log('

    Worksheet Information

    '); +$helper->log('
      '); +foreach ($worksheetData as $worksheet) { + $helper->log('
    1. ' . $worksheet['worksheetName']); + $helper->log('Rows: ' . $worksheet['totalRows'] . ' Columns: ' . $worksheet['totalColumns']); + $helper->log('Cell Range: A1:' . $worksheet['lastColumnLetter'] . $worksheet['totalRows']); + $helper->log('
    2. '); +} +$helper->log('
    '); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Reader/20_Reader_worksheet_hyperlink_image.php b/vendor/phpoffice/phpspreadsheet/samples/Reader/20_Reader_worksheet_hyperlink_image.php new file mode 100644 index 0000000..9dad4b6 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Reader/20_Reader_worksheet_hyperlink_image.php @@ -0,0 +1,54 @@ +log('Start'); + +$spreadsheet = new Spreadsheet(); + +$aSheet = $spreadsheet->getActiveSheet(); + +$gdImage = @imagecreatetruecolor(120, 20); +$textColor = imagecolorallocate($gdImage, 255, 255, 255); +imagestring($gdImage, 1, 5, 5, 'Created with PhpSpreadsheet', $textColor); + +$baseUrl = 'https://phpspreadsheet.readthedocs.io'; + +$drawing = new \PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing(); +$drawing->setName('In-Memory image 1'); +$drawing->setDescription('In-Memory image 1'); +$drawing->setCoordinates('A1'); +$drawing->setImageResource($gdImage); +$drawing->setRenderingFunction( + \PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing::RENDERING_JPEG +); +$drawing->setMimeType(\PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing::MIMETYPE_DEFAULT); +$drawing->setHeight(36); +$helper->log('Write image'); + +$hyperLink = new \PhpOffice\PhpSpreadsheet\Cell\Hyperlink($baseUrl, 'test image'); +$drawing->setHyperlink($hyperLink); +$helper->log('Write link: ' . $baseUrl); + +$drawing->setWorksheet($aSheet); + +$filename = tempnam(\PhpOffice\PhpSpreadsheet\Shared\File::sysGetTempDir(), 'phpspreadsheet-test'); + +$writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, $inputFileType); +$writer->save($filename); + +$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader($inputFileType); + +$reloadedSpreadsheet = $reader->load($filename); +unlink($filename); + +$helper->log('reloaded Spreadsheet'); + +foreach ($reloadedSpreadsheet->getActiveSheet()->getDrawingCollection() as $pDrawing) { + $helper->log('Read link: ' . $pDrawing->getHyperlink()->getUrl()); +} + +$helper->log('end'); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Reader/21_Reader_CSV_Long_Integers_with_String_Value_Binder.php b/vendor/phpoffice/phpspreadsheet/samples/Reader/21_Reader_CSV_Long_Integers_with_String_Value_Binder.php new file mode 100644 index 0000000..2c80de3 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Reader/21_Reader_CSV_Long_Integers_with_String_Value_Binder.php @@ -0,0 +1,27 @@ +log('Loading file ' . pathinfo($inputFileName, PATHINFO_BASENAME) . ' into WorkSheet #1 using IOFactory with a defined reader type of ' . $inputFileType); + +$spreadsheet = $reader->load($inputFileName); +$spreadsheet->getActiveSheet()->setTitle(pathinfo($inputFileName, PATHINFO_BASENAME)); + +$helper->log($spreadsheet->getSheetCount() . ' worksheet' . (($spreadsheet->getSheetCount() == 1) ? '' : 's') . ' loaded'); +$loadedSheetNames = $spreadsheet->getSheetNames(); +foreach ($loadedSheetNames as $sheetIndex => $loadedSheetName) { + $helper->log('Worksheet #' . $sheetIndex . ' -> ' . $loadedSheetName . ' (Formatted)'); + $spreadsheet->setActiveSheetIndexByName($loadedSheetName); + $sheetData = $spreadsheet->getActiveSheet()->toArray(null, true, true, true); + var_dump($sheetData); +} diff --git a/vendor/phpoffice/phpspreadsheet/samples/Reader/sampleData/example1.csv b/vendor/phpoffice/phpspreadsheet/samples/Reader/sampleData/example1.csv new file mode 100644 index 0000000..b8cdf18 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Reader/sampleData/example1.csv @@ -0,0 +1,4 @@ +First Name,Last Name,Nationality,Gender,Date of Birth,Time of Birth,Date/Time,PHP Coder,Sanity %Age +Mark,Baker,British,M,19-Dec-1960,01:30,=E2+F2,TRUE,32% +Toni,Baker,British,F,24-Nov-1950,20:00,=E3+F3,FALSE,95% +Rachel,Baker,British,F,7-Dec-1982,00:15,=E4+F4,FALSE,100% \ No newline at end of file diff --git a/vendor/phpoffice/phpspreadsheet/samples/Reader/sampleData/example1.tsv b/vendor/phpoffice/phpspreadsheet/samples/Reader/sampleData/example1.tsv new file mode 100644 index 0000000..29c9297 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Reader/sampleData/example1.tsv @@ -0,0 +1,4 @@ +First Name Last Name Nationality Gender Date of Birth Time of Birth Date/Time PHP Coder Sanity %Age +Mark Baker British M 19-Dec-1960 01:30 =E2+F2 TRUE 32% +Toni Baker British F 24-Nov-1950 20:00 =E3+F3 FALSE 95% +Rachel Baker British F 7-Dec-1982 00:15 =E4+F4 FALSE 100% \ No newline at end of file diff --git a/vendor/phpoffice/phpspreadsheet/samples/Reader/sampleData/example1.xls b/vendor/phpoffice/phpspreadsheet/samples/Reader/sampleData/example1.xls new file mode 100644 index 0000000000000000000000000000000000000000..bd9bb110b9a41b0aab816ba9e20682ee5bb43aea GIT binary patch literal 22528 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 literal 0 HcmV?d00001 diff --git a/vendor/phpoffice/phpspreadsheet/samples/Reader/sampleData/example2.csv b/vendor/phpoffice/phpspreadsheet/samples/Reader/sampleData/example2.csv new file mode 100644 index 0000000..1750f1b --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Reader/sampleData/example2.csv @@ -0,0 +1,223 @@ +"City","Country","Latitude","Longitude" +"Kabul","Afghanistan",34.528455,69.171703 +"Tirane","Albania",41.33,19.82 +"Algiers","Algeria",36.752887,3.042048 +"Pago Pago","American Samoa",-14.27933,-170.700897 +"Andorra la Vella","Andorra",42.507531,1.521816 +"Luanda","Angola",-8.838333,13.234444 +"Buenos Aires","Argentina",-34.608417,-58.373161 +"Yerevan","Armenia",40.183333,44.516667 +"Oranjestad","Aruba",12.52458,-70.026459 +"Canberra","Australia",-35.3075,149.124417 +"Vienna","Austria",48.208333,16.373056 +"Baku","Azerbaijan",40.379571,49.891233 +"Nassau","Bahamas",25.06,-77.345 +"Manama","Bahrain",26.216667,50.583333 +"Dhaka","Bangladesh",23.709921,90.407143 +"Bridgetown","Barbados",13.096111,-59.608333 +"Minsk","Belarus",53.9,27.566667 +"Brussels","Belgium",50.846281,4.354727 +"Belmopan","Belize",17.251389,-88.766944 +"Thimphu","Bhutan",27.466667,89.641667 +"La Paz","Bolivia",-16.49901,-68.146248 +"Sarajevo","Bosnia and Herzegovina",43.8476,18.3564 +"Gaborone","Botswana",-24.65411,25.908739 +"Brasilia","Brazil",-15.780148,-47.92917 +"Road Town","British Virgin Islands",18.433333,-64.616667 +"Bandar Seri Begawan","Brunei Darussalam",4.9431,114.9425 +"Sofia","Bulgaria",42.697626,23.322284 +"Ouagadougou","Burkina Faso",12.364637,-1.533864 +"Bujumbura","Burundi",-3.361378,29.359878 +"Phnom Penh","Cambodia",11.55,104.916667 +"Yaounde","Cameroon",3.866667,11.516667 +"Ottawa","Canada",45.423494,-75.697933 +"Praia","Cape Verde",14.920833,-23.508333 +"George Town","Cayman Islands",19.286932,-81.367439 +"Bangui","Central African Republic",4.361698,18.555975 +"N'Djamena","Chad",12.104797,15.044506 +"Santiago","Chile",-33.42536,-70.566466 +"Beijing","China",39.904667,116.408198 +"Bogota","Colombia",4.647302,-74.096268 +"Moroni","Comoros",-11.717216,43.247315 +"Brazzaville","Congo",-4.266667,15.283333 +"San Jose","Costa Rica",9.933333,-84.083333 +"Yamoussoukro","Cote d'Ivoire",6.816667,-5.283333 +"Zagreb","Croatia",45.814912,15.978515 +"Havana","Cuba",23.133333,-82.366667 +"Nicosia","Cyprus",35.166667,33.366667 +"Prague","Czech Republic",50.087811,14.42046 +"Kinshasa","Congo",-4.325,15.322222 +"Copenhagen","Denmark",55.676294,12.568116 +"Djibouti","Djibouti",11.588,43.145 +"Roseau","Dominica",15.301389,-61.388333 +"Santo Domingo","Dominican Republic",18.5,-69.983333 +"Dili","East Timor",-8.566667,125.566667 +"Quito","Ecuador",-0.229498,-78.524277 +"Cairo","Egypt",30.064742,31.249509 +"San Salvador","El Salvador",13.69,-89.190003 +"Malabo","Equatorial Guinea",3.75,8.783333 +"Asmara","Eritrea",15.33236,38.92617 +"Tallinn","Estonia",59.438862,24.754472 +"Addis Ababa","Ethiopia",9.022736,38.746799 +"Stanley","Falkland Islands",-51.700981,-57.84919 +"Torshavn","Faroe Islands",62.017707,-6.771879 +"Suva","Fiji",-18.1416,178.4419 +"Helsinki","Finland",60.169813,24.93824 +"Paris","France",48.856667,2.350987 +"Cayenne","French Guiana",4.9227,-52.3269 +"Papeete","French Polynesia",-17.535021,-149.569595 +"Libreville","Gabon",0.390841,9.453644 +"Banjul","Gambia",13.453056,-16.5775 +"T'bilisi","Georgia",41.716667,44.783333 +"Berlin","Germany",52.523405,13.4114 +"Accra","Ghana",5.555717,-0.196306 +"Athens","Greece",37.97918,23.716647 +"Nuuk","Greenland",64.18362,-51.721407 +"Basse-Terre","Guadeloupe",15.998503,-61.72202 +"Guatemala","Guatemala",14.641389,-90.513056 +"St. Peter Port","Guernsey",49.458858,-2.534752 +"Conakry","Guinea",9.537029,-13.67847 +"Bissau","Guinea-Bissau",11.866667,-15.6 +"Georgetown","Guyana",6.804611,-58.154831 +"Port-au-Prince","Haiti",18.539269,-72.336408 +"Tegucigalpa","Honduras",14.082054,-87.206285 +"Budapest","Hungary",47.498406,19.040758 +"Reykjavik","Iceland",64.135338,-21.89521 +"New Delhi","India",28.635308,77.22496 +"Jakarta","Indonesia",-6.211544,106.845172 +"Tehran","Iran",35.696216,51.422945 +"Baghdad","Iraq",33.3157,44.3922 +"Dublin","Ireland",53.344104,-6.267494 +"Jerusalem","Israel",31.7857,35.2007 +"Rome","Italy",41.895466,12.482324 +"Kingston","Jamaica",17.992731,-76.792009 +"St. Helier","Jersey",49.190278,-2.108611 +"Amman","Jordan",31.956578,35.945695 +"Astana","Kazakhstan",51.10,71.30 +"Nairobi","Kenya",-01.17,36.48 +"Tarawa","Kiribati",01.30,173.00 +"Seoul","South Korea",37.31,126.58 +"Kuwait City","Kuwait",29.30,48.00 +"Bishkek","Kyrgyzstan",42.54,74.46 +"Riga","Latvia",56.53,24.08 +"Beirut","Lebanon",33.53,35.31 +"Maseru","Lesotho",-29.18,27.30 +"Monrovia","Liberia",06.18,-10.47 +"Vaduz","Liechtenstein",47.08,09.31 +"Vilnius","Lithuania",54.38,25.19 +"Luxembourg","Luxembourg",49.37,06.09 +"Antananarivo","Madagascar",-18.55,47.31 +"Lilongwe","Malawi",-14.00,33.48 +"Kuala Lumpur","Malaysia",03.09,101.41 +"Male","Maldives",04.00,73.28 +"Bamako","Mali",12.34,-07.55 +"Valletta","Malta",35.54,14.31 +"Fort-de-France","Martinique",14.36,-61.02 +"Nouakchott","Mauritania",-20.10,57.30 +"Mamoudzou","Mayotte",-12.48,45.14 +"Mexico City","Mexico",19.20,-99.10 +"Palikir","Micronesia",06.55,158.09 +"Chisinau","Moldova",47.02,28.50 +"Maputo","Mozambique",-25.58,32.32 +"Yangon","Myanmar",16.45,96.20 +"Windhoek","Namibia",-22.35,17.04 +"Kathmandu","Nepal",27.45,85.20 +"Amsterdam","Netherlands",52.23,04.54 +"Willemstad","Netherlands Antilles",12.05,-69.00 +"Noumea","New Caledonia",-22.17,166.30 +"Wellington","New Zealand",-41.19,174.46 +"Managua","Nicaragua",12.06,-86.20 +"Niamey","Niger",13.27,02.06 +"Abuja","Nigeria",09.05,07.32 +"Kingston","Norfolk Island",-45.20,168.43 +"Saipan","Northern Mariana Islands",15.12,145.45 +"Oslo","Norway",59.55,10.45 +"Masqat","Oman",23.37,58.36 +"Islamabad","Pakistan",33.40,73.10 +"Koror","Palau",07.20,134.28 +"Panama City","Panama",09.00,-79.25 +"Port Moresby","Papua New Guinea",-09.24,147.08 +"Asuncion","Paraguay",-25.10,-57.30 +"Lima","Peru",-12.00,-77.00 +"Manila","Philippines",14.40,121.03 +"Warsaw","Poland",52.13,21.00 +"Lisbon","Portugal",38.42,-09.10 +"San Juan","Puerto Rico",18.28,-66.07 +"Doha","Qatar",25.15,51.35 +"Bucuresti","Romania",44.27,26.10 +"Moskva","Russian Federation",55.45,37.35 +"Kigali","Rawanda",-01.59,30.04 +"Basseterre","Saint Kitts and Nevis",17.17,-62.43 +"Castries","Saint Lucia",14.02,-60.58 +"Saint-Pierre","Saint Pierre and Miquelon",46.46,-56.12 +"Apia","Samoa",-13.50,-171.50 +"San Marino","San Marino",43.55,12.30 +"Sao Tome","Sao Tome and Principe",00.10,06.39 +"Riyadh","Saudi Arabia",24.41,46.42 +"Dakar","Senegal",14.34,-17.29 +"Freetown","Sierra Leone",08.30,-13.17 +"Bratislava","Slovakia",48.10,17.07 +"Ljubljana","Slovenia",46.04,14.33 +"Honiara","Solomon Islands",-09.27,159.57 +"Mogadishu","Somalia",02.02,45.25 +"Pretoria","South Africa",-25.44,28.12 +"Madrid","Spain",40.25,-03.45 +"Khartoum","Sudan",15.31,32.35 +"Paramaribo","Suriname",05.50,-55.10 +"Mbabane","Swaziland",-26.18,31.06 +"Stockholm","Sweden",59.20,18.03 +"Bern","Switzerland",46.57,07.28 +"Damascus","Syrian Arab Republic",33.30,36.18 +"Dushanbe","Tajikistan",38.33,68.48 +"Bangkok","Thailand",13.45,100.35 +"Lome","Togo",06.09,01.20 +"Nuku'alofa","Tonga",-21.10,-174.00 +"Tunis","Tunisia",36.50,10.11 +"Ankara","Turkey",39.57,32.54 +"Ashgabat","Turkmenistan",38.00,57.50 +"Funafuti","Tuvalu",-08.31,179.13 +"Kampala","Uganda",00.20,32.30 +"Kiev","Ukraine",50.30,30.28 +"Abu Dhabi","United Arab Emirates",24.28,54.22 +"London","United Kingdom",51.36,-00.05 +"Dodoma","Tanzania",-06.08,35.45 +"Washington DC","United States of America",39.91,-77.02 +"Montevideo","Uruguay",-34.50,-56.11 +"Tashkent","Uzbekistan",41.20,69.10 +"Port-Vila","Vanuatu",-17.45,168.18 +"Caracas","Venezuela",10.30,-66.55 +"Hanoi","Viet Nam",21.05,105.55 +"Belgrade","Yugoslavia",44.50,20.37 +"Lusaka","Zambia",-15.28,28.16 +"Harare","Zimbabwe",-17.43,31.02 +"St. John's","Antigua and Barbuda",17.08,-61.50 +"Porto Novo","Benin",06.30,02.47 +"Hamilton","Bermuda"","32.18,-64.48 +"Avarua","Cook Islands",-21.12,-159.46 +"St. George's","Grenada",12.04,-61.44 +"Agaa","Guam",13.28,144.45 +"Victoria","Hong Kong",22.16,114.13 +"Tokyo","Japan",35.40,139.45 +"Pyongyang","North Korea",39.00,125.47 +"Vientiane","Laos",17.59,102.38 +"Tripoli","Libya",32.54,013.11 +"Skopje","Macedonia",42.00,021.28 +"Majuro","Marshall Islands",07.05,171.08 +"Port Louis","Mauritius",-20.10,57.30 +"Monaco","Monaco",43.44,007.25 +"Ulan Bator","Mongolia",47.54,106.52 +"Plymouth","Montserrat",16.44,-62.14 +"Rabat","Morocco",34.02,-06.51 +"Alofi","Niue",-14.27,-178.05 +"Saint-Denis","Runion",-20.52,55.27 +"Victoria","Seychelles",-04.38,55.28 +"Singapore","Singapore",01.18,103.50 +"Colombo","Sri Lanka",06.55,79.52 +"Kingstown","St Vincent and the Grenadines",13.12,-61.14 +"Taipei","Taiwan",25.50,121.32 +"Port-of-Spain","Trinidad and Tobago",10.38,-61.31 +"Cockburn Harbour","Turks and Caicos Islands",21.30,-71.30 +"Charlotte Amalie","US Virgin Islands",18.22,-64.56 +"Vatican City","Vatican State",41.54,12.27 +"Layoune","Western Sahara",27.10,-13.11 +"San'a","Yemen",15.24,44.14 diff --git a/vendor/phpoffice/phpspreadsheet/samples/Reader/sampleData/example2.xls b/vendor/phpoffice/phpspreadsheet/samples/Reader/sampleData/example2.xls new file mode 100644 index 0000000000000000000000000000000000000000..dd213ab9428bc6fbdd8caf253528b92367e9a9e1 GIT binary patch literal 36864 zcmeI5378Z`w(p}@1Vl*|K?K1P6l4`~0|DucwY#CE8xTb$yDRA~y1KHhg{E=qcHcp8 z7ZCv!!39xKaiMS<_ify_!EwCfj9wj=@jBze{r^v#QY_N@X70Q1d*8jci!UoGPvtL9 z#EElGL}Uf<(u-ZT-*{KA4@}PMXw%yKxTAyFZ6|pMpC@L|I~#nzV+T*Ot5A6pyqfoWwnw%LYvV}6fguBkV>8&m2gYQqI?t(OS@zkd}- z9o!J>kb&ck!>5p00cj_*kJPNIymyoLJLS5E$y z)3i0+jOn+gU;m-~`;QnicUbmg-!^>^zdd*Q?)VvAg%Tab~DNx9+S670A(LB|obhKmKQhhnXSf;GGm^Y1N-aMeWbrcC(kb#9w!W zrL=FEFy(X>k$;{2>OYpV8-uR@H97AZbp8KidB}v>JbhVvwnfj09lp(#5Be4P&|i@c z{uOzLKsm=isZaZ#^)7GK#&m9Ky@O?!rro6M+SEqMZcXi^G)+dzy_(wYm?wGW9Mjnh z>CL;eH{K$*u0H0Cyx6qC-G`cOq4qcsX^(`=_hxgq3_jn7qm40z(NtqQ6n;9MN;IMt zC2lI3N=Lj7_~^&0<#StOirva|tfMjc^Q&vzcr=-Ex3)oAgprZ;c~amcQHcb$0L3s;f7;wc$OE7$vvzH3QE#$JmTU* ze!SX`xqBE>koMw!GMpbxcuCwkKT++)Q_(oFIbOnB=*Fo!QRksL7+I#Hf(txUH-bjw zC(@NJ+F0nuD?R8WpH8L{ZY)Y2v!Y&HZY08}3-e%1O5-m1i@ijp8(kocp5`W#ZW>w& z+#0veO@b_S*{OLs6;_c+V)C`3u7-P z>BpiAqik`xn{XF+3;iAxCSjT|>>8fzB^GcfL8NM3{SD+QVT_T!_CTqg8qKWEgJUk^C!<~~DHPG;gn+TVqyTb)uwTt_8 z;pOSL7Y!G&nMpV1)}i&~{(PxqK{{6L!eiiO)6;G>Y?H3`)6(aOTGTN-(M|eLUyxpq zuB%KZ#NcV%FcO7r%WC3&UAWAP*PzygZe67x5r>@P!nY%A6@*^G_vyIPQz=vkAK(gZ z#6=~_&~NOoLbu+7BPJpqsxis);a}2JxNf1_SO>?|Ceq^Q+jJD8U?F@EUKGxsZ+&}) zSD&toMXONRX#*sK9*byz*20=-jJ;UkMHfWl)m9Q`#R~jtKPArU z$NaiVanMrsPZXCI`gQo2L=D*|i`|9MSj;1gi1n?8@KitPu{}PVKRg2=2Sd(5%)#z{ zx;EkO$?sBLI5Kd`LLbotoyWP=39pj(Pxwd!tj1&)5hZm;=-``a(JDVF-xN01qYrTj zELWZOXrIMiRZTeCc2uJnZe8Oh#jJikdd5Yt;wD92ybd!yTv4g%Q{ z^XsB<)E5<`rTnl6s{O7%e--~p3#?iM_e4V{xXDzwA_~L65XYpWDIc09RHflYloYzr z1T8+Hy0Jb5Q{dL&ayPb+U&D?QVn3asE~W6yN+0ez;pDWN^5K)Q@T7D!?$IRqNmQP; zm;l#Lc>Gl1#$wSpn>HbtLKo46^COWc@>HcODQ7~eChFIVLzW{-V_qXt-$XZ7%W%oM z6dGUQCt$LLaWsCSoAABtS7>K>dLcVvB0L2bPDXNw#%t*f6Qgn71&u31_^?YRBI{Mr zui&!CX&&#E@Z#tlv;zH!Z_423Udm%F>^Eh8tTFDPZ(+-lXeCmqIu-K*V+P*7ARWVp zNeC_p*ouLbuziwskVp}~DDV=ng@_Uemqt`8zp5(17@LHlLoAr8@#3uKq=e^Hd1%zM zbh;M7!gFa0JsWA(8&-ilO*fu|e)M8~x}F0QPEuYS`Up-}o*IszPhsUy<|k4x%%rrJ zh$lTes>R*fL?abtJFg(hVju8LN>v3kbnWIrB3K9a=B^VbXZYb=OXA}30f51g;5+N?gCo^ z$*)6aQs5^d@*$sd0A640E_Q2cc!t|dW0+O4Ma5pck)2(EU}>O{ild2WCDIDMEBAbv zO_U?{Yr@4oq6juEPB*~1VNOZF*)uj4!%MAKOHGZ5>c+*?#HPUdxD4|Z3~~5?6q-oW z>?K~M8yAl)b(1iHh?0J)hMBR{k0&tT(bRBq&QDOCS#Bh~7(P)FMFLB~HB+7#b5=AK zkES`zD2b+OFcq=EkzV9sbV(>Jv$nY)c77FVwzhQMM6QfirrKV+$@qt6Q_CgE+-rLb?6TGTpcPn%|qr$ z$P5Funau=+Z`}wMP&<7UMyK#A+7KqF^Ei|+*a~4{1eNsSY|J&`{cwJ{Z$tPv4}B4j zh(Y3Rb(;0TXb11bXWl7_VVsdLEn4kyM9HsAFQ6aEd6c~|%}>ntW3`&I!IZmEOxG|$ zz&EUUJd7tDOu##(_36o&FBMHRxb%BC?a6Kmy*?e5Kpi;UsBgwe{}TA6x7JAy{W zHFS=fMia_#*E%=bclHea#sn6|yunLWa>PN_PrG5-!sb8RGe4P*S4Dl!NHKh{v5in( z60M_CmLYzmPVs1rAzFs{O0>QnBO#4D+f5|h27JKS0n_0_Ni>PUg13U+G}0qJa-^M# z;31COi~$|95xRAeUqfF$#!a~iG_)XHl}0^N98Qp@T=WxF`^nmcF2?>Dn7F`n;fY?v zOCXI%gcmaf3&8nhgy~4e4R}n#>n&CywfnMF-sR>vXF>+oI#;I*t6@`r~gWLs$n_{T!V zV|ffASSxWWKA{N#mOaQT!B(6zL}VgyYuZVw*r&67;RjP zB|1VEH<(#X&qW1_quxS%GqaXUNLmLOCX97tWfTpa8IPvCh;8VEI;^`>5`tKxBcmbr z==U7aBR)-71ANCcSV`A<~jUN}w?@b@C|1MCWstD5AzBPiBd`XG(=|(6U%W37t;docIOZ-gxhk*q>le^f zO5G|uw4v&y?t*l}N47^gLKwti+0jPr5H9i4QFcnH%P9e7IE4JTTSfn$i69FXxTriV z1Sy`o*i;Z}#GE0;#~Abt%&g#psLBlbJS@S*X;l^5mXF|xqSn)*Y0fHaO2wK_B51~+ z5JEn)L%G*jRfADjp5v6mE!}#IQ}h~H@p7eKj%NoYE+P$|b*|bF@54?A&*Fp}i5syK z#?TtJ9ianL1;$;Ei%NXJxB$-=|Mce%lWYYqsX#J{VhSyMgDd)AQN%mBN z4kj*fw>RPm1sBk>G5T?mQ10T+j1-J116j`TkWF@&Eur>iHBNC;^A*YyP?n>W52L&T zWlucQ`wpdtaykt80Lt4@?v2O41E6Xt%Ck{^hVpkPr{EWvF)-~-D3_xAJIaqxPDkhE zp@-I^ybfhs==vVze3ZwdN1s4>Kgxd4)ee>63S>6QZ7AN?R@~{^(fmx=Z`3BP)@_>bU(^FQSJks{1J&$&l6ETh4ML+M?m)wSo;c; zjVM1v`7+8e(0w4L3QJHnqx=Nr-%wUT=R9=!Gbp#A90FZ?Bd0c_Y()7X%BN8t3Z1<% zYd8nxsVM&i<)2YjKxaOlb!|X-1IiB2^&Lv~`1HNe4wM^Ec89JXQP!Y57QXW+$}dnJ z51m!*yW>Dd=?k}&S1Yv1*q_Qe|Kg{zj;)63CFm@C`rrUm>|kR?ABBb$!2wE9iCM<9 zo(Bi0F=kKs(PB7iM9BGb3 zc__-crr0zZ-&7e7KYQ^NYSTLOF8a6ijlE}tT*>R#z7Jyl85!c)jKGTwKl7D6=jW0z63j4u;;)4N=hr`bJ?}^xshyf4 z@Pu7o)=GTO+RK)$q)t1_cV&Mm3IF1>X*^Fu{4EkM88;2uu zZW0pf|8sZ!pL{jVLwj`|Xzhh@=cgCt;b~U>_)k*IV33jHW(cvU82~t5YFg-_N*1o=y~?6HO}aBJmWPEG-S_0IHRAn zhLSYe+&hmkM?obc~{1T<+%=Uup;0Rmz(m)hYsABhxcz+ z=Cu}$)QnhAqO}n8MBcx(5Sym{>6#Pg6^KSgo@1W>Xt%;Xv)YSB*42>~Q$IJ%IEEfd zp7VIe%)a8Z*=?2#%~%Qf_19grwQiP|A;H!;k^}4+d3r9dZYzA`xa*$$CU1i1Z!7x0 zTlDdR$Itn8JPEed!PZ0D;@w0)J(@i8iDParTl?MU+xHj!y9vMg)clSe77dpA(>e}T zd6sV{JUyL!JK;;#Z@zAOX}<97gfCn+=i$c=E|vPzat^F(`S!vy0?4-){=79CQVn|- zY z>+j#PzSm^Y-BI{!m+yT|(@7(QXV!7BHPiBY2+!yu&y}=eCT}Z$>WUGz?RyCSW~Ygt zcKdR&@a$*DF!rc_PvIGL^j5CN;is3c$wLc{cqC)JmHq60Nh(arK4QykM@`Od=6UDkTx;fD+szO(S3b?Exed(T^Mp=UYf0Oh*~e<04-TYTPt z8g;r?*e=3;asRp=T`n6Vy6Iz%8K7)eVKG@XY&)adF<;X8b{uxpp7wH>|MF?=e+yD=X+*bHpz&wS{Zrg@k4-Fn7|3|qvU=$Ne& zmxbqcu%6Db!NKzo%l8l-4|EK#?jii5jt3lc`lmKi^br2U;-}h;JAS0JpHbm(E3D|> zTX@dX$?q-vlJB==lDCW!{d)_4!8ND#UGwp1;Tb0mp2b+cr|^d=-&6R?z1l4CmmDm7 zPvOgs_-OafznPR_iy2vtIZXL|gg;z)Zk%?EGimmpK8ugcV~zF^zTSOrQNzG8;TdNR zo()<3y@Wqf`Ch^w9A6W9=!tH5^rl|I@1J~9>dvTixI5RdCcT9nt!!^$@yG=f}%z5cMf*shQ8Ph9+$Z^CoOiGGf|jyW3d7NU^b2qi~0jIClOsR?f0gd$#NMd1yE1pJC|CojWl1Nc8S_J~j2R$T#ypTKW2EQG81K0ah)q;1n) zIi>*b_KY!Dh%?4PSmNN$-45FG@XZq?4#E-#1Cm2OU;pWh3<>(GgLts~{t`zMl;21*?e@%&Z>nHpq<@*W0+pq)XrKTJzd_Uo@>GFQx zvu9Tb<dr0`KJe3qM8q{=ygc`NLo5CAtXTU--u&>*rNmYV$wi#W7QrKS20m;dgQ6po^F(t|$D152%2MRy`tdfz5&3PGG^g!Xi=(^(X zd28+X#W-}#H01{fKVA6&!mq4+E&SY9g;M_k!f)x?{OA38ogn%dvyLfKexUHjC_hm6 zwHN+==Id8>6@H-bcm63qf6%$M{}|tnnW6ka!j~(5knrEnZ2J5Nf1uR=AmKMxU)g5e z4HHB^bAe+jlpiGgOyvg&zigvFuuGRN!VePum~9`l?Q)s5AM=J|W+^{d_}R+yHxC)9pkKC|u24DHVxgZZ8ELxi8BJb#UG%o883KCY;!x9~%Rzwyk+ zI`-?=U3lgr#~i2pP~nePeyH&KESb?^`p4G)hYH^?wcRH>zCKvm&)ntUml*4R!-PLU z`C-C~xA(Ji$6>+E)czFd zyAi@J<^aCd_8s%AgI}L)+Yb>of-`#hA;K;jSMdDO*NesWhX_A>!^VBK_ZuX-nUftn zBencU;dw@Wr0~mowwcvw*g%=Pj1<24?Tx}RcRP5>YS}}DtycC>VV7>bXWjH`hl}n* zg5dLJ(AhbJF1tY5$VGgiVm>)7GzClp0alDc#3aPU{utnMR(uT%aA;k#@Y|LBN$ z&u=Avgy{dEpzpRzZY-C&bDVMTWZCjZ3hyg_r0|WmpZ(dg&PPkzj}-o~PF1h`Zv1%B z&oRltFN~HSCH%=aW7|gwABtQd>?mQoKRvtEpb|UZ=h)@QuZ^_gXknAejuzG!J7*d# ztc~^kMK8xV2hYQ;-lK#~6X_{)Y?|5)K0l^-konBOnmb8W|d!jBbx z>l>Z-z3L>ZpX01!7Arqa_*0Z0C;V>CZy$N!ttlC{Xq@nubZl3!>C=)7t;I3fF{dhj zwD6}Xf3&Ut*L#gFYBgQ-^RM*6(%WB4RQKpDJjZXxoUZ(M;m=Thyzt%oJpIx514fGe z@xo7PjrGs8`7&pX?an!Cp81deaK@TFc;#3*%QBp^=0WGq^9aiF17rmOGR`e>SCg$|eTLI9JHMjag5KEaTL#$aV*c3akS2ram>z@ zaa7Kgacs_&arDiVF^A{MILhYAIM(LMIGX0lIHu;xIBMp~ICkdBI6CIaI7a5mI11*< zI2P_KV;to{hUWr!w`ZKQHsg#@muEQUKX%{7_(R>NNgU-#99;Tb|1(DX#pVI}s$-_Aco;(mEm9=>>OC{N6@A=K>d)A6%!SHN5dI?NCkTJyS&w}E z%^wHLxI01k&>yc6-HbuUT&(OwVJ}g3qOi9uIq#7N-?2~kCJI|ozbL;bVxMd>ULA9( z@{@#LqWmP`_ju~W&wnhKDz=*>e9r;pZP&bFpUg709qb*keLq?F%aori{P6PS+xHyZ zL-@(Ux7{by{nGH$^2~vGz%k2|pCbHn<);Y$c4GJwGk+)$ev0rzh6DPlk77f1FZ(07Z4ze@RH;p_k0`spz{ z4i>&x_yy-}9Z>tOowqO-Ip%7-lP?jTXYA<`;g4>2W&g-zyWT1hzH|O-AHTNBj^E5% zj=4tpQsJ*vzEt?O+dh2f!7bMJOND>(u~Rw?ozzC^&m8BN>y)1+{0il#3IBSibHiV| z+WGo4;ZIz>xOU$0Lxg8Objk2L zQRmg(dtP_DR$@MO%o^oq2)|bO8NzpNeZqIG1`n0Kn<4xcgUpkMK5y45%-xQ;N%?Z& zZ&tos`1PHaJpSy~Ny3*4|Ir>#^znB;G>^T_JnxuWl&=teo$?jJ|M0iX^KNZ#eXm0J zJe{Rx#;Kk;g}7| z&l3JNU8vOF2{A}UxP=2=XZ#DM5uFqR` ztutHrcbi^x`fs++={YVr=1%306@H`g#|nS)Z5O>$vo25Sf2{D^AL+E>(2+Lxa13+I zUCPf9ev|TZgztOAW!H4P&E}pt!sj{&?XxD}TK3?PurRxbjPzJC7Isy#enI`Rbd4gy%Tw znERBUEByV+&lP^t0}EF6|M3vfKUeq{C!alW<4C)1;F#-}2b4cS_$|txAiO;5zuLzB z3BtGk@RpPP_l>ll<1>CaQT{~XA5#8A;Wu~PQvCa`ZTn9Y{-x8`p1;T6Z2LKuJF>Bs z-h7hqk3hy>cDRcYzjJ(ZcOKr~elV}K;ZxQHyZ>^=y6e+aJ6Fxaxc_T^I6IvOEj!O5 zD02g3l>xG<09hnJ<^{;+2gs@eWHkY@Xn<@%fUGt^77LKo1<2w7GCx38A0Rt9K$ZxQ zB?Dxs09iUf#WTynkP7RQ8rIuTl(*tB@9WHMEA|m5qfF05{wzgJgdZfA)ZrWz9mMkCvoKlB^XiIf1pIQ5HBcE zZHeb@&VZ=6NeRZAV_sCEMu?Y`sIf%ZS=2FiX~wpk(dd|$m52)QiV{&vL^hG=d5P*^ ztUBhmN-PlKRV5Z!;^=!x+_6RpMzUjGQ=(P~p3zHcEipy%WBap|V4OSVbtPg#yrD$Q z65p@LK*uSYlwcNc%$rKo3GtQ^bwXg*EQy2GE5RJ$n75UP3-OK;ajT;&nTHkF3y&$m z4C0t~mGFglPYGXjbOsSx^^g+GFOGR%iFzS^r$oKgF?l(OtIx~W{$TcT%yuPC7UBaX zPPWA38%WH(QgtvlIr0x=F}@N)e5gdi67^S-c>O-r!OZ2DKj582QizX~NLr%ih75?x z&8mZW%`yL?L`sN1Dv`28({d84?^1$U&oLh>krv`lN~A4eR_1{?{xPi;bE0GZti(bg zK2c(!B^I3xqIvrZs)HHR!G34kjs_wARfz^moJij_O_=#fTbWNC^O+Khg!o*EMV9z} zO$N2fyHyEhTgUuGiAEv5P@>Ti_gn)Vp=Tadg1Oi+Un;R!h_94bY>9<8Qpc)mm0+fJ z%-2esBE(;nIK>i)o2X-LMhWI|$9$v2sY3jl5~o_C`W_PVPE~?g-Z9@Qahec+Q{psB zyt)j8$vY-vZN)LbF@IO$bRoV|;&e;QU^|+}y`(xgLOA$GgKU4CA;b?#oMDN*HFtGVM7J8i}Xk|M@?h^E5$A?ikh_AUiWac2;Tz00kU%gWakCQ&JU1X5FooS zKz31p?BW2~B>}Qa17u4AWJ?2Nmj%d{1;~~M$Sx0%T@fI=GC+1!fb8l3*);*OYXfB0 z1;|ze$gU5NtqhRekRxLbZGy}(KjPh7)EhWwr0)H7wh%+rQ`C<~=&sBn+{qwr&EFsz`ah4_4-Ic*? zERl_1`aSkws*baT*iDJEEwSn<=xDBaPIWLUue#U z$8=NTA|dut;v!3|kkO}dv+7`+JEpr57YortiHo&X2SJC)%hrln0DB^pxI_s4RZsfW zC00ihX9LY^exogCj&RIAN?a;LFC{Lu#0eZrj5%D1B|`L8Vu=v^+i}#f#1d`p01>Lc zSlhw;f~PP_EES@!5=$+ybOnhQA5enX2fH1WxJ-z!5|>%x<~1aod$d-}P1xV4#4;iJ zDY48F#dnd2tW|=U3p*N>ST4i?N-Vd;tum7xe!CLPYmPZkiOYo;pv2{t*j|-^jzza9 z!K~+)fl6E<#6e12VTo6-%>z+&f5!S1bE0DgDRHF`gO#|_5+7edVtsZ#$Bc>HiAr20 z1pkH@`{OE043Iher}wH3=2PrPRN`tOhAVNk5cvB)8T5I*rv$St_G2q?jSxJej%%!r z)?(dJ%eCdq#n^$U#I-^kqQte9FbCwJ9p7eW{mj(ZbEw31LL92Zb(ZKVecmrS>t`Ov zE<+_&2ywU)D=bkXbKf@CX*-zZv9C~x>xDQ{iR&%#mkU^{%P&`gV*qv&DzQ?C(Mqhe z#4}>u+p_aHju6;CsKgCIj8WnSOMGuVk#qR}%6ZT#XwIDn-54NS9Uxm1AX^(CyD30+ zbAarY0NJ_#*{uPx^#QUC0kYcyWVZ*%?g)_G86evjAiFC-wkbe%cYy4k0NK3(vdsaq z`vPS52gn`>kZlQ&Js2Q+C_wgbfb5X~+14BxXFjVS!=4cJ{8i%l@rG>~K|LwGjDAthU5<8AZR%J{6&7 zV=tf*YlJ9NVvP_cTm&7VrD=_M`aO34DX~_F2}-QBIzHt*C^RvB}2{B2D zo2(AggT%Sn9L$)(4nHMs7GjDLH(MR4wHXja>$K&JDD3G|;uax_mAJ(cVVP?UU7-Zy z4ZHZ1SSLiO66-8+h~%zJcFxFX#J)WxZWUs>61Q5Sjm))*vQP9FtJtZh#Cjo)QDVI% zzLe*4nJro?Ml$y1DX~F_awRsX4vuim*JPi(FwU_XPl?-vn5o2VmcX+k*vg!!I+z8p z|4xb9g_y0x?Up!oRR(^Q$&PT$5!i93#2rG+QQ{6EOkM$XT)jkfFoR%^of3Bnal8_D zS{;9uc~C6tCCo3_Rj0&8Ax=||47vk*}wHfuZ1%7ECE zeO}6piT!Iz+$Th>68BkR_?^_z;}O-te2N`wO586*of7v8Ve;~+gHs5GO0K#S%r=K}T~ey9#5b#ttbZ9uy*}#Dl71X&!2| zBRgAW9>)$eB_0wYt;9o?NUQ?UeEs9vAI$RDbEd?@LNqAxun;D1B6YlagAyDAu**z| zM}%lp;t{K3`&Ahb=4K^0LSSE+5?h5hMTxDJ7|WK2#xleGSI&bn(40FD+7=*tG(h%P zfb8)A*%JY>Cj(@^36MP%AbUDM_Dq26*#Oyd0kY=cIc=>7SN@XvP_Lwmxc!5f5Zo8+tGH zfa#Tw$(3j6y&tnzHgVhw9r&WwhQ5tmUrIbK#5qblZi$!V*_FwzQ0e*D=cU9GLY$|> z6PC#E*;Vrq*(V>23+(Jt;z=PcP~u5Tl-)>T>XX`HMh*6IDe)U2E>hw*mRQH%L7K-t zs{~^TJGhj1N{CC8cuEM<#Id!x`6(qBaoDq^#M447QQ~Q<L+O1vNh&$ya@!4ltc6f>re){0pI`>T|AQHX1m zc+nEeWG4ASc7$OL!Hy~=UJ_!35-(X|qx^C*G3!^%FxW$-#LGggRN`ezTrP9ZW6#dm zNMioM&M75c5n`1RuUMkw$_!>lRWB>S?1a5iO8i!c)k^%<673~EuFB3_n7e-V-B*QJ zqr|J0n0E?vgs#pK%xsQXt2$m2f@ieuYnEs_ArIEwK1Z#~yoViBO1v(_%}Ts(3Hj|S z)Oo8C%!=4ErNkRTtW)9*)xj7H{XYAYh&dCxq?CA5i1kXmDTHYnO=45_$sjW-_C+c2 zmJqio@s`ywO6DP7Wpyy$VkeXmZwqmU5^q~#E$1QF38VeNY>d56O1vY)MkU^{#3Fez zI4V2WVXnq*Cneq$Vv`c@TH+{~fep<*iDD+l{w5{f6XG5v-m}C|`F-uw>@y$cdF*IX z;(Z}DEAhT1UXu0S(RZOg-oW28<3E6Sto)KS_&@nu*3qrHn$5dGvB~3`zWSgZt?g&% z=WEC3hF2YlV|Skak9hrvUrXACTJ6#nwt4$GH+=cG={217HF|Riv?ON&|-lW3$$3E#R4rBXt6+x1zIf7VuAk(3;ajx|5mR(|JsV- zy*n?z3hV!&f4hV0{f#)d=I8odP> z7HF|Riv?ON&|-lW3$$3E#R4rBXt6+x1zIf7Vgb9R<`SCgaQsX!zwL9ELpvN?=X1Ty z-40yebM4M`d?y^-qQtd+7aU!2aJ|15j_x?P=I3*Po;dcw(F;d!9Q)$vgM-WU{cwbF z?2n@#j{Z0fz;Pgs0XPQYI0(le9D{KT!7&uaFdV~i9E@WGjze&a#BnH&!*KkF|Mcq! zl-#8;3dd+1N8uQQV=NASe>BSRIP!4h<0!yUh@%JxzyART%l{3p8TikoeEbudVKYJg zYpDeO1Hgag)dRanvUZ{$h1>HTdoJZ&{vBrf3Gel$&uM#{{A`G}%?f8jdjkI$MWxiE zHY@&z%dYr~xmo=z|55O$sJ*hSDK}|Thd(ar(P*Z~f3?LrQNsUfi~sr7ufGn0{t&)q m{QRT(59jR|Jb1N**YQ7GWF72(?)A^rXVib9{o|-u_5VA)ah=`( literal 0 HcmV?d00001 diff --git a/vendor/phpoffice/phpspreadsheet/samples/Reader/sampleData/longIntegers.csv b/vendor/phpoffice/phpspreadsheet/samples/Reader/sampleData/longIntegers.csv new file mode 100644 index 0000000..166f4a8 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Reader/sampleData/longIntegers.csv @@ -0,0 +1,6 @@ +"Column 1","Column 2" +123456789012345678901234,234567890123456789012345 +345678901234567890123456,456789012345678901234567 +567890123456789012345678,678901234567890123456789 +789012345678901234567890,890123456789012345678901 +901234567890123456789012,012345678901234567890123 diff --git a/vendor/phpoffice/phpspreadsheet/samples/Reading_workbook_data/Custom_properties.php b/vendor/phpoffice/phpspreadsheet/samples/Reading_workbook_data/Custom_properties.php new file mode 100644 index 0000000..1c222b5 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Reading_workbook_data/Custom_properties.php @@ -0,0 +1,53 @@ +load($inputFileName); + +// Read an array list of any custom properties for this document +$customPropertyList = $spreadsheet->getProperties()->getCustomProperties(); + +// Loop through the list of custom properties +foreach ($customPropertyList as $customPropertyName) { + $helper->log('' . $customPropertyName . ': '); + // Retrieve the property value + $propertyValue = $spreadsheet->getProperties()->getCustomPropertyValue($customPropertyName); + // Retrieve the property type + $propertyType = $spreadsheet->getProperties()->getCustomPropertyType($customPropertyName); + + // Manipulate properties as appropriate for display purposes + switch ($propertyType) { + case 'i': // integer + $propertyType = 'integer number'; + + break; + case 'f': // float + $propertyType = 'floating point number'; + + break; + case 's': // string + $propertyType = 'string'; + + break; + case 'd': // date + $propertyValue = date('l, d<\s\up>S F Y g:i A', $propertyValue); + $propertyType = 'date'; + + break; + case 'b': // boolean + $propertyValue = ($propertyValue) ? 'TRUE' : 'FALSE'; + $propertyType = 'boolean'; + + break; + } + + $helper->log($propertyValue . ' (' . $propertyType . ')'); +} diff --git a/vendor/phpoffice/phpspreadsheet/samples/Reading_workbook_data/Custom_property_names.php b/vendor/phpoffice/phpspreadsheet/samples/Reading_workbook_data/Custom_property_names.php new file mode 100644 index 0000000..0f287f0 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Reading_workbook_data/Custom_property_names.php @@ -0,0 +1,20 @@ +load($inputFileName); + +// Read an array list of any custom properties for this document +$customPropertyList = $spreadsheet->getProperties()->getCustomProperties(); + +foreach ($customPropertyList as $customPropertyName) { + $helper->log($customPropertyName); +} diff --git a/vendor/phpoffice/phpspreadsheet/samples/Reading_workbook_data/Properties.php b/vendor/phpoffice/phpspreadsheet/samples/Reading_workbook_data/Properties.php new file mode 100644 index 0000000..5bf25b8 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Reading_workbook_data/Properties.php @@ -0,0 +1,64 @@ +load($inputFileName); + +// Read the document's creator property +$creator = $spreadsheet->getProperties()->getCreator(); +$helper->log('Document Creator: ' . $creator); + +// Read the Date when the workbook was created (as a PHP timestamp value) +$creationDatestamp = $spreadsheet->getProperties()->getCreated(); +// Format the date and time using the standard PHP date() function +$creationDate = date('l, d<\s\up>S F Y', $creationDatestamp); +$creationTime = date('g:i A', $creationDatestamp); +$helper->log('Created On: ' . $creationDate . ' at ' . $creationTime); + +// Read the name of the last person to modify this workbook +$modifiedBy = $spreadsheet->getProperties()->getLastModifiedBy(); +$helper->log('Last Modified By: ' . $modifiedBy); + +// Read the Date when the workbook was last modified (as a PHP timestamp value) +$modifiedDatestamp = $spreadsheet->getProperties()->getModified(); +// Format the date and time using the standard PHP date() function +$modifiedDate = date('l, d<\s\up>S F Y', $modifiedDatestamp); +$modifiedTime = date('g:i A', $modifiedDatestamp); +$helper->log('Last Modified On: ' . $modifiedDate . ' at ' . $modifiedTime); + +// Read the workbook title property +$workbookTitle = $spreadsheet->getProperties()->getTitle(); +$helper->log('Title: ' . $workbookTitle); + +// Read the workbook description property +$description = $spreadsheet->getProperties()->getDescription(); +$helper->log('Description: ' . $description); + +// Read the workbook subject property +$subject = $spreadsheet->getProperties()->getSubject(); +$helper->log('Subject: ' . $subject); + +// Read the workbook keywords property +$keywords = $spreadsheet->getProperties()->getKeywords(); +$helper->log('Keywords: ' . $keywords); + +// Read the workbook category property +$category = $spreadsheet->getProperties()->getCategory(); +$helper->log('Category: ' . $category); + +// Read the workbook company property +$company = $spreadsheet->getProperties()->getCompany(); +$helper->log('Company: ' . $company); + +// Read the workbook manager property +$manager = $spreadsheet->getProperties()->getManager(); +$helper->log('Manager: ' . $manager); +$s = new \PhpOffice\PhpSpreadsheet\Helper\Sample(); diff --git a/vendor/phpoffice/phpspreadsheet/samples/Reading_workbook_data/Worksheet_count_and_names.php b/vendor/phpoffice/phpspreadsheet/samples/Reading_workbook_data/Worksheet_count_and_names.php new file mode 100644 index 0000000..630312b --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/Reading_workbook_data/Worksheet_count_and_names.php @@ -0,0 +1,24 @@ +load($inputFileName); + +// Use the PhpSpreadsheet object's getSheetCount() method to get a count of the number of WorkSheets in the WorkBook +$sheetCount = $spreadsheet->getSheetCount(); +$helper->log('There ' . (($sheetCount == 1) ? 'is' : 'are') . ' ' . $sheetCount . ' WorkSheet' . (($sheetCount == 1) ? '' : 's') . ' in the WorkBook'); + +$helper->log('Reading the names of Worksheets in the WorkBook'); +// Use the PhpSpreadsheet object's getSheetNames() method to get an array listing the names/titles of the WorkSheets in the WorkBook +$sheetNames = $spreadsheet->getSheetNames(); +foreach ($sheetNames as $sheetIndex => $sheetName) { + $helper->log('WorkSheet #' . $sheetIndex . ' is named "' . $sheetName . '"'); +} diff --git a/vendor/phpoffice/phpspreadsheet/samples/Reading_workbook_data/sampleData/example1.xls b/vendor/phpoffice/phpspreadsheet/samples/Reading_workbook_data/sampleData/example1.xls new file mode 100644 index 0000000000000000000000000000000000000000..0db0efdcdb6da3a7a99f5e347f034f78c3c57fce GIT binary patch literal 20992 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 Y+#9?HP7=1j=i_tce^7sVsjTw<4dj0Ds{jB1 literal 0 HcmV?d00001 diff --git a/vendor/phpoffice/phpspreadsheet/samples/Reading_workbook_data/sampleData/example1.xlsx b/vendor/phpoffice/phpspreadsheet/samples/Reading_workbook_data/sampleData/example1.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..2224dd4fe674dba451e53d763dac310aa30a3b85 GIT binary patch literal 9733 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 literal 0 HcmV?d00001 diff --git a/vendor/phpoffice/phpspreadsheet/samples/bootstrap/css/bootstrap.min.css b/vendor/phpoffice/phpspreadsheet/samples/bootstrap/css/bootstrap.min.css new file mode 100644 index 0000000..ed3905e --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/bootstrap/css/bootstrap.min.css @@ -0,0 +1,6 @@ +/*! + * Bootstrap v3.3.7 (http://getbootstrap.com) + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\002a"}.glyphicon-plus:before{content:"\002b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:focus,a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:focus,a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:focus,a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:focus,a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:focus,a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:focus,a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:focus,a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:focus,a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:focus,a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:focus,a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px\9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control::-ms-expand{background-color:transparent;border:0}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px\9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.form-control-static{min-height:34px;padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:11px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.focus,.btn-default:focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c}.btn-default:hover{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{color:#333;background-color:#d4d4d4;border-color:#8c8c8c}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled.focus,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled].focus,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#286090;border-color:#122b40}.btn-primary:hover{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{color:#fff;background-color:#204d74;border-color:#122b40}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled.focus,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled].focus,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#449d44;border-color:#255625}.btn-success:hover{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.dropdown-toggle.btn-success.focus,.open>.dropdown-toggle.btn-success:focus,.open>.dropdown-toggle.btn-success:hover{color:#fff;background-color:#398439;border-color:#255625}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled.focus,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled].focus,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.dropdown-toggle.btn-info.focus,.open>.dropdown-toggle.btn-info:focus,.open>.dropdown-toggle.btn-info:hover{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled.focus,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled].focus,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.focus,.btn-warning:focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.dropdown-toggle.btn-warning.focus,.open>.dropdown-toggle.btn-warning:focus,.open>.dropdown-toggle.btn-warning:hover{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled.focus,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled].focus,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.dropdown-toggle.btn-danger.focus,.open>.dropdown-toggle.btn-danger:focus,.open>.dropdown-toggle.btn-danger:hover{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled.focus,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled].focus,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\9;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px dashed;border-bottom:4px solid\9}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control:focus{z-index:3}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{z-index:2;color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:3;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:middle;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{padding-right:15px;padding-left:15px;border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item,button.list-group-item{color:#555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;filter:alpha(opacity=0);opacity:0;line-break:auto}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);line-break:auto}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);background-color:rgba(0,0,0,0);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-10px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000\9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-10px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-10px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.modal-header:after,.modal-header:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.modal-header:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table!important}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table!important}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table!important}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table!important}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table!important}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} +/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/vendor/phpoffice/phpspreadsheet/samples/bootstrap/css/font-awesome.min.css b/vendor/phpoffice/phpspreadsheet/samples/bootstrap/css/font-awesome.min.css new file mode 100644 index 0000000..4ec9223 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/bootstrap/css/font-awesome.min.css @@ -0,0 +1,4 @@ +/*! + * Font Awesome 4.6.3 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.6.3');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.6.3') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.6.3') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.6.3') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.6.3') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.6.3#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} \ No newline at end of file diff --git a/vendor/phpoffice/phpspreadsheet/samples/bootstrap/css/phpspreadsheet.css b/vendor/phpoffice/phpspreadsheet/samples/bootstrap/css/phpspreadsheet.css new file mode 100644 index 0000000..5ea3734 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/bootstrap/css/phpspreadsheet.css @@ -0,0 +1,13 @@ +body { + padding-top: 20px; + padding-bottom: 20px; +} +.navbar { + margin-bottom: 20px; +} +.passed { + color: #339900; +} +.failed { + color: #ff0000; +} \ No newline at end of file diff --git a/vendor/phpoffice/phpspreadsheet/samples/bootstrap/fonts/FontAwesome.otf b/vendor/phpoffice/phpspreadsheet/samples/bootstrap/fonts/FontAwesome.otf new file mode 100644 index 0000000000000000000000000000000000000000..d4de13e832d567ff29c5b4e9561b8c370348cc9c GIT binary patch literal 124988 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ÐT?>-5aYhU|+1c>&YMpPK5x#-mbV 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'NS^o{z1ZKlHsAybrl=JE7AP369ntV%vB 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 CDx5C> literal 0 HcmV?d00001 diff --git a/vendor/phpoffice/phpspreadsheet/samples/bootstrap/fonts/fontawesome-webfont.eot b/vendor/phpoffice/phpspreadsheet/samples/bootstrap/fonts/fontawesome-webfont.eot new file mode 100644 index 0000000000000000000000000000000000000000..c7b00d2ba8896fd29de846b19f89fcf0d56ad152 GIT binary patch literal 76518 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%0DyT{kunD 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⋙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 z + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/phpoffice/phpspreadsheet/samples/bootstrap/fonts/fontawesome-webfont.ttf b/vendor/phpoffice/phpspreadsheet/samples/bootstrap/fonts/fontawesome-webfont.ttf new file mode 100644 index 0000000000000000000000000000000000000000..f221e50a2ef60738ba30932d834530cdfe55cb3e GIT binary patch literal 152796 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

    =v_rA!CPE#mM^4@)}X7qf;At+v)G*FZd&; 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 ALjV8( literal 0 HcmV?d00001 diff --git a/vendor/phpoffice/phpspreadsheet/samples/bootstrap/fonts/fontawesome-webfont.woff b/vendor/phpoffice/phpspreadsheet/samples/bootstrap/fonts/fontawesome-webfont.woff new file mode 100644 index 0000000000000000000000000000000000000000..6e7483cf61b490c08ed644d6ef802c69472eb247 GIT binary patch literal 90412 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%0Ded40&XED0GYFoIh+AV*?9!MR5pBW?X_8Bp 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 IH>08b4`|GD*#H0l literal 0 HcmV?d00001 diff --git a/vendor/phpoffice/phpspreadsheet/samples/bootstrap/fonts/fontawesome-webfont.woff2 b/vendor/phpoffice/phpspreadsheet/samples/bootstrap/fonts/fontawesome-webfont.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..7eb74fd127ee5eddf3b95fee6a20dc1684b0963b GIT binary patch literal 71896 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≠dR0E9ICQ{-tcR;@{R^I5P#1GUH!p=-{c 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 literal 0 HcmV?d00001 diff --git a/vendor/phpoffice/phpspreadsheet/samples/bootstrap/js/bootstrap.min.js b/vendor/phpoffice/phpspreadsheet/samples/bootstrap/js/bootstrap.min.js new file mode 100644 index 0000000..9bcd2fc --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/bootstrap/js/bootstrap.min.js @@ -0,0 +1,7 @@ +/*! + * Bootstrap v3.3.7 (http://getbootstrap.com) + * Copyright 2011-2016 Twitter, Inc. + * Licensed under the MIT license + */ +if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1||b[0]>3)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){if(a(b.target).is(this))return b.handleObj.handler.apply(this,arguments)}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.7",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a("#"===f?[]:f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.7",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c).prop(c,!0)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c).prop(c,!1))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target).closest(".btn");b.call(d,"toggle"),a(c.target).is('input[type="radio"], input[type="checkbox"]')||(c.preventDefault(),d.is("input,button")?d.trigger("focus"):d.find("input:visible,button:visible").first().trigger("focus"))}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.7",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));if(!(a>this.$items.length-1||a<0))return this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){if(!this.sliding)return this.slide("next")},c.prototype.prev=function(){if(!this.sliding)return this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.7",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger(a.Event("hidden.bs.dropdown",f)))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.7",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger(a.Event("shown.bs.dropdown",h))}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth

    ',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);if(c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),!c.isInStateTrue())return clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null,a.$element=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;!e&&/destroy|hide/.test(b)||(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.7",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.7",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.7",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return e=a-d&&"bottom"},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); \ No newline at end of file diff --git a/vendor/phpoffice/phpspreadsheet/samples/bootstrap/js/jquery.min.js b/vendor/phpoffice/phpspreadsheet/samples/bootstrap/js/jquery.min.js new file mode 100644 index 0000000..f6a6a99 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/samples/bootstrap/js/jquery.min.js @@ -0,0 +1,4 @@ +/*! jQuery v3.1.0 | (c) jQuery Foundation | jquery.org/license */ +!function(a,b){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){"use strict";var c=[],d=a.document,e=Object.getPrototypeOf,f=c.slice,g=c.concat,h=c.push,i=c.indexOf,j={},k=j.toString,l=j.hasOwnProperty,m=l.toString,n=m.call(Object),o={};function p(a,b){b=b||d;var c=b.createElement("script");c.text=a,b.head.appendChild(c).parentNode.removeChild(c)}var q="3.1.0",r=function(a,b){return new r.fn.init(a,b)},s=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,t=/^-ms-/,u=/-([a-z])/g,v=function(a,b){return b.toUpperCase()};r.fn=r.prototype={jquery:q,constructor:r,length:0,toArray:function(){return f.call(this)},get:function(a){return null!=a?a<0?this[a+this.length]:this[a]:f.call(this)},pushStack:function(a){var b=r.merge(this.constructor(),a);return b.prevObject=this,b},each:function(a){return r.each(this,a)},map:function(a){return this.pushStack(r.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(f.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(a<0?b:0);return this.pushStack(c>=0&&c0&&b-1 in a)}var x=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=function(a,b){for(var c=0,d=a.length;c+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(N),U=new RegExp("^"+L+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L+"|[*])"),ATTR:new RegExp("^"+M),PSEUDO:new RegExp("^"+N),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),aa=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:d<0?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ba=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g,ca=function(a,b){return b?"\0"===a?"\ufffd":a.slice(0,-1)+"\\"+a.charCodeAt(a.length-1).toString(16)+" ":"\\"+a},da=function(){m()},ea=ta(function(a){return a.disabled===!0},{dir:"parentNode",next:"legend"});try{G.apply(D=H.call(v.childNodes),v.childNodes),D[v.childNodes.length].nodeType}catch(fa){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s=b&&b.ownerDocument,w=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==w&&9!==w&&11!==w)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==w&&(l=Z.exec(a)))if(f=l[1]){if(9===w){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(s&&(j=s.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(l[2])return G.apply(d,b.getElementsByTagName(a)),d;if((f=l[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==w)s=b,r=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(ba,ca):b.setAttribute("id",k=u),o=g(a),h=o.length;while(h--)o[h]="#"+k+" "+sa(o[h]);r=o.join(","),s=$.test(a)&&qa(b.parentNode)||b}if(r)try{return G.apply(d,s.querySelectorAll(r)),d}catch(x){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(P,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("fieldset");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&a.sourceIndex-b.sourceIndex;if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return function(b){return"label"in b&&b.disabled===a||"form"in b&&b.disabled===a||"form"in b&&b.disabled===!1&&(b.isDisabled===a||b.isDisabled!==!a&&("label"in b||!ea(b))!==a)}}function pa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function qa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return!!b&&"HTML"!==b.nodeName},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),v!==n&&(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(n.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}},d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){if("undefined"!=typeof b.getElementsByClassName&&p)return b.getElementsByClassName(a)},r=[],q=[],(c.qsa=Y.test(n.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){a.innerHTML="";var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+K+"*[*^$|!~]?="),2!==a.querySelectorAll(":enabled").length&&q.push(":enabled",":disabled"),o.appendChild(a).disabled=!0,2!==a.querySelectorAll(":disabled").length&&q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Y.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"*"),s.call(a,"[s!='']:x"),r.push("!=",N)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Y.test(o.compareDocumentPosition),t=b||Y.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?I(k,a)-I(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?I(k,a)-I(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?la(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(S,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.escape=function(a){return(a+"").replace(ba,ca)},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(_,aa),a[3]=(a[3]||a[4]||a[5]||"").replace(_,aa),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return V.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&T.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(_,aa).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+K+")"+a+"("+K+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:!b||(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(O," ")+" ").indexOf(c)>-1:"|="===b&&(e===c||e.slice(0,c.length+1)===c+"-"))}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(P,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(_,aa),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return U.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(_,aa).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:oa(!1),disabled:oa(!0),checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:pa(function(){return[0]}),last:pa(function(a,b){return[b-1]}),eq:pa(function(a,b,c){return[c<0?c+b:c]}),even:pa(function(a,b){for(var c=0;c=0;)a.push(d);return a}),gt:pa(function(a,b,c){for(var d=c<0?c+b:c;++d1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function va(a,b,c){for(var d=0,e=b.length;d-1&&(f[j]=!(g[j]=l))}}else r=wa(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ya(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ta(function(a){return a===b},h,!0),l=ta(function(a){return I(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];i1&&ua(m),i>1&&sa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(P,"$1"),c,i0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=E.call(i));u=wa(u)}G.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&ga.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=ya(b[c]),f[u]?d.push(f):e.push(f);f=A(a,za(e,d)),f.selector=a}return f},i=ga.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(_,aa),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=V.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(_,aa),$.test(j[0].type)&&qa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&sa(j),!a)return G.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,!b||$.test(a)&&qa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("fieldset"))}),ja(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){if(!c)return a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){if(!c&&"input"===a.nodeName.toLowerCase())return a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(J,function(a,b,c){var d;if(!c)return a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);r.find=x,r.expr=x.selectors,r.expr[":"]=r.expr.pseudos,r.uniqueSort=r.unique=x.uniqueSort,r.text=x.getText,r.isXMLDoc=x.isXML,r.contains=x.contains,r.escapeSelector=x.escape;var y=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&r(a).is(c))break;d.push(a)}return d},z=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},A=r.expr.match.needsContext,B=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i,C=/^.[^:#\[\.,]*$/;function D(a,b,c){if(r.isFunction(b))return r.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return r.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(C.test(b))return r.filter(b,a,c);b=r.filter(b,a)}return r.grep(a,function(a){return i.call(b,a)>-1!==c&&1===a.nodeType})}r.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?r.find.matchesSelector(d,a)?[d]:[]:r.find.matches(a,r.grep(b,function(a){return 1===a.nodeType}))},r.fn.extend({find:function(a){var b,c,d=this.length,e=this;if("string"!=typeof a)return this.pushStack(r(a).filter(function(){for(b=0;b1?r.uniqueSort(c):c},filter:function(a){return this.pushStack(D(this,a||[],!1))},not:function(a){return this.pushStack(D(this,a||[],!0))},is:function(a){return!!D(this,"string"==typeof a&&A.test(a)?r(a):a||[],!1).length}});var E,F=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,G=r.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||E,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:F.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof r?b[0]:b,r.merge(this,r.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),B.test(e[1])&&r.isPlainObject(b))for(e in b)r.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&(this[0]=f,this.length=1),this}return a.nodeType?(this[0]=a,this.length=1,this):r.isFunction(a)?void 0!==c.ready?c.ready(a):a(r):r.makeArray(a,this)};G.prototype=r.fn,E=r(d);var H=/^(?:parents|prev(?:Until|All))/,I={children:!0,contents:!0,next:!0,prev:!0};r.fn.extend({has:function(a){var b=r(a,this),c=b.length;return this.filter(function(){for(var a=0;a-1:1===c.nodeType&&r.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?r.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?i.call(r(a),this[0]):i.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(r.uniqueSort(r.merge(this.get(),r(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function J(a,b){while((a=a[b])&&1!==a.nodeType);return a}r.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return y(a,"parentNode")},parentsUntil:function(a,b,c){return y(a,"parentNode",c)},next:function(a){return J(a,"nextSibling")},prev:function(a){return J(a,"previousSibling")},nextAll:function(a){return y(a,"nextSibling")},prevAll:function(a){return y(a,"previousSibling")},nextUntil:function(a,b,c){return y(a,"nextSibling",c)},prevUntil:function(a,b,c){return y(a,"previousSibling",c)},siblings:function(a){return z((a.parentNode||{}).firstChild,a)},children:function(a){return z(a.firstChild)},contents:function(a){return a.contentDocument||r.merge([],a.childNodes)}},function(a,b){r.fn[a]=function(c,d){var e=r.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=r.filter(d,e)),this.length>1&&(I[a]||r.uniqueSort(e),H.test(a)&&e.reverse()),this.pushStack(e)}});var K=/\S+/g;function L(a){var b={};return r.each(a.match(K)||[],function(a,c){b[c]=!0}),b}r.Callbacks=function(a){a="string"==typeof a?L(a):r.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h-1)f.splice(c,1),c<=h&&h--}),this},has:function(a){return a?r.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||b||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j};function M(a){return a}function N(a){throw a}function O(a,b,c){var d;try{a&&r.isFunction(d=a.promise)?d.call(a).done(b).fail(c):a&&r.isFunction(d=a.then)?d.call(a,b,c):b.call(void 0,a)}catch(a){c.call(void 0,a)}}r.extend({Deferred:function(b){var c=[["notify","progress",r.Callbacks("memory"),r.Callbacks("memory"),2],["resolve","done",r.Callbacks("once memory"),r.Callbacks("once memory"),0,"resolved"],["reject","fail",r.Callbacks("once memory"),r.Callbacks("once memory"),1,"rejected"]],d="pending",e={state:function(){return d},always:function(){return f.done(arguments).fail(arguments),this},"catch":function(a){return e.then(null,a)},pipe:function(){var a=arguments;return r.Deferred(function(b){r.each(c,function(c,d){var e=r.isFunction(a[d[4]])&&a[d[4]];f[d[1]](function(){var a=e&&e.apply(this,arguments);a&&r.isFunction(a.promise)?a.promise().progress(b.notify).done(b.resolve).fail(b.reject):b[d[0]+"With"](this,e?[a]:arguments)})}),a=null}).promise()},then:function(b,d,e){var f=0;function g(b,c,d,e){return function(){var h=this,i=arguments,j=function(){var a,j;if(!(b=f&&(d!==N&&(h=void 0,i=[a]),c.rejectWith(h,i))}};b?k():(r.Deferred.getStackHook&&(k.stackTrace=r.Deferred.getStackHook()),a.setTimeout(k))}}return r.Deferred(function(a){c[0][3].add(g(0,a,r.isFunction(e)?e:M,a.notifyWith)),c[1][3].add(g(0,a,r.isFunction(b)?b:M)),c[2][3].add(g(0,a,r.isFunction(d)?d:N))}).promise()},promise:function(a){return null!=a?r.extend(a,e):e}},f={};return r.each(c,function(a,b){var g=b[2],h=b[5];e[b[1]]=g.add,h&&g.add(function(){d=h},c[3-a][2].disable,c[0][2].lock),g.add(b[3].fire),f[b[0]]=function(){return f[b[0]+"With"](this===f?void 0:this,arguments),this},f[b[0]+"With"]=g.fireWith}),e.promise(f),b&&b.call(f,f),f},when:function(a){var b=arguments.length,c=b,d=Array(c),e=f.call(arguments),g=r.Deferred(),h=function(a){return function(c){d[a]=this,e[a]=arguments.length>1?f.call(arguments):c,--b||g.resolveWith(d,e)}};if(b<=1&&(O(a,g.done(h(c)).resolve,g.reject),"pending"===g.state()||r.isFunction(e[c]&&e[c].then)))return g.then();while(c--)O(e[c],h(c),g.reject);return g.promise()}});var P=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;r.Deferred.exceptionHook=function(b,c){a.console&&a.console.warn&&b&&P.test(b.name)&&a.console.warn("jQuery.Deferred exception: "+b.message,b.stack,c)},r.readyException=function(b){a.setTimeout(function(){throw b})};var Q=r.Deferred();r.fn.ready=function(a){return Q.then(a)["catch"](function(a){r.readyException(a)}),this},r.extend({isReady:!1,readyWait:1,holdReady:function(a){a?r.readyWait++:r.ready(!0)},ready:function(a){(a===!0?--r.readyWait:r.isReady)||(r.isReady=!0,a!==!0&&--r.readyWait>0||Q.resolveWith(d,[r]))}}),r.ready.then=Q.then;function R(){d.removeEventListener("DOMContentLoaded",R),a.removeEventListener("load",R),r.ready()}"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(r.ready):(d.addEventListener("DOMContentLoaded",R),a.addEventListener("load",R));var S=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===r.type(c)){e=!0;for(h in c)S(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0, +r.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(r(a),c)})),b))for(;h1,null,!0)},removeData:function(a){return this.each(function(){W.remove(this,a)})}}),r.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=V.get(a,b),c&&(!d||r.isArray(c)?d=V.access(a,b,r.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=r.queue(a,b),d=c.length,e=c.shift(),f=r._queueHooks(a,b),g=function(){r.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return V.get(a,c)||V.access(a,c,{empty:r.Callbacks("once memory").add(function(){V.remove(a,[b+"queue",c])})})}}),r.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length\x20\t\r\n\f]+)/i,ja=/^$|\/(?:java|ecma)script/i,ka={option:[1,""],thead:[1,"","
    "],col:[2,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]};ka.optgroup=ka.option,ka.tbody=ka.tfoot=ka.colgroup=ka.caption=ka.thead,ka.th=ka.td;function la(a,b){var c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&r.nodeName(a,b)?r.merge([a],c):c}function ma(a,b){for(var c=0,d=a.length;c-1)e&&e.push(f);else if(j=r.contains(f.ownerDocument,f),g=la(l.appendChild(f),"script"),j&&ma(g),c){k=0;while(f=g[k++])ja.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),o.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="",o.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var pa=d.documentElement,qa=/^key/,ra=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,sa=/^([^.]*)(?:\.(.+)|)/;function ta(){return!0}function ua(){return!1}function va(){try{return d.activeElement}catch(a){}}function wa(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)wa(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=ua;else if(!e)return a;return 1===f&&(g=e,e=function(a){return r().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=r.guid++)),a.each(function(){r.event.add(this,b,e,d,c)})}r.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=V.get(a);if(q){c.handler&&(f=c,c=f.handler,e=f.selector),e&&r.find.matchesSelector(pa,e),c.guid||(c.guid=r.guid++),(i=q.events)||(i=q.events={}),(g=q.handle)||(g=q.handle=function(b){return"undefined"!=typeof r&&r.event.triggered!==b.type?r.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(K)||[""],j=b.length;while(j--)h=sa.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n&&(l=r.event.special[n]||{},n=(e?l.delegateType:l.bindType)||n,l=r.event.special[n]||{},k=r.extend({type:n,origType:p,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&r.expr.match.needsContext.test(e),namespace:o.join(".")},f),(m=i[n])||(m=i[n]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,o,g)!==!1||a.addEventListener&&a.addEventListener(n,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),r.event.global[n]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=V.hasData(a)&&V.get(a);if(q&&(i=q.events)){b=(b||"").match(K)||[""],j=b.length;while(j--)if(h=sa.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n){l=r.event.special[n]||{},n=(d?l.delegateType:l.bindType)||n,m=i[n]||[],h=h[2]&&new RegExp("(^|\\.)"+o.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&p!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,o,q.handle)!==!1||r.removeEvent(a,n,q.handle),delete i[n])}else for(n in i)r.event.remove(a,n+b[j],c,d,!0);r.isEmptyObject(i)&&V.remove(a,"handle events")}},dispatch:function(a){var b=r.event.fix(a),c,d,e,f,g,h,i=new Array(arguments.length),j=(V.get(this,"events")||{})[b.type]||[],k=r.event.special[b.type]||{};for(i[0]=b,c=1;c-1:r.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h\x20\t\r\n\f]*)[^>]*)\/>/gi,ya=/\s*$/g;function Ca(a,b){return r.nodeName(a,"table")&&r.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a:a}function Da(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function Ea(a){var b=Aa.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Fa(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(V.hasData(a)&&(f=V.access(a),g=V.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;c1&&"string"==typeof q&&!o.checkClone&&za.test(q))return a.each(function(e){var f=a.eq(e);s&&(b[0]=q.call(this,e,f.html())),Ha(f,b,c,d)});if(m&&(e=oa(b,a[0].ownerDocument,!1,a,d),f=e.firstChild,1===e.childNodes.length&&(e=f),f||d)){for(h=r.map(la(e,"script"),Da),i=h.length;l")},clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=r.contains(a.ownerDocument,a);if(!(o.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||r.isXMLDoc(a)))for(g=la(h),f=la(a),d=0,e=f.length;d0&&ma(g,!i&&la(a,"script")),h},cleanData:function(a){for(var b,c,d,e=r.event.special,f=0;void 0!==(c=a[f]);f++)if(T(c)){if(b=c[V.expando]){if(b.events)for(d in b.events)e[d]?r.event.remove(c,d):r.removeEvent(c,d,b.handle);c[V.expando]=void 0}c[W.expando]&&(c[W.expando]=void 0)}}}),r.fn.extend({detach:function(a){return Ia(this,a,!0)},remove:function(a){return Ia(this,a)},text:function(a){return S(this,function(a){return void 0===a?r.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=a)})},null,a,arguments.length)},append:function(){return Ha(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ca(this,a);b.appendChild(a)}})},prepend:function(){return Ha(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ca(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return Ha(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return Ha(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(r.cleanData(la(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null!=a&&a,b=null==b?a:b,this.map(function(){return r.clone(this,a,b)})},html:function(a){return S(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!ya.test(a)&&!ka[(ia.exec(a)||["",""])[1].toLowerCase()]){a=r.htmlPrefilter(a);try{for(;c1)}});function Xa(a,b,c,d,e){return new Xa.prototype.init(a,b,c,d,e)}r.Tween=Xa,Xa.prototype={constructor:Xa,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||r.easing._default,this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(r.cssNumber[c]?"":"px")},cur:function(){var a=Xa.propHooks[this.prop];return a&&a.get?a.get(this):Xa.propHooks._default.get(this)},run:function(a){var b,c=Xa.propHooks[this.prop];return this.options.duration?this.pos=b=r.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Xa.propHooks._default.set(this),this}},Xa.prototype.init.prototype=Xa.prototype,Xa.propHooks={_default:{get:function(a){var b;return 1!==a.elem.nodeType||null!=a.elem[a.prop]&&null==a.elem.style[a.prop]?a.elem[a.prop]:(b=r.css(a.elem,a.prop,""),b&&"auto"!==b?b:0)},set:function(a){r.fx.step[a.prop]?r.fx.step[a.prop](a):1!==a.elem.nodeType||null==a.elem.style[r.cssProps[a.prop]]&&!r.cssHooks[a.prop]?a.elem[a.prop]=a.now:r.style(a.elem,a.prop,a.now+a.unit)}}},Xa.propHooks.scrollTop=Xa.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},r.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2},_default:"swing"},r.fx=Xa.prototype.init,r.fx.step={};var Ya,Za,$a=/^(?:toggle|show|hide)$/,_a=/queueHooks$/;function ab(){Za&&(a.requestAnimationFrame(ab),r.fx.tick())}function bb(){return a.setTimeout(function(){Ya=void 0}),Ya=r.now()}function cb(a,b){var c,d=0,e={height:a};for(b=b?1:0;d<4;d+=2-b)c=aa[d],e["margin"+c]=e["padding"+c]=a;return b&&(e.opacity=e.width=a),e}function db(a,b,c){for(var d,e=(gb.tweeners[b]||[]).concat(gb.tweeners["*"]),f=0,g=e.length;f1)},removeAttr:function(a){return this.each(function(){r.removeAttr(this,a)})}}),r.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return"undefined"==typeof a.getAttribute?r.prop(a,b,c):(1===f&&r.isXMLDoc(a)||(e=r.attrHooks[b.toLowerCase()]||(r.expr.match.bool.test(b)?hb:void 0)),void 0!==c?null===c?void r.removeAttr(a,b):e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:(a.setAttribute(b,c+""),c):e&&"get"in e&&null!==(d=e.get(a,b))?d:(d=r.find.attr(a,b),null==d?void 0:d))},attrHooks:{type:{set:function(a,b){if(!o.radioValue&&"radio"===b&&r.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}},removeAttr:function(a,b){var c,d=0,e=b&&b.match(K); +if(e&&1===a.nodeType)while(c=e[d++])a.removeAttribute(c)}}),hb={set:function(a,b,c){return b===!1?r.removeAttr(a,c):a.setAttribute(c,c),c}},r.each(r.expr.match.bool.source.match(/\w+/g),function(a,b){var c=ib[b]||r.find.attr;ib[b]=function(a,b,d){var e,f,g=b.toLowerCase();return d||(f=ib[g],ib[g]=e,e=null!=c(a,b,d)?g:null,ib[g]=f),e}});var jb=/^(?:input|select|textarea|button)$/i,kb=/^(?:a|area)$/i;r.fn.extend({prop:function(a,b){return S(this,r.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[r.propFix[a]||a]})}}),r.extend({prop:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return 1===f&&r.isXMLDoc(a)||(b=r.propFix[b]||b,e=r.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=r.find.attr(a,"tabindex");return b?parseInt(b,10):jb.test(a.nodeName)||kb.test(a.nodeName)&&a.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),o.optSelected||(r.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null},set:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}}),r.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){r.propFix[this.toLowerCase()]=this});var lb=/[\t\r\n\f]/g;function mb(a){return a.getAttribute&&a.getAttribute("class")||""}r.fn.extend({addClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).addClass(a.call(this,b,mb(this)))});if("string"==typeof a&&a){b=a.match(K)||[];while(c=this[i++])if(e=mb(c),d=1===c.nodeType&&(" "+e+" ").replace(lb," ")){g=0;while(f=b[g++])d.indexOf(" "+f+" ")<0&&(d+=f+" ");h=r.trim(d),e!==h&&c.setAttribute("class",h)}}return this},removeClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).removeClass(a.call(this,b,mb(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof a&&a){b=a.match(K)||[];while(c=this[i++])if(e=mb(c),d=1===c.nodeType&&(" "+e+" ").replace(lb," ")){g=0;while(f=b[g++])while(d.indexOf(" "+f+" ")>-1)d=d.replace(" "+f+" "," ");h=r.trim(d),e!==h&&c.setAttribute("class",h)}}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):r.isFunction(a)?this.each(function(c){r(this).toggleClass(a.call(this,c,mb(this),b),b)}):this.each(function(){var b,d,e,f;if("string"===c){d=0,e=r(this),f=a.match(K)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else void 0!==a&&"boolean"!==c||(b=mb(this),b&&V.set(this,"__className__",b),this.setAttribute&&this.setAttribute("class",b||a===!1?"":V.get(this,"__className__")||""))})},hasClass:function(a){var b,c,d=0;b=" "+a+" ";while(c=this[d++])if(1===c.nodeType&&(" "+mb(c)+" ").replace(lb," ").indexOf(b)>-1)return!0;return!1}});var nb=/\r/g,ob=/[\x20\t\r\n\f]+/g;r.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=r.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,r(this).val()):a,null==e?e="":"number"==typeof e?e+="":r.isArray(e)&&(e=r.map(e,function(a){return null==a?"":a+""})),b=r.valHooks[this.type]||r.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=r.valHooks[e.type]||r.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(nb,""):null==c?"":c)}}}),r.extend({valHooks:{option:{get:function(a){var b=r.find.attr(a,"value");return null!=b?b:r.trim(r.text(a)).replace(ob," ")}},select:{get:function(a){for(var b,c,d=a.options,e=a.selectedIndex,f="select-one"===a.type,g=f?null:[],h=f?e+1:d.length,i=e<0?h:f?e:0;i-1)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),r.each(["radio","checkbox"],function(){r.valHooks[this]={set:function(a,b){if(r.isArray(b))return a.checked=r.inArray(r(a).val(),b)>-1}},o.checkOn||(r.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var pb=/^(?:focusinfocus|focusoutblur)$/;r.extend(r.event,{trigger:function(b,c,e,f){var g,h,i,j,k,m,n,o=[e||d],p=l.call(b,"type")?b.type:b,q=l.call(b,"namespace")?b.namespace.split("."):[];if(h=i=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!pb.test(p+r.event.triggered)&&(p.indexOf(".")>-1&&(q=p.split("."),p=q.shift(),q.sort()),k=p.indexOf(":")<0&&"on"+p,b=b[r.expando]?b:new r.Event(p,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=q.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:r.makeArray(c,[b]),n=r.event.special[p]||{},f||!n.trigger||n.trigger.apply(e,c)!==!1)){if(!f&&!n.noBubble&&!r.isWindow(e)){for(j=n.delegateType||p,pb.test(j+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),i=h;i===(e.ownerDocument||d)&&o.push(i.defaultView||i.parentWindow||a)}g=0;while((h=o[g++])&&!b.isPropagationStopped())b.type=g>1?j:n.bindType||p,m=(V.get(h,"events")||{})[b.type]&&V.get(h,"handle"),m&&m.apply(h,c),m=k&&h[k],m&&m.apply&&T(h)&&(b.result=m.apply(h,c),b.result===!1&&b.preventDefault());return b.type=p,f||b.isDefaultPrevented()||n._default&&n._default.apply(o.pop(),c)!==!1||!T(e)||k&&r.isFunction(e[p])&&!r.isWindow(e)&&(i=e[k],i&&(e[k]=null),r.event.triggered=p,e[p](),r.event.triggered=void 0,i&&(e[k]=i)),b.result}},simulate:function(a,b,c){var d=r.extend(new r.Event,c,{type:a,isSimulated:!0});r.event.trigger(d,null,b)}}),r.fn.extend({trigger:function(a,b){return this.each(function(){r.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];if(c)return r.event.trigger(a,b,c,!0)}}),r.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(a,b){r.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),r.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),o.focusin="onfocusin"in a,o.focusin||r.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){r.event.simulate(b,a.target,r.event.fix(a))};r.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=V.access(d,b);e||d.addEventListener(a,c,!0),V.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=V.access(d,b)-1;e?V.access(d,b,e):(d.removeEventListener(a,c,!0),V.remove(d,b))}}});var qb=a.location,rb=r.now(),sb=/\?/;r.parseXML=function(b){var c;if(!b||"string"!=typeof b)return null;try{c=(new a.DOMParser).parseFromString(b,"text/xml")}catch(d){c=void 0}return c&&!c.getElementsByTagName("parsererror").length||r.error("Invalid XML: "+b),c};var tb=/\[\]$/,ub=/\r?\n/g,vb=/^(?:submit|button|image|reset|file)$/i,wb=/^(?:input|select|textarea|keygen)/i;function xb(a,b,c,d){var e;if(r.isArray(b))r.each(b,function(b,e){c||tb.test(a)?d(a,e):xb(a+"["+("object"==typeof e&&null!=e?b:"")+"]",e,c,d)});else if(c||"object"!==r.type(b))d(a,b);else for(e in b)xb(a+"["+e+"]",b[e],c,d)}r.param=function(a,b){var c,d=[],e=function(a,b){var c=r.isFunction(b)?b():b;d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(null==c?"":c)};if(r.isArray(a)||a.jquery&&!r.isPlainObject(a))r.each(a,function(){e(this.name,this.value)});else for(c in a)xb(c,a[c],b,e);return d.join("&")},r.fn.extend({serialize:function(){return r.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=r.prop(this,"elements");return a?r.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!r(this).is(":disabled")&&wb.test(this.nodeName)&&!vb.test(a)&&(this.checked||!ha.test(a))}).map(function(a,b){var c=r(this).val();return null==c?null:r.isArray(c)?r.map(c,function(a){return{name:b.name,value:a.replace(ub,"\r\n")}}):{name:b.name,value:c.replace(ub,"\r\n")}}).get()}});var yb=/%20/g,zb=/#.*$/,Ab=/([?&])_=[^&]*/,Bb=/^(.*?):[ \t]*([^\r\n]*)$/gm,Cb=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Db=/^(?:GET|HEAD)$/,Eb=/^\/\//,Fb={},Gb={},Hb="*/".concat("*"),Ib=d.createElement("a");Ib.href=qb.href;function Jb(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(K)||[];if(r.isFunction(c))while(d=f[e++])"+"===d[0]?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function Kb(a,b,c,d){var e={},f=a===Gb;function g(h){var i;return e[h]=!0,r.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function Lb(a,b){var c,d,e=r.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&r.extend(!0,a,d),a}function Mb(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader("Content-Type"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+" "+i[0]]){f=e;break}g||(g=e)}f=f||g}if(f)return f!==i[0]&&i.unshift(f),c[f]}function Nb(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}r.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:qb.href,type:"GET",isLocal:Cb.test(qb.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Hb,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":r.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?Lb(Lb(a,r.ajaxSettings),b):Lb(r.ajaxSettings,a)},ajaxPrefilter:Jb(Fb),ajaxTransport:Jb(Gb),ajax:function(b,c){"object"==typeof b&&(c=b,b=void 0),c=c||{};var e,f,g,h,i,j,k,l,m,n,o=r.ajaxSetup({},c),p=o.context||o,q=o.context&&(p.nodeType||p.jquery)?r(p):r.event,s=r.Deferred(),t=r.Callbacks("once memory"),u=o.statusCode||{},v={},w={},x="canceled",y={readyState:0,getResponseHeader:function(a){var b;if(k){if(!h){h={};while(b=Bb.exec(g))h[b[1].toLowerCase()]=b[2]}b=h[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return k?g:null},setRequestHeader:function(a,b){return null==k&&(a=w[a.toLowerCase()]=w[a.toLowerCase()]||a,v[a]=b),this},overrideMimeType:function(a){return null==k&&(o.mimeType=a),this},statusCode:function(a){var b;if(a)if(k)y.always(a[y.status]);else for(b in a)u[b]=[u[b],a[b]];return this},abort:function(a){var b=a||x;return e&&e.abort(b),A(0,b),this}};if(s.promise(y),o.url=((b||o.url||qb.href)+"").replace(Eb,qb.protocol+"//"),o.type=c.method||c.type||o.method||o.type,o.dataTypes=(o.dataType||"*").toLowerCase().match(K)||[""],null==o.crossDomain){j=d.createElement("a");try{j.href=o.url,j.href=j.href,o.crossDomain=Ib.protocol+"//"+Ib.host!=j.protocol+"//"+j.host}catch(z){o.crossDomain=!0}}if(o.data&&o.processData&&"string"!=typeof o.data&&(o.data=r.param(o.data,o.traditional)),Kb(Fb,o,c,y),k)return y;l=r.event&&o.global,l&&0===r.active++&&r.event.trigger("ajaxStart"),o.type=o.type.toUpperCase(),o.hasContent=!Db.test(o.type),f=o.url.replace(zb,""),o.hasContent?o.data&&o.processData&&0===(o.contentType||"").indexOf("application/x-www-form-urlencoded")&&(o.data=o.data.replace(yb,"+")):(n=o.url.slice(f.length),o.data&&(f+=(sb.test(f)?"&":"?")+o.data,delete o.data),o.cache===!1&&(f=f.replace(Ab,""),n=(sb.test(f)?"&":"?")+"_="+rb++ +n),o.url=f+n),o.ifModified&&(r.lastModified[f]&&y.setRequestHeader("If-Modified-Since",r.lastModified[f]),r.etag[f]&&y.setRequestHeader("If-None-Match",r.etag[f])),(o.data&&o.hasContent&&o.contentType!==!1||c.contentType)&&y.setRequestHeader("Content-Type",o.contentType),y.setRequestHeader("Accept",o.dataTypes[0]&&o.accepts[o.dataTypes[0]]?o.accepts[o.dataTypes[0]]+("*"!==o.dataTypes[0]?", "+Hb+"; q=0.01":""):o.accepts["*"]);for(m in o.headers)y.setRequestHeader(m,o.headers[m]);if(o.beforeSend&&(o.beforeSend.call(p,y,o)===!1||k))return y.abort();if(x="abort",t.add(o.complete),y.done(o.success),y.fail(o.error),e=Kb(Gb,o,c,y)){if(y.readyState=1,l&&q.trigger("ajaxSend",[y,o]),k)return y;o.async&&o.timeout>0&&(i=a.setTimeout(function(){y.abort("timeout")},o.timeout));try{k=!1,e.send(v,A)}catch(z){if(k)throw z;A(-1,z)}}else A(-1,"No Transport");function A(b,c,d,h){var j,m,n,v,w,x=c;k||(k=!0,i&&a.clearTimeout(i),e=void 0,g=h||"",y.readyState=b>0?4:0,j=b>=200&&b<300||304===b,d&&(v=Mb(o,y,d)),v=Nb(o,v,y,j),j?(o.ifModified&&(w=y.getResponseHeader("Last-Modified"),w&&(r.lastModified[f]=w),w=y.getResponseHeader("etag"),w&&(r.etag[f]=w)),204===b||"HEAD"===o.type?x="nocontent":304===b?x="notmodified":(x=v.state,m=v.data,n=v.error,j=!n)):(n=x,!b&&x||(x="error",b<0&&(b=0))),y.status=b,y.statusText=(c||x)+"",j?s.resolveWith(p,[m,x,y]):s.rejectWith(p,[y,x,n]),y.statusCode(u),u=void 0,l&&q.trigger(j?"ajaxSuccess":"ajaxError",[y,o,j?m:n]),t.fireWith(p,[y,x]),l&&(q.trigger("ajaxComplete",[y,o]),--r.active||r.event.trigger("ajaxStop")))}return y},getJSON:function(a,b,c){return r.get(a,b,c,"json")},getScript:function(a,b){return r.get(a,void 0,b,"script")}}),r.each(["get","post"],function(a,b){r[b]=function(a,c,d,e){return r.isFunction(c)&&(e=e||d,d=c,c=void 0),r.ajax(r.extend({url:a,type:b,dataType:e,data:c,success:d},r.isPlainObject(a)&&a))}}),r._evalUrl=function(a){return r.ajax({url:a,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},r.fn.extend({wrapAll:function(a){var b;return this[0]&&(r.isFunction(a)&&(a=a.call(this[0])),b=r(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this},wrapInner:function(a){return r.isFunction(a)?this.each(function(b){r(this).wrapInner(a.call(this,b))}):this.each(function(){var b=r(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=r.isFunction(a);return this.each(function(c){r(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(a){return this.parent(a).not("body").each(function(){r(this).replaceWith(this.childNodes)}),this}}),r.expr.pseudos.hidden=function(a){return!r.expr.pseudos.visible(a)},r.expr.pseudos.visible=function(a){return!!(a.offsetWidth||a.offsetHeight||a.getClientRects().length)},r.ajaxSettings.xhr=function(){try{return new a.XMLHttpRequest}catch(b){}};var Ob={0:200,1223:204},Pb=r.ajaxSettings.xhr();o.cors=!!Pb&&"withCredentials"in Pb,o.ajax=Pb=!!Pb,r.ajaxTransport(function(b){var c,d;if(o.cors||Pb&&!b.crossDomain)return{send:function(e,f){var g,h=b.xhr();if(h.open(b.type,b.url,b.async,b.username,b.password),b.xhrFields)for(g in b.xhrFields)h[g]=b.xhrFields[g];b.mimeType&&h.overrideMimeType&&h.overrideMimeType(b.mimeType),b.crossDomain||e["X-Requested-With"]||(e["X-Requested-With"]="XMLHttpRequest");for(g in e)h.setRequestHeader(g,e[g]);c=function(a){return function(){c&&(c=d=h.onload=h.onerror=h.onabort=h.onreadystatechange=null,"abort"===a?h.abort():"error"===a?"number"!=typeof h.status?f(0,"error"):f(h.status,h.statusText):f(Ob[h.status]||h.status,h.statusText,"text"!==(h.responseType||"text")||"string"!=typeof h.responseText?{binary:h.response}:{text:h.responseText},h.getAllResponseHeaders()))}},h.onload=c(),d=h.onerror=c("error"),void 0!==h.onabort?h.onabort=d:h.onreadystatechange=function(){4===h.readyState&&a.setTimeout(function(){c&&d()})},c=c("abort");try{h.send(b.hasContent&&b.data||null)}catch(i){if(c)throw i}},abort:function(){c&&c()}}}),r.ajaxPrefilter(function(a){a.crossDomain&&(a.contents.script=!1)}),r.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(a){return r.globalEval(a),a}}}),r.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET")}),r.ajaxTransport("script",function(a){if(a.crossDomain){var b,c;return{send:function(e,f){b=r("

    6)CDi&Dfdf9a)nwad!2Jv#*zCtV4wBYA}5 zYk*XDeRM|S-#`Z31qaspBk^{{7wE=mqx9WRQ)!%LJ;m6>PYX!XbrW?d`4vEn9A-lq z==q3B9tYMeDWd}8{vH2I9*K$Y_S|U%AsGisa{(03l8MZ1Hf)yIb5vW z3zU^x4P)48>;d@TKfVTjs-9?lbgv9|z&Z5jCjZyMVUX8)V#=M+9(VH_wTj?BRKX#+ zCm}16Lutn79VsU&y6sTgZ1WPAHciBuQ$C}HoN}4!VN6quS1*HTu|!er|H>~kwL=3* zFQmv=-DJC4J18LvL-s9dcga zh!#O8NO^wklbZ!Q|7~OuY7g+AKv-)Dj#89b$K150PfaN`AULB_xlnqPbe?!!bn{8F z14as+4QS>oV1l4Tr65W5CkKS99u#} z8v)&69Jb(k1_88;G@yj5lzg{mYsE#bpofeqapAkx;OW}An8qLf=iVlW3xzIFJw!pR zXaeZ0dj6!bFH6$4s*HUF)SR&E0Ww9ydGHOT`@4TO@EzcEnLD49Vj{x>ncJ_0^x?4V zL{{<`{k+(K1t=J47ps&qYXB|hoewbby{JMz(Vo!&Q2sy6*g=Vy?YE*?B#~E)8CqMy9gl7`qTW7IS!c&_582qVYU%Sk$W%ku8(7Xvl)YZoN)b z`Rdw)lc$US(f{AIJQb4HL!KJ;4KFwhKoAOvV#?4OoZDUgvx%gaMtG5U+DgvH1sr0? zJq&%Z&ZjzbvG#EsFjI_omX=sl;bp7IO7R=Da#sj86fV5C7#{T?z-)oK5mz5@BOU(A z0Ic;mXX0#L)zqf^HH0FjDDMoO=53dCz!zwkIBE*DoWOEe{&C!Dw59%NkQfJgnxwvw z1e}#KzH>I){4RX^OLA29{r_2)cV&3^#{i*)jOZcdzs)EdCryn9zw^JWG}RUo-wOM` zsgZx#V&EUzb2w{y#Sy+U8W8g6=G%0OI#qo83puDv>4lUO_)&t$-r9P*TiGn5a4P;I z0xM1vJM!xzviT&Upe6rBN~`&T7=~BH9x#|#!`pYb6|iO0$-IX1KR2{4YAl4A*WvsF zFQ*8M;;u!Y_50h#9N;Ohk0okuV z_zih?ZBPDYC?%0;ASSOuOZv0ep2Q;i^hbpskgN7)gePpOA9_9E(!OnQ|6V|j{cV#NQaTINnr?O2Bo z7>@W{mvTyUN%D38$^4%pV3}_sVZrc$EaxeseD9O2f1R?Hffjb=Eh5|6Cc*avk5Z)u zw5^#7MaH6k+vuHyhFO?$?& zetMmO_a||C-MVJKZ2B{VnFW6wuQ-=8mY(}PH>pbtxf_HtlBKL#YVZXx%?qg>NtE0h z%Gn>hd`#!mvcOKuqwA^p%_~^3V8i_TPpNX(7#>lZWU#>EMDH={_ z;LMhRs41yBKuuZS2qJwU?7xW-_TM8T|JaE>b$LLn4 zPPdlZLVWL2C{7b$n-6J$qlEl(gG%9;9L*o2pzI5f)F0X2#G!bQgBj#G*M0QXh9mwt z%zP**wK09llA)QVlUt{^)94d@gUy=I5h((%=omv%(m519<28_g+XuQ<9igjzkHq~3m@NmN7?Z5dR+-m-5md0<`^4UsDSq>8=??asyLDuxk4o%|x!xTrcGH!Z zGcnIV!sXfYGOQB%9gj%>*)UN0Ge9|4j+YSmOEH*^ZE!hb0^a*7{9uMiyLm&gcHD8s z4-yl^*+%Y{Ou0SQ@ualzYac_cf^&kIfO|1lQm?>BSjy)9ALWXySHBzk2BTrv1t60h z2BjW)6M9BzQ6~GIoT`}kkv#uL&qahpeu)m~ZS!vRip;byBs?r;6D6a=(BU5`Sr>Yj z`~e1H%vj%D;#)(RL1MtWw{2uQN#PKkQrY|K?ubf4{gmvZaw7n~+8TKmo+0F{nW|_7 zr0SqrzfV4vo`dZGhXa_LhqI z{h3>oJk#-)g0{YknmnEKhk<_%X@?3dVD`t?d%l5g%QtZskJNGaDlUs`+}FLhbFFGzMj(M$l2vJUKa$l{`@0ya%T7pMm-~lNgx;o^ z6dj(AsgycXu0Okka>maas6g@g^CIg>(Fv*E3+2DG|IRuG^o|bYo;Ab!!bRZo8SBCF zYZ8)0;3fn+d7N^&wM6}!W~s}z9tIzr7(2?RF)8CGsYP6$p1IFOLkb z(!8$R!C?paYep1Yi!Gn(eYQ?^xjrwnD2<-4h79T8MZHo;;`uuilx>*(jQ_8kfaV0uBo2U7dKUagOaP+>Q+0 zuV)i$e2Yv>oXPmG8@TQ^V?`ZcvqZAtuKNvYkJHH^HFR%l?Kbwx_;QDmIsLbP;I_6m zcehJ7*|J5DFNGDL<4ygwuTCv&O0+7&&g|dzR;);LG*ZWgoZfFLsA&j}pDYw&=@Kfj z?l*8{pRD$8!X1JN_kL=>b@t#cPB!N2-sKsB2vK zfm>!8{nO%ewuFX4S!tr|QX89{aBG?dSUAXGbwlwJt8?;%nn@rv!ov%h1F!b)Ec9%3 zw}k>+57FTqLrn7gLRE7H=4OHUilnn+A#-XX(pK`)NlSvkU7y#o#&PL0dLSjCBWtgN zj3ziH+)Jau3n^@ruP0(h$Z#lVQraf*c)>Z)&@koV4-7H_i;eA8M{!kL;kCJR+nZj{imsA zg$K9=$57Yu!jBeS-b~f2q$qIt5|m`pP*RK%6pnK$#FCq+O{<_?VRY>a3N+Rmj&nA= zU0#F}Wzi6BtbiAy%2L@fZrPiQ!5mn@o1Z>ROP#XiOE`Sm`XY~*rNyA?TL&=~x0tJ7 z|9h>S^ERTw+z(HwIM?PZ#}7)S`+Sn4r6cezex3GK8R3iCrb-#%x-3VzTy+P-xG4w2 zad2Io7Fpd{gJfN?44!WCG-avPq=Rtz=t=ln~H1=E*gTrP(=QH|IuYXUY4 zE-!UEeH6-ecu-5tT5~n3j*GOGinLmaX4#pCt-E|2$tpae8ZrGq-FMk|sSJ5!RXg-7 z`f;TJK#Y0jW0r$E)_@b?Ja%an-oQBVbkw|o)r93sAakI!!x9qBJj>1>t>5qSo87!2 z{MRZBov#b5@f-)Z8Sv_td}77%fY#Cg2YaU30QaX4Hi}he1K}tg-4vSl7k8_FF1%Me zE)bKhJCP5r+hZ_1+dX8wXRn?Jl$EpJChasqysxf2kiA#Iz(pfVR77WH9@m{R2RF$B zI4e7K85r|8ff&M-=M^4A6}IUymNu?X5&QVYmHyO@4zWgI%IN(&F5Q)w<<)E zE@?8}V#3|G_EyIf_cda_UlJCJu$%jaFl#3N-Wix-r{6s(l}aiNiI}~5W#j%~OTvMP zOfUL$7KW~_(-3+0$D?!Ud@AY2%$(rA-(fV|))>wSo`9Nvv>;4JiqkY$-L^ zNvyd0BgGd5XWJ7CyrXyqv(@+aMS)jx*vAPka`pUe8P+stN+Ma$k0jA-RSbn~?eBb0+r>M>*u=JsL4?#U4vEqbi!&5YE zWXk)g&f)tP42h|GbxA{wUw6J?v#{q@xabZh2(}^)MEDBxVdj7su`q{F=kLCAg;etM zL3|&{Oet451R)V*VFYBplF`HLNAqROucoQ=v3wtnh`2r>0d(~cjrH&a%kh@V$ANSF z73}h%eXP|N@77n~c0_LVXa;xuA$sV+t5mLSo6D5bIZ1#jTyE>lYQE)ur7imN$dvv` z&#vcv=Hs?A<&$5@W^Q+&pRZIcx^s1KGJ7@^0;W21B~uN@vkp*mzwJO90)H7^D<^~u zS&Q0mrJC)L1g8w1R2rhmnOhpQ^o@TjX`O^}(ewFor1Cp+>C@Y5lB{DT4+g7b!&eHv z_HJgUV6g>kO69iI*n~DjZ`caCb5Zdb?$sx-c6M)7I761F|1`Dpmpju#?t$W<$LIXz z)xV+fB}JTMd}e!gJloH5x$3N-&+EW#bah}TU7gJeUESw5B)Y%HN{QVO$r9ST1L!+n z*_Q^X%fRu&N-LqB)$yo0P{8UPd8^m{I}Uo%WetSJ*!)F}|vGQohf)GuMW6 zI?Pg6AlhhWC|D^3WFzj-c;#j}c!4-hR@GY3*Be|X#<%3Y?OYe#J?&zXEW)>Fgfy@8 z!dM`>!EOSRPygqdv(un>B*oj`@)!JOdq$k!d#7T2kIZbyLs%WCX0BXB*ax@+OMFoy z*dd=3L9`=QfHwWwr;^%sg@HXbjS92iwCSV+95!FpQs2*nJn7}+DHXOnrX3q)jbE_r+gz%6$c;#K^y8R{ zR-UKg#*jm6235%)jy5+e_G^wt zI#KTvxhq6W)xhb~irUCScKQBhEc#B6Px77Kb=JGiN9(xD_(@>maXkE8to*ZB(j@qNwi}^SOx+d)qz^W)#ep%|6;}u zeXlVzoJp~Xh|WSzPiG0Wi}YanC7vBII+uQ@N%uvj{H3QS4ec>H&H+@A?T+&wdqNSG ze79wrpH^=agxhdR4^Vr~2Oyn(rt?`3-%6mqv+Bg<&fI7EO~}v6cO)VpZEe5i4WPFQ z92URL#6%5N)wgRyE$`L08yzstGFwSJ*$oCNcQP;_Z;s$PS-nj|P~&0xZC<6wOB2h* zExW*sQ@&&8v>s>OQWUvfus|7s9CJovSP>aOzix=mX;pYaL$e6!X14y(rw|~Si>1VW z!_<<(+&(xbJPJHxxB1LMAE-|Jy6e_InJhbWgUM)~IjtFQ4yb?}yf=$h5u5HzmbGz) z1x<;nWm-mpbMtZ88jZ`3^LGF#XcAQgd(^(0W)^~`v`U?(C%IM5K&oL7>%drw(&Ce$ z*>Y3(iYxmaM@I1%8k}l*TJuHCTkx4zX}XryV?)?WwhsL^i$!!s%7SkNCzbmW>_-v~ zLgx+l>;!95wz^$bn!xlT2`{*lXD+(M>^8=2H9dU@VWauiL>?z*q31jqO?cX>&hc@*~7_J?mziK6akEI%RebX_R_?Y{O z|9D3N!#?=wsSkn_(IRF5(LK^NYhCgV1Jv!F6>7i4M%*_BRNw1_m3$(jyvYu6;Xdxebxw6aDd41aUbsv}L2Hc|Fq6D#F$?SPo>i@$SGjepgjqs(-I?0d)Y)} z7wHf@&#Kc>{!}|bGqlDWO-zLl_P{+*<*^|KwS5@y|liIKMp z;tTxbOSLbl3UT&M-PME?5H7uz5RIBV-F+r*Up}8W{@vP5g>&S8yV{tuc|&dIeuh_K4CjY4B@7#f=P)bius8$=7KW6@JmB}~SrSPvGu+4I zKdN2J!!{z3>{jUe0ylj!xklI>3BYdO*!xG0N)?o3O#H7s>`ZJH?+Ji=?J%mDwzGz^ z;;j56k_~>kp0wpCPgS2J3(DAKI4069$qAh_3Mpk-0iGSg^MD=ZLs|?B@ zJt~Nl#DGc+-QWl!$`I1h-5t{KV`wSqMp{6SP6+`CDd|Q)x&@@`+oRt5-uJzq|KXf- z_Bng6{j9Z~6}u75<3|Z1IZx9OP4kmY2;6R{U^-58@iu6>u(#tfameR2pZ2@%5)3%y zc4aZLHA1P>=Gc zKJBdEpz==CzPz&=T(?g-^3@bgOpEkhHIy^D{`Oa(q}*+b<-y6Dc)5m`d8UEg0g_St z4fQmUy`#%b5ISG0OS>owk%o?jy~cObXFtwE>GlkghhleDuWhm!>@K)&e4!iG=iTyJ zVLxguqV`wzyGTllzI}_fJ~nhX_JOx9dJnVkASYrjjIVKee(Cm%T{fhSdiUdzicBD>lETP(rbNO z`ma}|azBC|4f}-W?AY<~N6m3_a)zr7;s!%cK6%wV=b?T#<+MLQ>gRRp?4{4m<{u)k z$yh@rxFfkg7CJK`0wokWPYpUtzibwW>n9W1h~{ui45U9b6chzV85W z&QUCWAf|eZby=<(5}T>-XCvAP9tX$>gKAr_Ve=x^y_0*|pOwJ0JFrPFJv_ zR+E1Uk0-C8>HzR}OK^?>Ofi+)43rTq_maP@Q1+WDaen8(c=;t7=;5|yuy zX@VF)^aGfX{ZhfBY&yB(RVb<#;FD;A#eyn^3mvMjPyuplM!~$`_rX zchEkPbng;D==qbWsfvph-f=JJ&6UqQilx$;a%Y>LI6r3HiJj2u>Y-MsJeBh8g*cvO zMAdqk6+uL0;2N@WeBt`5yDwCWouQl&$CVE2N8)ZM-geRjPrB`fOU9%nHVxd{_N1T7 zcX8ES4Q5;{)a!+RZf9*?TaJR}F9y8Os5}^ZO0OA!`*T6n!tvZb!y`~8;Br}MHnbw~r7-sCmud8<^T?aKo4huXYd z0rQ4qpC`}!ezd196vepW$q(klJruTwx6T$cdbEY;oT6xSb+`JqZh%Ogy5s_<=y{rqKkS!Ms!yO1b$guAki;nYlE>q9y0%7U1 zomP#T%bxU8qLwVprXwXXhzNhT!UM0c1%;8!ur9;MCKrm_etoITfr4F?gCuXpx0~NS z#y_Qhx3oQfgHhy+{zZ$F#Ix+Y5}(QC(=Pp~9UqoM;Z&$A-yP$-89(9*g)8~pMg3N3 zri!C?TQ{G*dsoX>`h%gAU{RHJafsxKC3n>Oha9-xx5+2>;sL0@9lW|Tz@u@T`;e35aB(q zT3OA;*Sk1n-80ip_`7Jj=H{K2v4Q9Ab=HmEAU|_5$moJ4d53shG>PBjf{H5aX>7`A zsnX>LNeLl`=MXjJ-IleBz(wxUp75_BNcBW@7?*zc;*MZAd#r_55SssWrmE;zLw{m-XGrz$IC9(805O_+!a zjX7L3S=0}*$R$g?`!+BB&6cV|X_#zkwET}RZw%2)jubJ?TN6jK^HCocg!+fhpXeo} zZCNi|s5&g>MsZdSJ9dEtUyA&=k4*GyXDFg)8th>Tl;I2ZUs41_M|Rf36d0Fh+8#+v zr!UVvF!C*geTSi#-$QePL93~e7pl$3=!qP!xTwe*Xi7 zMe14A>Ypy92T=RCZMg-C6?VjV8~{0wXO0s4A8GvoKm4hlLPulq1OHem03 zD5CUH38U~KNO1*al)+~R&{shE4hNE!DN$=M{!j+zkpcRv{_k^U*UY{C2}?Nz6`{ds zj;2UI$0b$w4oFbi%2b13vdDfY|80>DNL0J?=8F11Yhw&`a6XG2Dtl%T1GKnY8r1XP zYuu6|*Vq^OsJBpfxb3gd`JVy`ws8$0&DYYp$*xp)6X&1O$$gNn5kKoYe5x~_a;p1e zv8X3{3}T5^dYZiy|CwKUfz<@fcv--UJa(XbS59XV*jt%F(oILzO%+~VZ;y5n=j@x1 z3}+Z`BbYo}1r`C-{7RBF*27e&MERkUT(N_nCzS#be9T>(+nQ+HU8f>})J zTcIVOB#@&Vv|!~ZYL*IqbJX_%>iSI^lFhxpq(~_)5k)(^2#pzolUW+=pyU$o@+bsh z^VG%8iV<48r7#_kSI@Eh{sbN`)Rxr#{c7|**cb9kTj^RwB6zhM=*dx{$!&8u^vdXk zg#%nwK@E~mdMgH(llcdM3TZ-^wW{NW=p&X=0{+l-y_}%QyodQeOeG5(M@FM}>l~Gu zH5lAIHE++vaAq67p0G)LaBcZHwk~?c(-D_IdFHzH zRz+g3(9h3U#nv+-_MNof7j1}n8N29x@4u2D?ka}lLnVqb1BYkj^fdLrImt3zOq&u6 z%bB)aE~Z+WCJSxrrFecMo@m)A;Px(~l$dOZbC%4717GB{Ic zv#ILZVzD7&Nuc{~Wnx;WYeRv3iXFMRb@eMt=|1tqd`{WCS2v?2)|10?;V+KFP%wk* z4Cl|u)xBTQi7SMFPQ~wwZcEG@wse`MD!r2EwI|Qd7lsliRqaR!5U9n26mZ#oHs zi-~L{4bTZ&g2~Te-IyK$EYk$J&T!{;rZdS3GS;HOWhD5VPme~MwPoJ;?Ds%rt!R3;j3Wld;kN#HmpG3Z)f9;;A#RHCs z(!VHO^M#YWoHk{uk&3cc z@MRvvA{T7ap>On!x&#T}uy(5vNEHL3DasD7PD*%h; zruI!jPbkT7WBZ|oAt~))Z2o~{$u1PT>#x_W^b2LQMOH=s-SS{%tfp#YY~u-Mh^4P? z9V3$#vyS(4cb|r@C?YeLQ+hx*+T@Li?=@4VeIe?wt}U|xA*tii5qI%|syB^(*V`G^ z%00Z83#z^r@5i$}lN(i6Q-D>-;)JbNKF{(`x8>$P^Jw1!Fc>2_|7l&_1e|&?z?o&$T2)tV-pwB_Qis0cSEtnna%&7_-^W1*{M4|O|#J(W`>3zM~4Llqw$X+PfkfouQb zRt{(UTjxVa3fpZPb2M#u2WiZE8P(e(aN=cm@hpD)=Q1BARZwro%?lFKJR0jPq9px} zxSP5UDSI`f5vSqP@d6&~Q#NA!;vlTV6@xyVW%mz4Qs3P2Ewh(#7uV(b9K23&*73>D7_>dhs#%7$o}rpjeBlF%9o=n4P}yw;cw!*)G_GF?)8;Cp4*z? zR^@sYDEgcY zlf?|kYGyhU%pF)nE=EGvT=kq=!ySp{KKJ+#yWCC6g_E*#sc<8W0@>rLFq&4szcUnJ zu6w&YR39xrt_q8;=U?j)X(+KFj}^rF5d5n*_od)z;#;T@A;>G?YX1eq(nxOL-NJdT zvxolXx%MWUY0p4}GQu*BX23OOcl$9T&kb8DBbKOy;P*&9v;+$?x>_S}q z8JNQdgf>C2NHtKo-Uy}-+cxFU&Q;gcfP3oo({;&k6Y1dnPRlP7aAK;+k5GTe@0DYj z!HHU4_z3MxOyc4;E@ZBRsh!KujDC;DS z!m={b^QDy*!D$eeP{te*PNpT#gRG-St+Bzs^?C*4R(Bf0%C+oexx_mlEmD9Na|R#& z$w9b$`_^gCfK|*oH!@@H$?yA`z&Y}|bGy=VQ#J2@Q!H;glt>b2_9=TQfEhQ?X<6*N zsJt?i9S2Ir#`Nj6P9Hx2Iz}bP7~R{@@=#e2$*wp_v@6l~>SB5hF)JorOdhLf6(FMt z>^c0H9=P+?6hHX@Vo}!uCPX#tq^g&2SFpcZ=;$gfl$>tKHc=;oim{d_2xCGpg9GgE z6prDx+M1!|!B8;mB>S7gzz_`lBU(YfD3=0-20kw=mi0mm3km1^B|QS>F?&q2{Q45B z1{UR;X<@RjmD>a`U+QWXM8?RO1vgLjX%cEVCr{Gvguso?5SFama3!ZVp3v`UiFWod z?KT&+o^Pv@{0HBNGlx=;zo~=lvR$x%74_bO4nr&cjEK z(yNV2_(us6XozoFu?BEL6LfaT=w3)P{UoMKe;OPMGW#>q%+TPp_bXnybTQfN_(Zn) z=w6X#En<&1d6(8=K7d-yvOt5wqWmHw^q;81q7_wCT2d6QUt`wOOIBBH4tVGaD{tIm zy9*1^lJJm1>B)0zW#yE=k>b`lzp=1?-I7I?9_x?BZx4HSEw^|E_GLoH)|NflZJqlc z`NO!D{-RGL{$FNR7fD#G>u36oU*8qc zRAI;&m%D7ZKD_V+G#sIW=TXo_kORD80Fy1`aTS%?K!l4MjA7@HNwW+yr&N!37;Lsa zxBP4kG@)~_Mt9r9-%alW3B0*_6C8Xo0L9vtEM4eF+^(mHF`Ct?26$d>wo7JR|MSwu zY^qqf2dtCOHiftF4pW2AAF%w6J-7<6sPj$O0w}l7qTuvq+GxqpZX?<|{c!khhr@b` zPhip_c5h-D7u#uHPz>vg1*s|slw_gzgNCIIyxS4N~> z<86WfDSATmmFOEJYF*GhIs5JeAPH#|PJ5C9a?F=}n&gQ^7@cLiELM+6*fxW1aFiX9C zGP1#WhE0~)5SVKH`&zw38cwEF+3%+WlhSWb2pFHaqWZFbzmzBDZ{x^l>9;d5gEvmH zeSpgi(rE2jw1M9TFbnKUo2y`s@52;nVDxD8A3^IHnS$gxZ$hRYhQOUyQr%{$awal4 z{*k>BMpfHBElp8%95(W1Ga2z-Y3id-2gOlU@D0p5O+!>TVFsO=uyQ=f5KfqoZS;+E z;efu_w^DEqbn<)!zEJc@#agxgD!SPF<2{l&ANCyE40=fY+w$#yF1~cU zYx|(!hla#7D{!?uzHlOqXXYf;KTmL!rMw&Vl{lac>eY{s_6Trnw-P1qSCZQlrjv#R zgo%CYD0wu!M3O|~X=3V<;z{)|2}Kl&(jWjwqG;~JO?pTJ7dHZ>j9l^_ncac11?(^1 zA_fG3j>C-ZzHc1-ene=>^a9dZnNgDxqGQGN|P-xHREf& zLe2Dahn-`!V$)8l_8Zta>6s7mD|m(>U0FNq3iedm@UZE@MH^a-yYb>IQAZ5;7U%6& z1*#sLUfxgQQ9rwupM74fe>6Mv(Ospv&iL|#J?K_o!}0vL*?nG5YYC=^=MG*s-l2Xj z$tBE=Tg!eRwIJa5t8dZsQglcDCeIE!vfMPiY?D1Adh-MMar`qt$rM zH{6o0jacNmn!prq?+e#TvTVK5nJd9p{n{0pT`KaDx$Aa(ljJ}~aKZCZ!iidjCvM}J*>vZTHCu&m} zPAvC#cW2$!kAH##>3UXB1S!yb>bb+~32EDK)n1eh2WJxx6ouIR+Qa2;0wN>1=1FWn z>~f^|ebhRw&@IQsfeE)eIkP5m}`f>W) zs(Fy4@T^|GtpMm4bYz9TnQw)D(|K<`k!Ivt>!OY8cSh%f$;00=Dk~~Rdz|%tZX@n= zc4N8Gr;L%>w`hh!2fU!O3u1r>#jJG3+tf*kuwQ)5v@xX){DOrf=*1>yVSnr3`0t)W zvR*Dxnh|wvcsqueRz#R(;y`-s%s^BueG_+0`r2l5%n`k6wi0?G{5}Z*HNI} znju;E6dJS;C1QAj*PF`U`iuK)KU34l0UkgKqbHgF1ZCBzxNXD&NSD@Wj28Dy&?#L( z;}1$^DaPRDvC59^OhZUj*kKyAS0@}+a{k!WB;q;g)xYAdplohNv#H%U-mJyYdmRuc zExD#fs-RPt|8qRz8EKpTP9Ve=W+qBwufQkW;(^tz#~nsUKJQ{%X&={i=WRL$?T{8~ zKG=;wpikOCasH1tKrumd(CfNubxO8u0dyV#+b4I_3WV}{e1Hp8ip^E4@LtCBDV)p- z+>PDE`?=dsNp39t12A9B*di=Wjsy~DY`41k`3_SYMW3T34qHjJO)!_Ac_B*mu)5Vt z?+7@&nHD#4N!`7MJ&yt|vrXb`yPW4Tv51GkH!2ZSSa_3$G#94z5gGNi_x|E~4>VcH z>Yis^d*U-l*G`l+a?eeBF>zO1I9bp<;Xe8F*C@sc5MAeONSckjeNUJ81O6Q++fh{k z%A$c6cj}>3K^$yc8N0i7V|yj>THo4i)leDZl)8h>V+cxyfrRvjUC*<=(mG{a<)Djx zAOjO05mb46L@;tC7&KNI^b+oAm$LH05A!mp47%Rxa^2=sw=ICpC}9o*5_;O>_f+ScS*;@qzi z80Alg5@==!i0Pf78sLb;do-n+Ro0)kNJVDnamc3`-??AAJiNTne(k7lGsl;c-2yQ@ zH1Eml+1pq$k*kj3i3Vc?-(Zj2qdTg zD|8E-52*&GGH3y8Dn{89>_MY$?}JT2Oq?26YZg~Sw?Lx>kbdER4{V-aEAnKVuW8Fo z)j=mq41s`=erF(QY(Ua}x11^_F&Tb{sZPR>2!N-ZYjuKAguwZ@VxfA3#IethyK^?! z(KD1KyHLL#usQMMG){tnZn0B*5JJ_$8Zh~#mO{CKTf>R`#TLgPur|qgp8|fcA9L9y zp+ru%|AWD$C!avorC3>3JPl{VqN7o*Szu1H5t>h$mf=z3PkT-AD{wyRriby3#kc~F zVZ-9^*U|_*aES?8E4QFv=#7ap;6q5k=R93Bdh8fHuByh4guBSfozr|$tW2UoN&*)a zyi>KSC3l+ZuMJW|Gp%7FRB<;GSGyS<<#uMC-`f0A?w#qgd7WFzN%MLQQ_ATt+XBQ- zBmc_#|p5-?7wom_6t- zbPCeL`*{d4FJ|z&W7g9~ohL*gP>u*_?Pnu2iLR=Dp;7L0eYnYS{2;`fZm;u*1CX+C z6WEtgy|27YI`wR^P{7hFU1M*mFhEmGN9jiUIr1~JnX+VZZWHgGx(+OWYys{PJha%V zdBy?LY+S1hEURm-90rJqLmXP?<44kjGxwQT0~Q%Lw63YcnRdC8dD~WSK0~Y_b!?LG z6@rS_v8*dik|)p!h#`yI8yndxgc5xaYhNl11$Ey9Mjy#Y1k><=k0}_V`#l171nJ_9 zzA;9h#0nddEsEi+p~|6XuH;6n~9jHvp19H2O!${GLBFW0hY!HOMqIk7d41 z#UwF(f3|u2mmigx0f;BfwR!<+e(Wvkr=XF0ek6O-le!0;Z+r9$7~5XMvJ!O;FpLLy zmJ(pbOQieiOPe6FsRr1&9Uq{2>Y_oP5_L|JhKTYo{zeC6ezpxV>huw% zl9DNZ6oMcxF>Onp>?xPffFT;3j=rH?_6`v>!ALekqoAPHn&X$T_tnZNUi*Np_dJdT zVki4nloAdMdz@8nWSPG)?XqrR*yycsxnAYHJT^ne#5C1sRt+XiPOuQJ2-7R@_};dG z2+~}6SV93kW-Z&jU|-q>>e>UhdIkWs!$?Q1*6iT+{bv$QqE_uasmU2mNrRghzrF_^ z_{NGkw6l$nC4DZttb}Xi)~aFdIN&mz0cw@Z{LFV0Oji$Y0IgfHIGfl$cL;7ub`ii( z+WM<%Wh}ty!SJ7tQN?!C1S=CQ(~P_?cgvDw41J(EA4_$!SCYM-!bofu(5nz)&P{=Xme z1)@@v2j&Imx70O4OVxCS*DU+$&DlU{>&q>`+|8(xfBh1-isMD|8#;A{6F22*%s3r3 z2gEQ&$!nPSpp>z`DM!3t-FbbkpNoZ&UR2yWz)STg3@>o_FL z-=^<=4dmLe8)D3=hHp9H6vV-uKD8={3eARAWsO4CtH0h~dsLh(wj+adoKVwR2=0ZQ z%QSC*j_?{9HN-i+AJEEvDWD@mhZXuLn_s!fThPMAx7i8}nT!3EsN*0l$1TeLWr9*h zjl0PN=CGto_ww(Mevf!QFYw27Lj*y;sJ9L9K%*6|Cm4PZfG^2c;(wqY1M=)2-ET}u zN}MGno^P2*6Q%}4o8Vu_|Ju2ap>_djNe*BgsFr^zDGjX(;ybMBH!1Hu76in^%|EiG zQurOOOU3dZR!uk1+Xtf&HU8qvhfyn^d~Wr0^+VNdm;)Q;4gM_VEU1q!Hky9DLbbPl zvh}YK7~Uq%nkmCJWZ0A#0&)K6MU6^#N@n$vuq`Nw@3lYUEq80;*)OqrhqcyP*#m@o z$r8f(>bQa*Z6j3-S0#M<^eJ&dig0;u{RYh0_?75VdPj0bkD>-C>LUui=# z6@l?*I6~CpklY(Fs>k4S3FnJU&S7sFk;wuB|+IdHwOAC{c> zuo=K_p3#%32n1;okcuZHs$kspCjuxr+U>*V3|S31OQq%X*qvS7P ze0#tY?aK=!@TpY34!goK>#u9r=(m5?nCKM0NUJQ=HH7G2v&w5xN6ceUOYC~E_OHOl zUxi>bQFoKaavZzf^$>Jm`W>D8yAr&dSmOJ?!R^mtO_e&5T8z8eQQ!-z{q&S`8XP|rp@FToS}eQyN%{)lHU_s z8RMjSY)RL=$n zqnW)Pc?4Y5u$S$}U@s{v4K038UuXLgr!`AYiD#R4{A}gs4yvynBi#2X;kjCC5{*RZ zsrTYRsJa*>morZ^Xn@bIht5`FcCwnoDA7nDuqgmu1Uf3DbU`GUzyR&5y?};xPUwCF zMzPg1*oG7^+9EX&O&Sbfdi)Y*S^BEBuK@TX27B?*-*v*bapO*)LFQuqyp(=>R|oUg z7*J;4QUss1(EA5&!vn9AO+qqH3D~~*NU9@2Vjg(K-@pr0g1lQuGHAP*{P16U?`mRN zFcsal2eFIywGo`WNs5RM;L*S({Apx_{-lwllZBb07hCds7&*gm(m=keEI$#;d7ON4 zt#2?xE5Dl~SrbGpApWj7Xs~B7EstOCb_ljg5hh^fH^tdBNycfbkzs0{mdJNFLAObhrE=n596PwCZ0NhoE;r31NCb zVgjO)MBYS{ar;J2Jn62|;Y5^Xgl}ofiV#w_TH>qKop`{~C^M@;qaNN7`#=kbY#g+z zt*K**zJ!uW3qEngYBRl3)Cc{`Al*>YKr?EC@nzM-0)(h8+9YlKat(rkZu2I)cp+o< za?_dTe`b}aznTNtS}b0RD)Tw_a|!KpR|r{o#JrctrVvP$S(BPLl4 z>dA0{N+S^`qBllgbEH7(KjPMolv8>?gm=7q?UciU>Vx6Z3l<_E;MPpqSEAx4D&KeM zg4Z-KM>{_^uXi5K`uI@K`6!<`$lWqF-mpPl$^}c>Lptxcwh*JFDPHm1kdRKmD!l19 zy_9iEaIdY(J#dE1*3&By_O@>x92sz@KQO}rf*>c_EwM2)DUZ{Suh%eV~ zC$JAczyDI>jhk0&AjUiQY5R6fF}QN8wkPl1RQWDW1oZli)a3WF?+EHKoe;Z)!*Vh* zuF0@x;(aPEIQRW1cLr8|L)i8DFDeCsp4~}|hAW$#BCUH3B5;FJALD1i$>PMW9-tB8 z64fbILRupjWLhfcTE6Lr3?>DbJW6hpO;b_DaCe$H*JeLA*L-)~WFxE0+25j|dj$&#Cs>7D&d z%mpVv#wMh(3daI_Ep!_`WCXIv_Kk zZ9icxrB#*mwvPv2UA^!7IE2p^(-x+=z0kTvAwp`l8p`kERmtDj*mt`Zr&p7#8`uxW zr~zq=nY(@`qF)%?IsPFqF7KHkFv@=iji}NUfu1BXWK<#9~mQ1&N z)>JQd={$kwo%$bVzu3O$0yNTlY~reU^EV`b@AcrX>){eN4RIR7<8Wc%XaaX|Y~*fbBtLGg~Tl-lP=;dUK9PTpk3fVQ8LV>aO&tRwfAKMjv+ds%Lkh#K&)Zq`4y zy9#F1;~TOs{czt#nlm5jpP=D_*j-Ap_SafPAdQ{)gbA=deBf~N9TIzR`BMPSV5T7r zkH^WBF4FC*JhZ8fq9dnl=|C_44gGg^2O>NOY7BOCQ2gHX?1@#ltQ*u;A+rtpN zCp`ypWAssNcM#Xc4Q8Q7wRZyAlv~Wbm+t9zyO(xR%5t{1p4g-!x;5@!fl+j!Y$!DP zQKNusn9)U5?LvWoKjZmDd9n6kQ10}Lh<$kGc)2O8?(*Vv%AmPmbFwPQZG$ifCD%YR z>`I;HESlB}DT!s&&X-eEGt;7)Gv(zn4#zF#Vs}TGN8} zE%{Kj=0uwM7r`_ne}6lr@=4b`Z0gH3EqILvX{lzu`5cx2d?o&f5%U&5nE;2Tz2yOK zEF4GF#bBmvN!9Ev6vNq!)qIm%nR3J7>h*3LfXp!>6K8AemrdfoU{L4NKJ``2Q~Ut< zXa(rbLwan8elO!CP3!j}?=d&vh^HZLc;wi^*F$}C?z;wezY{$V5$}vQ0Ik;9S#JN8 zU||R5U*sIuKla>w79P=lDAF3RU3W?qbPKm0^wD%d!EgTA^&SCnPpET@SXy@EmA-0U z?(%vTd#)X~6qydTWPo~%{LZC$DUhV*)zlc!2w3e`yqU zze29~yqcfNXM#gA8oS9(v24!s=j!}qE27EOV4TsX?Ckt(@-2`NQWnf-XbZ}x=LlRzuAnL-3p3m<`=EDZ<^anH0AP{ z027N+DBcWP{McC5aRhXN{T)F#I)_DV5>NW}$cZy4zma>yMRWfBMBD+$j=|#lY&C_K zvgD+UFFwZ8(DpFvnZ2mVZG)RBkL&`TxsQ*QTd2aFPSwzS7-DYe@7KkLKm5F4_i*0& z(M$9E{QI>$ND5wQ%D5obo7*eEVtlE2yNpvjKKWeupC8lS*-L`gQ$SLxb@p6sR>{-v zk}We26&Q1RHdW!OUuHz23aw**OD5Y@#i*qdIEV9t0&bCEB59Dd<^01;mS7T^WDYk; z>0V?1#E#rwSZ3;4N4;%{U3Q7ypS=ir848_1bzU2_=C!%0d3?HG6*FdzR=(TT$rh+3 zisnXIzH*_@fpQF~7>&yUNrQ_) zRtv_z@AY_bWtfkCh73x=(bNsnPmX};qB>>G3>vB4808{V5p1HNfps$j`=1&pV9Fvk zpD@r%fSV=wZU`94le`4R8TjNRj z?;AfF@Z1VZ#!{(Z1>6j9(Q+g>P6MPxhu~~Mfn0fCulyd|8twC5?Ec^|%LJeWK5ip& zbYZOA??BQr)1=ef*GEZqUn>oi3H|1od(680XYI_Y9?+v0bu-R`K~@I5vaPiQBnEvE z59jS}XxTcr&y9hg%Nul{#cLQAyotN#W_ep1!zn@1-~dGmZaGMWYkU~$vg2FP8~O#` zti=Eg&auyIXj$D5?3S!1{3-@t!QgJuI^*Ma_hECju_e-o0jDuVIEqMC*;6f_`!=_Z zW-=2{UV&U?fq4%WGsu&2hvjDG#NqMI)RBx6koBG|+b8=kg`eoXG(LcoycL%C5wR&O zo(v|r<=M=YOsV&eldgHd?yAq|Dy;G#g={OlO$BxBdmx2TuY`?w;U?R5ZE~@r2{ZvL z-}ac+o6cdG9$`c&Ym$K^uKd3Z9M+_qXFt|U?>R4F0*)lEsJ}S^x8odG58j` z1jp!tPf(epG8}j`8z`C^a^m~X|Ibs!na^P}fCC$qQ~Liaq-A$LI~$>opZ0Sj$Eo_( z1l>6Xtca52@?2gbt*#i8ARMHVold?u zTM?vS^WylksrVd6gsRisuih#Tx98tj0n0HO5KG(9R`fz&OGvlSp00{qN?~RWZ=aIq zdHf6(=Jma#ZQ7XQHAHUUPPR8tyart0yqoqv5l(6ecK}4g4mU0M;DM4DxJ-JxTHRNMY#2jY&brxCyUwU{MEG67`>Kfk#8%ctUjU~_%zHZd{^6K zlW{C@GiGQrFSm*tW)Kf?U|9_fp3OEB3Y}ntNZF z{G-^%_Tzp>kWkh-YM79=qHOHN`P=K3iRTN$WI(oDNzk#1?0~q;XnlX$%kik6Th$nC z_v*BoOU5x(SoRk}9q1EA8S)#smDnIu0vL4O=LYBqwd6=;g*P7l`|v=qE<|hJD;Pb@ zfsnJQq#M#63H84MaF3puP{I!TavK^(jh~&JnJ%3)f(ey#Gz|ebmHuM@Q3xB+hmdn$ z1NE%gzoTvciI~I;ExJcP@As7&{x#0c=*zsuqQxwCBLgL)67e4Y#cFwuTmJZ;sCIV~ zpCza{%*=(yG_>^N=lCFy<61XmwZ>DWDjCXLnJA1+mDPD5aSETL@9PRcVVl(Zlm`b@ zF;JS6Yt${K3fU<4kep?%5r+Xd^K*K79!n|C@&|nyY3W_Mg=ngw~wuYiUjQi1= zU~&h9?v%*;d`!|_mN@^nsi~^OPx&xT){hmC>?r(ffICk(9msX%;DL-E4;G43iffF} zBVP-s%I?8J@|*tudi&i?j{wWXceH=@@YYx*QKhx!6%W^%bUq29+8wpS9zW$vBOVL+YRo~^Yu}++V0WpO8D>aSKw+E5} zj+>Qd7!7B;^JkEYvx7pL({GiZ(~$N5rPrL7Q+S(FoWA3XYR{gcS-N{y2p96S zvfy|e2s|!GYzAdhH%%qh9+efp&TUYUBacb8ju?Y%ZZnndc5O~nEoyjGLq9{t7ZET` z0^?MJ1HvTFTxl&5VpI93s~r%Qp)PqYI{B{k6QN?riqEv0I%;UN8 zb4tH{xXFyN7%%cd$AFZx$@67S4p>w(-Q?$!qe}*-tU@k^Zmi?6-D~MK$yJ;3laX_F zy$4$VM>69rCrjA!NEUn|ENVmcZ(d6O3^S|V3#06Y%nMN}pAhI{nOPuGwq&g&$p5Xb zr_7i^R-OH)ZA63+Kr>3w89b*m_nR8j?^DtyoW{uZ{m2HK0u}f|81R25hbv86P#kJt6HZ<)IZJg zPHd@t2ozV1!f013%+AJzgTs(2e z;0gy)kO!t1g39XR@;f1qHPiiO{B2t!U@DekB)V-(;?68k?I|L}a(++y;koO-N&efu z(9Q!*=iSZ&jZ&6`VX-+r>%r_whL*+T&AMg>68MBDz>7`mM?0C@2>H(kbOV%#r;(gU zZvj^ANSgj(kTfpCY@Cp+5$@pua>JX!OY-QGq`jGF&4WG!M3yC**kE2Io1HDSSb26y zS>F_(Urj@cNmg~m<-7XBf1)x$Y4PFZcaQC9r)`hz;|acqOuLa6AEuR57nAL^u%qpJ zbs6aI%hB>{SXihgnM7b;V<^XYP9K4fb5ES(x;|E0+f}|x`}`J=vmKDInzEWItnJ~M zxSGEP{%s95ZYJb)S`%@lo_|Gt`|JvjKj9o_GRFJ(Ld?FulpNC9r}pEUuD!>zE7Er& zZZy^Gy$96y;LmTm+s5c!>j0MiV+OV;Gftd|#?9!IH}3tu)pEEDI3&u5GM~gDHai{8 zdH@PZ676(?q3R=XeAjaP`uh=iKG>ZlZ)}YmD^M{Sm#6(pzxg>`NhEnOinGCx$ ze3nb{@$BZ<(G|;AZ|&mWXlnNL(J2S_GfHbhqu8`>W*cs$mk->&(X=s3qZ)5R6cP)* z^{7(`VY@59WIC#OZ)|}0fB#tB8AApcee(Q)V3T_bvKTQ(L&gH;y0c5AIeOLn=-V9G z=l}WNF5@D!sr~2^Q#S(iF+A#^Uqy7Gs1Bm*l?^AfAwtca9Ve1*X*-PV;lE?|yiXEq zGbwc)JS^en$md_!>uH(+A3vt$oNgkr^BSp26z?JXPCQ99>qZW13brG&J=1N}+%Kgs zSnn1-Gv%ZM7jrkoDpt|wV4B~v zsp3a175I;2r&M@{MxSi{WIVWnyMSZAU&h~jVflVTaUmG~#ZR6I`L-CHKsl7<8G)5F zS8!C-f<*RN`S?o4dEp_E`9dJbXhqz7(XWk^vFQ#ytU1z_?uf`QqWYP%K#mN1aq-ZP(yF^R zA&z?lgZvUj3j~V}@|F3HQZSY%waV@MfX3CF&+1K>I-je-Bu>&50+u zpHw+?z0%WBXQcsw_i5> znHi7k1l}T8*Tn{nSTvA5k>=m$k{WIJ#pEroBTuVRN);m55{XD(q(hL+xJIw#qE4O? zSooLym?5}`3GyfS6ufu38fRc?XkHdt-k9DR4o`vRosTGHViTQ8Bz!gF zCYmepCce845!YMqNUt1ng)jH)%M)s!f^{=5YItlm^o0`Q*ho2YZfcRogQfvD))e=6 zl%o^&A8Hq$P3pS)oZi2DwXUQ!b3N1IZ5e*B2#)f=TUH|kCpy`_K$PyYl7N5L3*@S@ zi=*(%p@JlIp|!g-4cN(H({5tzTOMi1`J9Q*v0?uuZhpo@;tTi7lJ(EC(Mha0k@w0q z(rQz88m0;cB_GiPkulVC6MI$FAXij!@onzklHmz9>a|!f@*ot~={*!JpQtbF@K3Zw zC|d^-5uXuhb^edT-5X7HzmbtpB7@V&Z5v ziiQ@h5};AH0Nl78QsdO05TfCU+94~Rf*Ojy{G1)n-$0c94{|oNJF2-?Nnwza@zUZ= z;NVlTArXLSuI(eVZ-5}nX}*w`z$k+iADzaFzW0|`{vTs+9TjE!y^D*$P(yd4NJ)ou zqae~D-CaY;&^?5-N_R?0N=i4<9a2L`4h=)cZ+u@rzw`O7bJkhszgev3x%1lBzV^*? z?`<=?w4IuBj%w>Qs3b?O`QMC~+4z?c+YpTSJUx)J8v)C_#}ADjv?nz`tVYtBgVPv_ zV7DkLIG4pd1q;Anx9Ug@P4}0lZwS|=SrX{p>U0j{*r~Vw3>s`+x%iwQL4 z7}{|0^$Fz=_E69-NoP1af17IZADqY+(=q2Ip4b(@mY@BQ+PLf~fq!Ey_eTE_+x6m< zPG`YMuofl)H5nOO6G@bMH=>QGC1 z1bt#v{x^O8_3`Cx9`}(iWd8L9&cC~ext`86nUiX()>z)FzrIO)Tfu1DP~Fz?(l0fc zV}80$O~*Lt;`UbtIwpc$&N2zNlYeL&wGJ-ofFyr&ev(7_>xFb%2>RHJPCEDmPOBv`JQnkNMi(t5D)L?Fv6p#=7H9|?bJA!Htv)teKE5C zKX_Lg8NQh;eVG~WqetJ{qGtR|INYnMhho^c5Y_F{Nur0RXgGW-9*z+_nw{}%@}`N7 zxdZ-&yoAOs`$UTng<9$*>dX;vmBsCzcBZ<{S-4Y^_6Aj|4to|GvXHab@Ag-IosgmQ zbLd7OgynXX`-*990(}C)=syLBlo`x#iR*O^dnY2JKf04ae|7>-ULH9jH%acQK_*+G zR}u0Jp-ubQjF5UWg`59xT9O2LwHQ?)w{B}{&8_#Zr);7lJ0zt(t(5u1JCpK!WuCBd z?hS%G^GVqtrXNV9hA!^J{*{Rsn87pe`vTGXK61aZ%Arr7`>1I~ZvU1YHbbVm7Jag~ zMlsx6w+D(s^uI!<;o8DJy>ZDdf`=6``OK8Mb;uxauNyDC){Y1rBOknXMHxNDH6 zveB5Q6R`Zx>Z?qA(#6UdtNk@zVEE&|)BvLQ*(W7hT=b=^1Dm$NISGuo-|b?a%OE8} zOuUJ(Gq`cRl~XaW6$byyN-zSr2>vMBhpBDphJmSEDSetZjL`oB@lHn)?u&bc@uN?Y zel6FAVNJbxV;re{Lmcsb^p8M--5R=`=s~x?w<#pugm&2~D)=SG_4K%mWQ4VEtOmvAwqKgw~Jo`_o`WK|7wa2QbeX}ge8ACDC(x&h^=<*2R$gfAa^|)*7!fIJ$B!yzb3<2>X+xTGb#q)UQ?MMxZ!T_2t_3?aQFI zxqu&q>{{q0yGiJk65*^v<*T7V$`n}RLTx!dg6ymx-={jNvu^-{WyzOyAbXx6@_)wS zGLNz&=8^cU79>?b|P3I6??d1wej&m8Luases0ObXmufaitBMlA?fz@|cj>(|eq@NYJB7oOsy z?3xiLe?^3Yg#7~9`F#2TZH!*H%QiojB&govZqYo)`J9h)*&&w;Zf}a`~Cw$(^y&!jbC8bJ8hh7RL z@6gz}ktj8{UvmpC5l4)gga}x@oCV?<{}1ZCndYs@YGr1ew15HEFw*Mxxu-+ThjWKc z!g<0gp0~(a?1R#zJ9n@DsI{VOA?&f%WBmh{!Pjr7Z-HSr#efk#TdbZpZq=j&#(wa` z`Jy7NY;2W#GnSUKW4-d`i>d8^YgV)ls)67Dk&J*p(V7oDJTmJ(SFMI5HrijN-xlgK zP^s7igAKxhO&)Xe*G4^dmwndnF+SfUc+x65|L{tYa60nYQg<7#lB|^=`l-C~y$3y{ zSgc);p%&iPZ@L+aW-^h~Y!EayX`Ssiio_K@2>6$o%63)=Up8F^_bH_d@svrvu9T8u zD^_d*?+oq*=TH|Y8!!S1Ua925yi(Vf;%YQveexw1+A@w49B{LLbi9`RfNAZ2 zY3;e$K-(Qby4)Feev&P|@g&9E62gHFh)M6SywOHP(DQA1lx{mq*cAMZdq1m!zffhk zmB#Qixi-;xnvL1)v|kyWcyAB*99-UCiiZ9uF?n72o8ZVm(Mz^N)O4G&(t>$2M-C}5 zsbtjSvZ8}$&a`w*5Mz*@^c^@7ywZCRwF;v}*?eLXu>_@+xEE=;UE4mM^~vrWYQHAS z^1VhD^gN~3{)H=kLx|QP%1k+r@lWJzSX809P(sN!j&M@%NfRtk6XhFl40R3hYM##}6OBt|Gk{Tj3 zx(IrjLNV{YbVt;3IkD}0bD%Pg^njn`eSPWtd)H&_A>T=Ou=VUgE)S>9NvE#T;jf3Q z&-6hmF*3i;?%wm^yiSz&@H6xR;k{PxWP)dz=?%7i4Nm1i*9%hW!%VZoo4zkmg6T<3 zOVXmWU#&YWJbAlty3xN1=M4znHoC>j^4&!yYgnZq(@%AZ0h0?u@98QDDI!Z>RpjG__Q!7CCwktyF>z}z038EE{y>34KP2xs4}ZO3B(C;ht9Pce+Wh};`^cHcEZ>&ii1yj%6W_Y*u$&gcj>l?y0F;P zqZY)ui90nnMpJZgNMG+)=Yu#W(`p?GONcgJSs7ng{c|^|aU7C5VUC=-5iY55W^?Sd zL>4KjsziNY05Wc|`I$1*AVC7FEoQJ#`|{adqFQtf3-!`a8{dwkE#_XlE26|iE1rt@b3iYY)1ZER_plwG?e%)I;)Na z4@b_2d4JX_^36=CpX7IC+V?t;(F>uWg441aL*;RAu!DqOmjBIr&xdP)+I|*7Vt5YS zN99k!tKXb5A|!oG=Qf(oo%Mn}k{n>@TF&?Fx5wts3~9a{vQT#Ua&?7l{~kzQOSLwd zp(#=fk#dftMltrfZ@^*apfzu42azw8{`P`#%wMN)EbCvmJCK7Lr{H<6r~DqV?i|g; zOMRCCk7$3JLr8+5lMT);4VCu1e^w3+Ajyi}c035fVSh=>ZbZ9m+p_Y#(V!)ks*ci_ z79v-&RIac_QUhe4g1rwn4nOT}Ah+}oXEeIE05QQI_|?J_50?%>svJg{u%f*ja16Yh zUl;Zbhg@_>aXaeqp1YOqO{GoF)6ee@L44~3tLT3%<-9j$v zW`2+T_0P<$?{K--Oeo6xj01q=5Qjsw}^09nQD2?&?P|!ewsT?lGd@TKuVD ztu;!AmfH_MyfGHOzF~S2sFG>Cj_dhI%dlc}LM$Fj& z@$6^xU_&*f$i>dJG(t9w{%lc(f6ea?QT%GC1Y8^i{CP}GZ4RH13tc&orDj{k92ipf zRhHsyUB*9z;ORVegVNF; zU-_kz`D!%xHK%6lIT&BrCaP~F)pI<5WoKF{WH3Lj&f8cOR95J0dSB=)bo7QUITmWP60d9!xmZah~PaO+t8sTOZA_PJjH3v1urT)A2PswEol`rw!Pn?baMDp zh_6he>^Rt_l9X#KC zt!G^O{>)p&PL9H=IN?Pyt?awaWMCp+0l|VEg<={y2u&%3Uz#vyL~_!TnGu@|tz7=T zU+UX~dG31&O(^$x>6nepj88(Y5q+*@CH{CGQ)$1m&5Ai$?T$E+o*O0RNHccOmLZx< zO$8)JH#um$me=+PUpd(mC1JQ69MVUvMtzfaBe`+Ws@9sLCRD=2$0kz;!+|e?AvX*K z6{?jWQW=o2OzS#kWAt=WJR)f*0I{jIP%s(M2DezdJU60R*f`Nl848Ep1P+I-E+Dzy zF|NOYp3g=f30px}Cg0sK$3<7$Mtx;O_0|#b(Z`}Dz2*N*G`R4CWZ=Rvj6rcBG< zbiZOlf+q0*?|`xOT!QL3e8CKWx0;l|1ZmeCDlVD@6XzGi=FTtb`kt_aNPV$D8T|ZQ zwZ2R87Xh-R0x%(rS*P%+6&5fBQM-V@Kk!#e6h2?N>9_-9FasvUGCr1wtvv7-8inaN z5fUN9lK-~|`lKY<-jRR2;%$XYPGbvh#$s_@ZXam1r4PhSmedhCve4Pf&V^UsBRSuM zJk5EC0%swshXJt_nkDlmE2N(@=cOfigy#9sh_7P$W)TDdk+Tbw$?n*|IE3gdPt3?m z<*@w|w4ap_7J~CkitSQZe4%RjIAnApdRi2}G^n`*LgcVzUhBW026$D0f@=%^jRRLa(6l;R8EAtp?RyV0$1aa-T z6fwn}j>_bKb)5u@q*yh|*D_>#$?z6CuE~I$Jtm%NH9T&io6ff1aJqE@mXXx=mJ>ep2~{)9Y;VeRgn&Fa#;D{@f(y~1SpD0Ci!e*X9t z3Y>2D+I@T|Lf|)Aea4!)4P&D2SgSTVfZTDABGYr}psN%g;Nm>2-^H58q&sD|6YUbSWjQ zDP*rxyL9_$ZDj%0$lMv%muyJ=MawMpny>HI+B25cS6lfz_oyizj^ASQhEa8JTeBdQ z+a?b}DI6iN$NoAZYqxb zu;7jl%AfmC6*}^SH`-I2r z=ZMdW6-k@@5s%claD7C>kKEC3t%hz@$GcrFnH*aFHqX)qZ zkQ+~j(MoOL;{hd<^yUaEsK3)5G4~;)=l1cnAIyM=BOu#hkHUle38DsG2;(w9D!&3b2fvNKLjs#3tu#E~`E7EC5NF{QvQPIQm%oTwiFy*@aJtlp*)b@j{A2D-T_-jjwkFGim@6j9^#Ag?)c*}WJ zKPJJzG<2t&-EdewN3S<$-+`gx5%-b$=de8sHsfF~Qq$BZt{69rS?lByvX9s2c4Yzh zZA_Wa73Kib{vc;5IfKJ-+EJDxT0e6a}9XBHimK{@B zyW(|#*5Rcy_QQ&L+k*mqdyRkeP3D{}o`EViI^tK%jrdk31SH%rgvTa;GTL2xW_z2k zV#~#W8xmEO&DrB8NPBzg>)TrObZ9*rCE1M{Gudq6>vC8}@4Q5^y73x!3)y2rGhVW7 zYt+eDbCeGda#l5rYFJJ4GO99Jr*i%>*^LrNy7sMF6a_{ZP4RM)qf>&A^B@SU>NU-f zpbRz>dZ_|8es{3L59Byhgi#KeHD6h8F{JoEOZr-G9G!PbI?<$av5vWf)7Fl1*-CSO zTWA<|D|!1auSMY`@pPxrFw#$^|5Vzb@S&hhl=RK^`kNGwXL|;FGW!DFuO^*EJlRJ3 zYinF?w^wi6mN+}X9EXAZsJCDC8lOOo1O5E*lbUmosiQibmC^PeFLcmm6R{U0!Y6*D zcEchh!plz{e^*~b*J=i;XXfx;2_fsM;+@lCjqCC|^4Y`!%KQ;&7yOXn+1sXE`GM|j zKYW9J-qKndeyrT?4!E7YK7PyuBi#i+d(LY(qz#!*neW@pz14mllaC{)#0Jt? zW>Z2o)6A9=>)<^xkzo5aq!tAUQA;-DKW*$IV|H{vAszn`nAcd{8b)90CR49l{nt7O=ua4Kxl4Pnwt3S?3+Eh_0nhc-c*1nwErsbFo+%^HNTGH zI`DN^yl(B^xUF`^zZeLeua9z}B)y;tKS)F0HciFiIuNy^ZTrH}XRbke`D1#JHj{z9l~@QS1&P%thMF-J61SApSNK<;E75=c*Z3 zOgLAr0$%4Y0nRLc9Mqt!YE8Z%5BDOhI+zEjxr_4U9IRF6T^1-*mCd^ch{iTLB9`&R z0{=uY?G)wu4faWk?ML{lzV9(TF1^~KP#b+ST2;8Wsh5G{_|&|=pFt&3+oU(#M|_{nqy}vwJL8(q-GLjAHjzDSE|PXX-d!*pOK=|rn58}j z4M{_PphUjpFvdJ!Vh@hp*pj+#{G}@HDx46Li3E~W4Cl|ck{sAc9d|?l41Q+zHIB9| z=XS0q=hi4b_^GSPLogJbh6>_Br0OCM8d?`iq82wyzz0wA%&I9?J`WIXD{|B5c23_C zu3f@}HjwdS>j5((j;y7lo=xS6A$>AMG8&5ZK?ioxFahE;oDt_= z-k@MGU+<}OLRe+35xqe>i{m#8gtUe^2%<~@RKjnH0Wwxyp~#4`h!D>3n-q`Tz@arB z-+*5oLu>1uQ>#zeK1AqV5R@IMmmTr2A#e-KM8yV7v9gbacsGt(%2WaCAJrAx-JT@% z!guOc0&Sf!N>>8O*Zusazd{Pv+@t5b8&?<_M?>$eJ1ilp$km5II%o*`01I^74OQLO zf=(ivP@u+qo{aUj{08rA>8-jg=W*d-V>>9SI9dnRWP0Wy8JT33WX; zu!-&e#=_A+B|w`Nor*ltHyXz1>6>n3Of!B;m2E#9XQbhj zS7#-qtT>R>I|*XmmI_@s{S!EmPy-}ewG7uDGoW}6(Ce7h$E+#5hl32jar+58z^@(Y zuJXtrvI&Uz`W?Pz-0nku2W#LY9C|!kOzwK@YY7m85uChSF*jk`6LLa6PpJV2kLlnn z7jkyb5s$m<5$wF}bQx4HZuCXfRMZC$Ya~l=%PZs}7uIDrQ4KO}+zx(K<+qK$ZSM^ zSEZ=$3$>yuZy46`=pwL*g4gw37ObBroCF&J+50*1?PB1n-a{BsIlWh*;{+Mi`N z`_vZHZNzDy=H%W`3$|87RZsI|$taAW9G14C%|i3)eVyU#Dk6$FL$+y)VRaiWf1&CA zyCLTv4=KIl&g-r=fuJ%0M~vA}aI|48h|A3#Wm5d_BI4CHlzL|)l1H5PYL)JVgCn>TeZ2qB7 z$9f8bHFuyOab8QY|6; zWkPW=l(0EY0kezf|ABg8{;h;H*J9zF4KJ!Or!h8zhBo;TQ)QUCX8O>aq`jiO82D9f zk8Am{-$}uyPS~Wg*C3{i5cwq`%rOj?%J_V;4OW}w4{8Y0nK<|M|0-AnsdyZ z8XMkogDM*DQ7GLs_)vIG*+PdQO)z$LvOrtpN#nS9mAdi@K;XNSSvw^3ss2ePg&zhh}OQyd}KD7ZZFWT+NYa zA9sW(DiFpor7hKvYVUkMqsk50>i}0NHV@573sq+?M643JJw%L-lP`bNFD#kff`3n-u2Dd3VFu<5Y|DC=C;KeX=&O*PueDjwoMt zzV11z#w9YCQ^@Tk(c+44AHass@|7d*qHrr_gA$Aa%-=1XZhp(0Ihi(oGbJFcVNk>} zs|0@4dm2-uX-eG~?G;m{HldokRT=D$c%WOoyw zuCv7Rob!jNjmEtEGK=Kz$L+L;6HL6Db<=Z+#)914M7hiF_eWHtD-X8RZ>$s-KUOv( ze!JYOmsPSm=KCrqdL9IV-BRl0i^VRyFd|RiW^Ot3ouR;b;Dg-l?6Ye8$$O>2Jj40k zL))O>@7L+Mz(o~sqPcM5*MmgU^kUy@1I40Cowki(rFDamKOj^O3lOW@7sD83rA8-B z!Vam4^69>e^y&1_zYIAdy$n36JB+PdUfb6SYg;T$*LhGLsyJ>XSzcWn5ssRxNplZ& zdbNxG206(X16kW4+<&N^T(61vhP|_+(c+tGd+(M1uGKWjPO~TBmQxT77QfU{?_v&V zR^#%vVGxqrH=V;sCY`uY{hY-+)*RSV`quug@^NaFCP$s56_3;yOTUkczN zbCvRDW_U`wr!>n?Phr2YrFLnJ)v0&5;Aigl79RZ}osmMl< zn5HidcUn5wvntwi2T;~_Tr{LL5HE}_2HWcASgg3)$ivJQ7uTCW+r}7Q8-CY=QxBk^ ztyR{-(iJ|3$0h1<6qv)S z$CoF1qR6nmdQx+!nx4oKT50@E+0@;^xJxJhP6+7mvr6}B+v^0Oon&IQ1M z8a0U_Ds{3EDsj{a_*s&n@2lGW50!*lV1pKFVYVfl1LtKNtO}Bs{jQc#)v>4|IlPWF zW=Om5iaUfCITxN%lTd=)@<8k@dQ{z{#-olV?-?m}1kbY?X@p}8kzxR&fq?rWseAva zTtu--)k>(QuLLp2E;ka#0k?8X)|@5KUq6pfpXHKLs?+B zZ;gQdQ?cXZ5%n0BO5j|cR=CnKs1?o8ninM`sDe>POnV@dC~>iV(<>xLbQaevZ7 z+2~7p-W6Xek(DBL{Cl_N+^(7}4Zu1T8tJCnqrf9~5!$TCjm@$19dop&3=$-BRcE{Tg{J%f@>oxj$)w%9DFO7>vo zQ3LV`fKR^awB2`sRCy>NI7ZV9o~NW61BU##Dy}Wgi)LKReOS$&7w^Z(hHCrnw1`X! zoqC^01%W7q1%VX2RgNt-`fH)k*9KQ)lE+VrYG)H-H}%#~6^Gm@044RF+{7EG^Oy~4 z-#v@wwHDtrb068+R6C>9_V_t8*pSwy#vSN}kg_M)R$HJYa4aJ;6l-}-)a!lMXsI`- zCDh|wLMl8sblF=Dxpzge_Hlq6$cVHlihVn1#zHlM&@HVOuFEHl*W ziUuJ5W#hKjPq5_wjiH&Fc1oU%ZqTKbpsWV&>wt{0s2U8e?P-}rZO$b^Hsp@1U!<7Q zH(%`K-=4Y=$vrS|=yoXcI8+wpxQc{n2*X{tQCEfL`GK(sP`g7%;OMl9NDlc07~#U3 zP2!5PXYu{&Oh^IU)T;Gv4L15r`$djdZM=sn+C;5KZRZp_k=s@c@8=@VV_7SLlodRr zw>O(}yHCZK-!C2YZ>F3pWJ`i6zJ%%lnDIHdVK#K)_eOf8hDU7{H+dzBzOomL_OQEtKboT_XaRcFMee9y`%3;vKfl&ok zLaJgPbdI=6X{UOcSh)ite)tX77?rfGMFlX|Gc7b8>lx5C)ZjH#9}BZV^rB&Fr>mhl zG($Kk0b=L+&M|K(n?4R2Vs(=kAwy2)f&FfJG=WFA8!meSP6G8udS~|>2Be|el8;_K zIkYs3NS=ylW~nQl%jcbEc}E6BZ5OiePFN*v7QNp|%TCOkKBp*}_xi~9?OIl6FRb%! z`*W9-Y^jE82><76 z{6YWqTHAva9pYV*T#d0uiUp6o#G0_r7^0r%ji1s;k4zre_Gx~REYyRr6fX8o&ng8k zpw;%W5#k^0i1x)xII~rVYf}Zi#G+MWyFc1z%_J)oe{s7zpL@d-7r_N_((HR@?G7Hl za9UjRTZ=CA6@O1UV)88WG4&;4Vm5Pih#U0w0Z*GN?n}mN##(`v)J-$q-4=u?uX#PU zc|>)Sm$94<4TG2tFdH>A7@?8okL0ASlg5SI5sRVOFL?3w)_r4+Xt~s0%J-}I7=AMI z23^UgbV8LQ5=ZwBKi)4Q$J(8)7Yf@0|h+N!}?$4jmYeIftrdA_eTzrBJkma$siGB~&L;5-GwGtx+RuW##^e)XI?4`!jnZIV$dMwVNdp09fHEb!DXc!F4WIkchisToX=~{}xd#3rkFysi|TP199*mZ_?fe;hIsaWR>kce*Z=9<^0Be=0azl~zTy@s zL--9Xq4k3W7a?N#%fEec>idEBKalFr7nhj3mq%{V+W~x7m6L!_`d<+?(qONF=N{3O z#V$n?USDy$@UNR>Wi~#3N1*(BZ$GC5fz@#wmJWI3h{>{M+Xiu3XEx;C?bGmT$#$-i z{)V|3NmQ>{^(1~Oxt9ARoHgow`pcWOIHxd5e@{Y+F3PngSOBmn%LgfPF~KI&yy6YE z1AiWIIZqJ6PLEH9aF>pLb>3?W3|zW!-c)8kJoR_m7&*>=ydl3kmXiqGHuhAz zOVhC)b!F#=Ag+fnh%`Qa!KQ7$>$uONUJ3xm`_f+KWott*B$_wfuXe|rAd~?Y3Z5rl zwQ~w;A+s5qdJcdZ2;`l+^CJVLxc_y~tr(((J;rPC!#52pF0^;+qAY@0PoC7o$-kD; zyl7ra(hoqDcczIs;g_~mHB$gO_`ZSA5Jw}bPj z1=sP%J&ot}%#`Y6ThyOX`dg`{BsWR3r$`;Pn+P&9{TXZL%n{8Tbl32!Djz$x$WN*tTb6p(8jcTYV%FE4^0;k5BD5R`}3-e@l0w4}t-9J{PSsg!uwRsQ@!`1_>q^HlSz zmh$x9w~eF+tw;Ecu&i*zsBc$EJ;a}IwqH=%7|!b2kGht-i8GR%boCDl29LGb_IYb{ z;CZ6ZH!J>Z+UNH~p(^Lbn6G!wH0eUIR9Fgd{5zv(#n8-veM=W={pxNR*$g#UbOZj2 z|Ma%(_n0pkb{y+N6RaNqr@Om>L#17w4x2Qo)caz899?RhoP5RWN@;6Yqhy3M;bFh{wt;SBv|N60OG*WD z`+NOisNyl@MJK{6c5VzWXK=NFgU7+*>-L84?TGQ=>=ER4gKxqfsDr0YQFPL zgOik3(V^alB>NvDbn!S;n%^P+JN6eYkIj!)yX92FCx~dmpe@(@2)~p2%K8UMI^u?3 z&KCsO2g2fv{a5|Ra1Ps>(GKn@;a$ob*1-Sjr-v_cj;h=SP=9AdG$Ew;H*d#mGbm2I(#VD{p_hT6#mp?zK0 zWCH+i8yBvh@sks+yU6igp@@3#kcoO9yYPj+I-X7S$Sspek{Q}JL;@|=ht>}r&TNM# zOaTkS7(e&NJm1enPFR!wf|3b*ygU8&^EcRaH{4Y6&8rpZuY zOW|c7dHwsHrik@(?as93RqM!SLGvi~v!IYf``OOZ#BZj%r-@ZI13YuWHycd7c<;gI ztDn7m&N89)byj}FWk%Yeeyub~m3n9j+GRm+hP1kZdxuO`f*H7c4yEUWg?VlIsp7=z zE5BBqi0qNYqy7+b@>_Q&IP$VnjGH(CMnPT1UK{m`B@rZ2NTN*k^56M(9hGDjz}fd;^km9KAjox{kw!yd_a~EI{wr~Tf1qjkMODR;g5oy)itKoCuyB>6TriO^C~V# zFGvBg<1L6BRs`yjoHLYCx3ASLlh0BvK3Zh^Hu2O6B;q6Bba#-QrPtuXTh`S+PRgnO z?CBsM@Mn{2(j>-97Dzm1MJoK+MI56lEGzHVmxZ+-O6na-!c6x}sbM)}#E#h-+u!rW zwX4p(^4<`z`Xu0c(ew{K&`XL}iY;gkp-@XGxGfbbX5u8q7uub%?~VnqxHlSv*y#kD zg%}6iW(*;1yHq?oySVV7MJ;t5jaRV&ph^lD*&8?N|JM*;~#1ki_eqa&HE*d znu8t}Z>j337|1t&2WBi(+#1v?ebZghgMyOmNa0r#{q`?zY(OWI#9ZdHRG-D|h6TyQ zUHO32{F7=%i4S)waPuni7wWb+sPG2AJGd)VnyAdyLFVP7iTy#?i1d(l1u-qEN+!QX z0WPIDCIFWj2N?|;oS?mF3&5|dY#FNy^w&05oQLI;#40$B>U%)FOq1#5w@a*POSb)r z#W8?~$Muh3iTH zvo)!DK*5i((}!P)yB9(Zc;~`x{@dSc#T1bI3@JUS+g%75ZKogw68z;Na z1)mND^>2&fJk+Qj4X7^>ySU%Oi^u?3w06Rj6`W?yw6=V?5?+5GY%`);v1tv!!WJeM zzuu4fNfaK>n(X{&8RQ0;T_SaTiV%1R4{{HEwHQtv|Sz^WX_X^xAA^rpeQSx zP(>UK@qHa?ze-l{<;vrecSFs8ntVSuY%*PHFC}DGo%)ly>0wOE_b&L^8A+Ysp#_t; z(?;B3qvf|him@om3*)Vd_}O#O>+`7NW?b>$(^#?9oePp`Jg{mT{AXwXYHxA?$X)l) z^L81UdPEjxx{lrZHZn2U(3P9@>F(hhHZ4%e&aZ8M>?A~SYT~Qa%K*HoiB8J+Qw|=8 z%j&Gi5jS*`gdYOzet{90XkoFl@s`oA82U$gTKqhfpZLJp0{HI#I6<4MfhgvTkOtM{ z7Q8>X6kT(4e$|69e9tJYJE3h?BEHv~+EzG(0DcHKxh1`m)X0}Qs{u~o`_`(8HC-x| z%j!7t(HsAz)e^FY=v8!T9#dvi#t278*BcSLPhFz85z$&w7WP&nX(!l?HmCf3w` zN&Wuow>CV$^o;C?u<4+u)aEZlfPLx+K;%Khu7aOZ9#7CdB!rS{x*a362qL}HMpWNL zV`j1X6uo*&>&LMYkN-mE3MLT1ne=}0$4{B`PTm<+T_c>$q*A-#XvN~7($jav;^rLQ z(whaKrdf8x4$3K~RF`Uk0Wx}q3PgNw^h~Q7Z2X6)5-wk?+?c=Ay&ssW9#yn*HfL+ccUi15o5}k zttu!k`J$MfUuOH#j)>QGpj%VjFHx9DU4vofh@Z_>IhimBO_ zWUYCWy7@tAr$iI>lJ0~P;!5OawgvOAQ+SY6_5U1EAXYHoe!)p<=e-PVz@7XHD`zOP zaCHD$GtS;wH)8#cSFp7|&b9kJ2rY40@a^xvXG{VYeE3{+#(k2y_pY02XE@7&<26MZ z=4~s7u~IyqRga$W8>2Z`YpAh!Dt$W`qtXrQm9ci#WY>PwuXersZ?mj*nKy$5;j@*} z$PVNkX{C6u=2NzoNpi+^3kLx4ew-e17K*n7J6}u=tIFf~C{KP?Sj-5W#?T*--7KWj z!8RnUtO3}BDa#(H>?Cy#6P{MJjc|NoZ?2AD9 zG#I-->?k6CwKN5c+)VlKkkw}9fba%JI|k^e%OjMur1 z$a=hC>2iuUJ$<8T^@$6Z3ndic?6yvOeIQpu1M?&oqI^h6DKY$mK%aXh&jWaa!~IEI zw)A%NaL#Q%J&WZ<2p3}cddB=wn^|A3e)WlVHNLy%_4FK}I5Fzu2{DbP!yg4wY_Y-B z@@_sCn!eq?>p*0l)nHT8Dt{|`-#BSmI^C3cKm-{0tTLqThoh1DE?LP&RRny!8=&aG z@s11}Vk?q6O@E_QE^vd`df!ha8(Bva5@0U!%Ty7b*_I8kgnc)Jani4rb4oTv!I`!HskZ=l-p6i?}$Jg0GUcgw8+V`YO)oT=x>T+!rptJ*T-zdBM*=&`*@MFho^>UhBQ?_ zzN42snCjTr*HdXPUp3e8i1nEu;DfvvB9gTRNQBjuJ0q8ihtn_InK0~{uTxq(1yVL8 zbsJ0EYO*~(z~_3uVWxLB1zrZu0Hm&g@rpeDJT}T`a+uo~Z~>3ycqup|h#b}ay~`91 z4@4*}Hnvzp1ptX{)`nh>l9^{wR+!s8oY2kIte~aT3A-&tMKcA9fT+bTfN7n7JW@q$ z*&~5SNmVxRfd+RKdvmj@r3GW(u;#6zTtlyKB}DnADuf4;M$SH}shz+4Gl5ICk8FBD zjKQ!?Nf}2mMd&Qjd>vO?57a;py&vZC=0WUkp9b+VcII)@FPMwy8v+5xA|w-DouQBZ z&o(Rv76W(Vnk|>q)A&dHs4UC8e@md0@xcV=7f(gFz&nkk?0PQ5YzEcTBpmGob=a}f zqNvoY-nJvUH-m?}CX@b>rw?-3GTG$qfhv!8p{eU(%57C4K8W==+v2WEOQ}~JB-AMR zRS~Kf+T=(f8@Bb_T~TJ+V6K{-M{(BOMvSGk^!p@#(zFq*b^FFlq)8}r%BDFY;Ia5> zhO=QnS9v*%JJH~yzw0#1#vD%A0IeG0u%J=5+39=VRC^U|J1V7Ib*7}bu7^0sumB>A z3w2R6RPm)|?qB9B;4mk#nco|A;Y@3{z#2BJ#n@t7dQ!7p87>nZC*>8+q4$C01x!At ztmzn;APPU~Fa*EGDdhCo&`1IiY_=SNs0Uz{P4zBdNVQEe&YTX?r;QRrXpM&|}9kEvR+v2i2idyl{gDPGE@8XAwi?2^F*zWew2jdqV z6R{Qmt#RHG@w0#TQWA6dY4qlX3p|C5DMxS7Jmn#2)oHm*(Mk0=DFv2hpEk6g~6YblozR;14DJQOB%Km(;9%zDLU`P>T3-JR;8xIpg1l zkhJo5h}~v~BH*IUd{jfFr^yz$7GKGyP>{nEV!B3)@E-9mzVkwWYL~-wG6+95s$Yw= zHEfKHs9hdJKDE^qB!?s2Tz?RP9|b9foR)K`0PKsK?ks)GGY2RS^O~i<7oRu^OkbDvTYLRHyvI%8c?B=78mH2ny~= zE+y^w`?%!lT7Zmlw`W+1$oYjB!cGMSn#m2CT(Q`U+Av7ij5z(@x*n@yC;Wf3y?0oX z-M1~OB2h4aiiOaXCQSmNcSJ=51f_RE3B7~TQ9=UI{1mc=40>2jCNvOivN}UCCHX6`bv%K=!sLM%46WwHR`z{?}m!DKIv6|pg z)2t+e;<}}K2;%K;I(z*G8@fBj?Ay0CxNczN`)>-YoQsy&Za5k__rP<{;}0v+%pWCf z)i696qN#@U%bcHBF;SM*W<^Ad>=PTDHLO*7L=D6=qvN752<4zs?!mv6XCTtGjLUh~ zszqW0!JLkMf~c^5@oqSeJ>U)lg*>8e_1MxGS=E!g*0vZhQi0taIV2FVHbXAuso*5c zOHuEfF@oyQo{(B+I|9-*I3vfTdk=0 zD8i96mogo^sP_cl+i$5_t&6=EO6f$TUcH0j++L2x~qcdxhG2_5YAWOIwX7S8T~n!d9{p*o_Dy-*xaY)l; zAo0CkNW0MzA%GRLieYW^gs`Vdz~&(*cNgo<-@GI1wlNru&rwh&Oc@5D?+A$~avKTx zQsj*zZ_#}EC&>%HRZwc!4n7N9vC=A8;&|)+-SX|skgj!ypG%Kce#8`$hH3k~sKUR0 zK*(g-&-ZG$1RKHYtg@Ls}TEt7tINVTPa_gz~>toFltt z$tLcz51Sb&3kOXgnEY`yTvq#nf)7IJr5qnM`Ng|q;@!VqA85s3ur3haINnCO(t(}< zlLUmT_^k}CmnUzm5w$Z?h_?6M1i_srxNzmJ!=#6-c9P6wj#29-gLP}*g(MjtK0=Gz zwB4L0f;-Ee%(lT7(O=uUq1NS5M8#|7QK5p0$j&ga!u*DY54cD2a^Vuhuc|h>6W=Sw zwD;T+1SArWD{=sI#6K*lsYMFp5iiqNJ9Rpd8*k}3WvB$RML-)}{|x-WPRKx=c=vVL zAO_DOklnc3uA^dO$%x^JUJ!6@R1ae#jAf3#T5hNPZeHk ztXH&+e1dS>3Egqn*>H#V^=6zQmzB5lED%|bx7?>?p!7W{WZ4=>33xQ2#Sc}TmW)knU9 znf?J`8RY4Q8F!-}feu%T_BLINeSkNg+wO+tV6iP4?w7jnKvere=9WF3m{A;jyK2f{ zdp9Vrjm5qjfu5uN((mF>nHV2m&SYOh%b`ybJABdgW1WrERTZ(f%LSU8?O)5+N`Fbb z`rK`sigZH%lWLb=(nt|EyiB#R7%_F5nUG6!bqxL`uU~iv1t<3xKwsI2aoD1Gf=e?% zkH4q1IIX0*y&0IZvgs$evKh3T#BxAl4YAJM4-L0sHtl&!6`IJ=oF5|_yU5%oYGu)Z zY|G1U&{6vY4M{HSHEI49p*ehg@H0w(PUs_9nCR%@C8d~Mwmu<_3JP_#_|5xnr7!2A zKjh8(@^(6Na90$$44Ws6fDYzg6?v_6hjBiVNG|P-KC2XCuz|cF8Vw=yv)`W=8lV5* zOJvwNLj-?IEd3?m`{sP7B(K6QWeb$_{rJ}DdxF<<*{{i_^Rl0+*>q8XHJ7GA$G)qF z>tt7PeNbxekumctx1We*m1>S&N5`R8IF>cTldqO~*ZgRLSAq=vTjo2oE3TM!;rS-% zUQ_F^im~)Tb-X0rkVuEvpWwf7i)fpT$#z;)c&+;S_I#kb@z-z%Wl%-7C34mfmoLt# z<`&O?b&FlimE&IiKvpAuu86P7rKofy5$GwgIc(mRo4!?qi#{I!yHvA8CU(Q{WZfU6 zb%_-j=dXSpM(;^2>e^w4w;WJpHj(OzJNM{&`#83Gr9WWoX_29OzoPu=R?t@B@+bLu zG4xzxaCr$HMxrl*Q`4D^?dy|7-44=(iHvIBO}I9cvS?a8bfxdttJ6M;Hf*Q4&&W(> z+E$j!ZEyODdVhiMFwr0^LL1pATCX3x8>HpLV^X6Q==5>DZZEG{gefG1t-6#wd~cV) z=kpgFhbqqa3sQ+b-oNCs)UQbZMbm?Lw>5L2^0fRnNmv^a6NoKh{8}I~K@W@!+ZIRa z8~9-DF6FK02Z%BSz!is-2A&n<&W{fUPp2tt`RIVR*D4#iT##-pF_c!=$ua7RFD}2U zDXC=x>2U)928}i+bXpRbvH|P<+HFcU(Sfj^voi@gtRTHdr{=NUI=z%^sp!JdJ?nN% zjUuAr(8PSV-Ef@v6nq!O&i&>eoIaLL${1D?Ssz537ns9%KGGLZng9R`(gIER6bf%b zZ;XT>JVn9q_c!yq#(}s;b+^N|P(1q$BK2mKVth92M|d!1TSXd$k65ji*>xLK3zVY2 z_E%19>V;PMY&`33pPjcnZJQq+XeQD+tbES91vb3O_nO`PLs!i)6)53FZ(JzZY4o=yU)25Y62iq!r8Z+0xpJ5ulM5X!B?=y$ zZua^r1}hw)h}agS{kW+i{HuJ^hvCykPSf**p?opA@7?5q@;9x8Yy`&4AX1=OR`DWS z!wG7wKvE%*whd&oei$0aE8~QDtE3vU`B48uon+-ZWkbJs|8%nJa|gx=RcA0*-Bs$5 zO6P1$6g?noLB}<2#X}H%lDP-kGHb9*_D%9J=ZKZ~txHqZW1r(|o3h>|nDfj##P4im zdgn}j2+y3mYoSa2-duK*7I#$b*c&1|RIs4w@HLHafFK))kllzyRv;TG;)c&6&103% zr`{AMf~Lb=5dGc67b|)G}fhs4@yY}s`+`eR1%R~UX~IS@gf%s>zoIs z--MdPRRVWnC9MZ@!4AJXZep^@i)rGAT-*}An%w*9$I`0x_Zw|#B1|L{qOW+r9z10> zh=4gKhyz*_7McpxQFc)o+N&7gx^AFatNj+7m3la0>h^6j2+0pr0mAu8!%M?zjIMH2-8cX0Imbu8`*viwXE_RALmyJ*Ji;V|eh8Df z_@DA_v?BLx>ZR_@A5{|{Q@1ls!P>!1twl$gi)F8JPcjljq?;IQXxPUFBUpn1_cT`? zX9oRlmDfl}zirh?B}VotHuOVwJJd>%{kOBJk|HmFYep;ThTz3ZoD3q{R3pwuxbG4n z>hX65PU=%2n~tJo_|c|k2hy)GOE|89&?0qFk@B`;_S*cgHNH+{J^p@vxGp}G{-vJU z@-#)~)1pH+DPY85k)^sSe~D>!DxPOM`VwoQf8|Z%72c9nQx)-Rx3&K%@R5LPt^>;%fFLACDC&{oD;$2r;iVTAcYdok8ybs9S$THWd zcVggZYy4?y^aGk7P>q#nnKN}zNd)cZ-FX^V64tuh?wM<=pyj$L31a)>yf)VrPT1}^ zgh|jilrY?|o2g1$?(*5drjD<79+Dny?@6^MU-~@`vEqN4r^1 zGt!4Uy(-&tJVj=vkodL&KZsHs4)?(zvpt@rZTzF=do3;vsB5h5^5XfDNMmLINsd6e z>}HVKy>$5N3z&9X0!*|Of8yq-n*^5(_mG1Gt;iv5>2eE7C6N5*bZ0tjf4{G^eO#TU z@ciACUkrvh?V8|^#fE$Ex88E_1UUtH;JDdb&0Jq^_OSD`Rxh^9_gl7TZJNL5z7nE7 zoLII*6`J7mQm_*rn^m51D=yt_M&a~%6OCf!L#;wf-#9|klCQjJYfEb|8bw@eX?zd>_HS)2RUbuL zvSr`OjM4aPoL+e-T49zSyJY_DQ`@VC6W^kj%40V3y&V@C@42;oV-5Mzq7esMK-loA zZBS*fN4p@j(2ieTFvM}B?}lRZIq`zlo-~Td^8T>oaEJrBRe#mI5o8Vw=c9o9?Xz3S zG+%RDHQ{Rox`YqiNr8R3Wo?Mi^2P}w56cUNp<83`^gm^@p{w2*Ttr3qSzBPTW}Ay= ztx|Lz=(E0`(hNrwlSa>K`-%=fTL07yo{d^mdzu)7()dTy!=}@AGPVM@Td8Q+@3P{c zyEN|;bJfK2@(bfy^5!{O1=KsVv`n)0;_~YBvhd*i^eQGi_HxGC^4l0^DXF+q&ias8 zTjy_KD5U9l5W>QEXq1o7E96;u&Z`=htpuOq){{5sU#Kj0Yq^!hdd8~KDPoH1pFU{a z3iDel=^5u1Gc=IXH#AH&Ffgm0Q#RN2aetHM1tPw%2|Cvu@a-e@oiR#&rF2_}BM_Id zB)n*L+!Vj5XvU3f@RGu{D+cpB1-MHbU<9p@TZj&zDGJ)531#y?6K;M8vg^`$qmA|s zvrS8+)|Y1J;p7(?*wIsFI*8nO7Uip?gQA4 z{Xua}icp;uuvNlm@Wfxz@^NqM%ep#2|s6nR)Yb<-sLc59c|F#`9{t zBWMuYU)VuTS$l-VMyA~9*@Aj)#fb}G=qU^Vw}nn(LkNhvLL>%fS)gZ*)i z?KuYf5&~>&vU+Vk=^gV4DZ1hDrQ+W1eBP=M8X8S;(HQas`wqPHeswnwc{r4Zhe{G> z9($BtM8lX5S3k~=hiLA-)8e|KaPiAsKS0VV2p3vrc{P8ef+#M(hIFdvD|ryT@5Le4 zxodwqs!7gTM+Xz2u@RIuAs2md_cdZ*5&i__sadP)xBjVm$69dNKUQ7LXtin)Za=9Z ztmS<1zNkx&WKL^Y6SU#v@8J!$e)OMays|u=r0S;ek*ULG)~jM;oO-cCyI?}K4MKc( zkiz4 zU~)cZL!TWMuU}uyEkpVNjfKxfpCza@kF}~$oLpL!Fcf!=9}!Tx9@Ft{Ck)c+=?M7N z(U$MhhTXG7{t-93kQl78)B(ROV6X{o_Xzj)@V?e(pvGxe6JL3KO~E1nuAmznC$@p)e_GDF|ZGVPXNGsw|K zwp1p#o6u+&fQDgY->z-3@uHv>aPQ2N5agSzAJ8guMwmogn-S`LL@^9%36UD4(4Dp; zZl;*TD7G;cWQ?6@9hrUkx(3GYM(-oIWxg$iy97x+I&e;K95|=4ikq&VukNg2Lr)iD}7LA2HcPa#@9Zg-DsN) z{hee`ukUo``JJ`svT5JINma-!`ami=G+~FiL(rgMt-rttW}Ot(d`q>;kSk$c0isSN z63*s%x-ZjKtgOdC*`5!`t+zH;C~sw**xpSw#C04jOMfY6a#&Vf4rcSe;^XcQxcTl3 zQ@Xv^51Q>pkI3x9A5w?7o4Qmeyoiv4`%KPMXl0P9GZjM7EeZ({T6iiz^N{B8>&LYv zl%3K8OBm2EQAvd|u)jRGt$Op*AT&@2P43?n-IiRIAGr|DC2QH*>CcYp4$k_t+@ruz z)Z9~$A%D&cvNOx5%7~Y%&3(aFvf5LF2ogm#kJ9eP`Ci?d7fCpkbX>7lt zzd4$1V(jQ(%$Dfh>q+n?(9ws54sB_sCpy(+&CCRJB+lVBL|(o&W@M4mQf0{$o8$eU ziry4kbJ;`$JOI#{`0^tjO0A$e7fAtJ8=a3|t+p-fAoeJf1B}Q&{q+QT+pipb8ICHniSNc;+)nvNn9TB=Xn7H?D zsX}8o!&TOmK25{EsjEMuWnMjdPuWz_9H`Qv8c%XRukNT|tIDfr7jujC0?MHe;E;eN zj$G>_k}VNy4n*tvF(`FI=c=0W`lV3CD80LaiecG)rln2d#9rbG!tQyV36A9 zx0-xj4?=gjov+$Jc5)b_ZSu395$qd;$;a!0Coh)C9*IgEP!>&jN`Vou@rB~7fXuA(#ckt2zKSR(#X^Tz6`2Ba zxCuXt#KOb6d2rvD?uG_TFg4UZkNjcu^X+P} z{g{tM59>&@4DM%_n)4JD`ew9w+r8K1qPF>0#W_imWXSD+lz0Z<%n$J7i=Dcmj7o{( zl&s1a;&5?~v-exO4?5Ck|?gVyvWcWfXc6}-@BuzNLMXwvNjtoypAAKp(L?mJ;6 zyr#uk!-_&^(VQk0BKLvfep3Gn%XO+RYZR1}l7&eiLlJKg=HI@D2V0}P60u}7f(grY zk#_EZ$S80+f%4(tZ(k-o_R9xy#xr^eT)Xu5UF37_?5>ZN`3OUSAOo-*ff1Nb_{Ep* zz-?12lX>@ybRNPMByDd_(Yo(-?=}4jM}AkvCSemfOcrDs8M~p;BH+$w+I%I76U=xZ z=}zkLYp&?nu~!3-DEvE5Y8SPbArmlgt<>Q?2eLlnN`ChpP= z(2km?Rdw^b)m_?E0)jExn6!$c=ay)H_PhPtk&e+&L2 z!`_4|%X&TLNTJuc+p*uI9{cd6DL7iNc%T18p!23IRnC=dU2B3;&j$^f1~PS zY(7o1qdokPwxZ<7Y+t?^lm61FzPGLSz~Eo4-}XE#E8<=$?9DP!RmwDJCnuRyuoXE zNU`D75aS<>R1Nr>Ek(}W^8icA3C15CrZn+J$MIGV`oMT7Pns4)G5Ho5C=Ij zsj`I=--zo&YHyhAG_UZu!B{_ltWMat7Xury;GjcLq3pgFuV0<7N5MT2+sgc&-q|B_ z>@6SK(l#&fRo}ISyj(q?&Emd}u-K2%6sqON!OKqmz4+GCC}W7VE>Ne0(956J!(=y1 zRh06o+0fLK7C(-7OP)1AjYZtgUIl|fq|-UVufR2Gg9SJnC>1s!+fxO0McN?lA$(`D zqR#o5Ly>%JFqFf(fYFT3;8Rh%L>WF~M}mnlRt;Pylu`i1Z&Z$$Dp_pU>}+VxEbZOM zmL|x%+`EeM@rjWSRI^{@mO zJ_9b|YlIiV0tS!4oy}D~YRcDc{l>bc=*ps|o((lpift72+1#1RX;th7|8R|g@;z)L z0|b$UuXG(S3WA@`n3e7;#*1~ji9VlwNAf1y{g=t7jX8z%shwD}DCz5MY2x`%c`x&b z_B1d^#}aOMmCRau+J-6K>|Ko4Z1XjVlb0+FP|9A(w4WuH#WM{Wof5VbNA4AZBsa9~ z5T6uVUSWglz%)=B?N+dA5T53c*`RB434Wq|Lq);fXP!^VWW!j4nTMfw(YE7W_3$gr zaI|hAB_dpl55-7g4nZV7sUUcK0*ilpe4+-SckLnPZ1I)^@&~MUgutwW+TB7}DBaO4 zo1{iUK5i{!3z&bqkAo&EU%-jefdP0mjB7+D`=9|%2)5&-fFs<JmcEmweKOyA=(t;xnYF3p$gU+ zmP#=qsj^g#uLcUB39DprTFCZW0BQv`(^VY~KaM#tFr)&vN>%9h7i9+SEKIWsrkq_0 z%vJhmWYR}2?6_|^^Q|EM@2rQp586#3+sjMrnpqRKD6aPkXm?m8E+WC`qj1bcAb+%i zPNQHL63!q-a#`Qw*gygDRcjFa;5`T~y=Xmz8zh%=Q?*fK8j4D=kPn1?G7f^H^Mv$B z6b5XbHPTCw&~a1&5kK3_?z_%4QpDRZ_Jn!1Cp6&qrUqXaX6qt0Ijn8iSnEv<;M% zQO+8dH>_e=4==l8+%`_ij}FnW^#ddLx@Szv z(jUnuBw5K*lC%s1eWfpwgYd%Dza*%A&1b-Q&I{QF_0g1L*UB_&jrsk9tRK8Nvi`2~ zN4c#(M@U?H@I4wKed77NLjN+0vzi6Nrvw@w+%l(k86u!El8WHl`Teb=!oibCf`(+K zq(U}B;p#7xNM+q-juh9&0&^-Cxpq}!8LJ*xyM;f)+xjVpi#IBqF&$cucqHpLU+QDadcnODpMhNz~;nJ`tfl0kU(=vj0K}c@p-|k1hqm zLR;!V@9jsB3Hi(*5v)lbKfBQQ!cs-+j!%;&5bM9r92`L%NT_(&gaYB1X;LM`OP0*v zSd_>Ux9HI!?HbFkuBUW)3gdjX$1{gM_d^t&>8opiQtxlyrwXDLvwMw~rC1CSw7p+C z)uv02Qt|RiPJHPPkWHL$--2lZtRd{}{Pu!Qm1$nBv@Ja&nQKiaNTu#MpHXOtVD|zS z)fh@gk$=hxRn&;C>3!Djw{&IGZ}H zDtqdKwl+(>MzI~>0m5J$ziR5B2T_K+T!tF6+!RTLRP`efRHVnTRtHD#V0}cJdfCA- zvz1bb;+2zZ=evl4(n|BIkEta9RVXFTAT%rd2g!BSX~IMLWGcX=^NBQ|X#21vqcKhO z4#>rSJRbjfmLHHbR8@~=f`2Hlj?}*Zu>!^YJH+h2R-Li29VSwibOluT`|OPA|MC@O zNYSS`(TL5Bo=%zNZ$zbFG2Nfpog2YPKxkrCa`TGLXxBl1ArUtSkBq9}>F-#3-8SAQ z29-EyTgS8MBW4gnt4b!kqXHX>R(CH-{h1$r`b+BEHp6J}r?X0) z(%k=gEWkt95~X{|RXZbK3rS0j^(Rp(?i_7Wm;}r#nBc^tU4=oztt@CHxxq2PQ=?+x464?fkr#q!xS;)>8z?c&iGbE-RF8AWA ze(|-W=+x(t_x$Wjn9u3?hS^lp&=hM=Gx?YUQ`Bn^Lz2IfZSYmyO5 zKo+%z|K6pC6{v``jb6ET>`YUN@lq))Osw6uLQ|SSc1dfEEgQVWdL5YQ-LtR@47~3t zLj?eA{b$-rg$8lBTFd8dKziHDK2nN4Oe`3I{;1NK_|IGi++nIf-hY+exR>LF`cm*V zBPk!ge?R&&`d3NZ__m3Vrve_+po$G^o*fs{-z!l5dF1yXDxfD8I!3W58%TJnFuN(X z^^@Lk>unG_cB(LpOloB?cO~jN2`}rle_{DCG{=#&)k6a@NrN{ieQadF{Pu5_5Li?v zbZG~yS&waQtT#ZdP*Uli#yc%s#Xs`lxB-2m_kic`jXo~vK)NQ-(N$A;?(EF*K)q_G zWNjuXlmF$PY5G4YHBvV3ac@?XAKfW4_W7U;fQtbNuc$eF?FtIM{ZGmHr!a^;F*qmo zM2|>JQDVJ~eWoWHEP}})O-UNe(0k4h9`WD)-Eot?Vs~z|9PBUu6^|+|2T}ncuvV{s zMd6c$22*&n8myPbi?@rEozJ#9Q&Sonn3Qn~sB3o!Brp2F9}?O{6CtgkO6L!5e1ji& zl4M{6lz~G4O*f5!s?KcaudL!VhRwEt`ziA@TXK_*W z3$6@X%h{=Y27`A>sSz#$rFLng@{H0r{b1QV^=cJNZ(ntzxXA@S4TZD}Px z`5!x_bi~NFg4UT2-&C2tsI7da`Ax8Ed({o~(#Ar35!to1-1pvfO}B~lua=}H4e~wv z_)G8H$D&H|2d_NHcQ=){&M=oT0aqLWNZ_F|*Dx|foJt)%vC-!tg`~M0JqUEUWgd2u zB?Cnzt7GWyG^1_!Tzm*3bs?14MZf;A*{B8jx{x;o`fIU~;aK7cz|urTsU=9qq{ zJ#V5Fv$OFaMm03Kj4Nf~p>_?M)5a@lO`1^KoH6zdEA%tX6laD4!5x9%9DM-FPnL#= z#GWh$(tUi4?fdwouBTIysCw^L|8M^tAQ@os)}43lMiVAUvUd~@0XmPAIJ4+=LUX0+XNV`uLGhN3HF?Cw9hSv<=|awDVKa9l-5^rMD%~a_P=nVFVgf zjDpkkG=Vt|%(O%+U$2!Al?3s}p*2>w>#UvMX3_v|?qwcW^*}w-TNX{IY@yBT35gwm zu>V)}r+v5_8^~EG%~WwAu2BD6xWu7wjHa3eAZ&i^;{ewUFgTeji^UOplNbw11o2IMRpN7+c2Bhx`s-qdIVUD$1yn-+E5MPkil9`?)2<7u^Y=VAX*h4f zA)46m;8zSkI@oh6(!~ft_2gJm4ACeIB5obPE_|^bUkl(w9!*eN45y_2a{@c4VsW$a z`clU@$LQ9cJ-G8>)J}8?tFd?8hIWyE!Vh6HDmvBCfDVN+B}IwG^^Py(#}k*sZUH!j zfafYlLWufOx|RgL<<5OcL5jckUgk*&W zO~!taYubo(oNH#7aNEruf5leTzRDjRd=AtIN?=G2ejg`2X(PKwK^G9^;;XbEAXLI` zVgZxCb|wV{tUT~wTCWAiioIt+n?IxLnA)U8c|h2Va~`CPZX?|H*+ zJApRwXBg1^CRDG~uCSj&{_MR2Ut7CVyj*L7ui_tZaRIe(zUSqpUnA&so3PxL3)Q|1 zUcbJkPNW{~vw0ovOdtQ1ymumrLj27D3aS0`hpXYfMiQwd@6K;t+hp0y0p4R+gVOaj zqa%{_+WvZXhp|H({?i+TthpO|S%n9ml4 z$C#DCevZfSgRVaBM%y@Bs#o0$X+nWP01WvUo=83_rzG1o?=tvt`2;`v&7MSry?cVp z`bYn`3#IDoBBi~VoS>3}OD=|mYJ5!uUaR#V;ZmqOjB^U$@X?asSx5vbpVId1dMaQ! z`oOwFEg&graMxmxa}3BmA{8tzw=vlGiEpExM%e|r<1-(vK1coPK1g)hd3Xu6LgBkD z%RMhYIFfbtM`9+4LsGe(?VMnON-soEDyKE%?%xyjB!dxE%gf4}eBs$nEVlw`G(oy1 zx07djfLi&o_p4S(V7Y3&Y2jes(M^*}0qiSMyEk#C%!~eV0BnCIBiu^qgVGH@!6ap? zb$`1gMoj(n1c#(p^TZFsg0eK4Dc5p`b$H5NMn|pIZz}6dw(l-X&VU++8uIDAIJLKv7<(D-~a>tkN)s~bbIs}f!c*E`6R|ain?(-0 z8?!@Q&-(E1EG~__ZS9%6OQqg>QG-Ec?9AkBO9XlCT%64OZMOg1$tTN9V@c<3cO3ObxIx0fcak8xRh4IIgVV0)dL#!^C#*#<=EueYRU zMm*2E%p$0KTssf&71|1m(N%gOZAP@91o*Q^qVO@6E04y2_Iky`+Z;}>O!{5`?Yq-z zI1Z*c1^SDI#N6%S&H*}weh1g>rSfmRx5)gUS9YJOTz!vyxv|@G^quo)q*PpMX1D6_ zqk(XP-gr><4G_Vme!lv~Qx{lN+vxJ!M6aXBR$ukpe12mqIEUXHA)I-B7rLaq?mD=lv9I9<1Yq@EGQ?zj{ zbM3UY3Mtg&@m*&%K29;k4Vx(1oA>6G_W%kJlu#a){U+oxh+f;>&_F@&25H?#W4sQ- z5)?AksC}Y~fS@R9{mB_HgsFWY-5c*Z-JGV^Vf|{nYDPY7(cLWOVU}x;?VA;1%;ygI#EPOq47L=)W zql@kP{7S4ZhDBbR(Cqu7w z)Dp$LPt&aZ?b3W6TtLXvzW3KN3OoP==?NTU%$cfmnHSokH2Qh#|z&gy!d(qy6?en26eN;ZkNMR;P6 z{AvSWdI?U9*LBG64EJWirwm zrDaDSdM*#2d8vEp+@IAOE{sxp`#ivorp{(2LClIB1K6EM}eKDsjZ4*1ss9iyMKp}pl4Pd{Q%-4d^0NGyBc z42jZtq}Rr{3fA}gv%f)uwOLyEF*gJsg}&L^0ep>86J%R8Y(dKE;>LS*lC1?~jjXC<|J3hwJ{%Lvn`_onrGxMu*7~j;@ zw7gKf_xpo&`D-ost&1+RTxwYdZ2d`OTY0m$C|<0m8BgaWit|*HeiTKDi7r0VEZzA! ze+ZM=OtMWoJU@5pqd|YHLY!Zx?+>@kUt;tF1rH+2u?i>WU##D=LNAFE7Q_-xmFXa1 zP~d6_@=D*yEHdYWWZ1p#sg0XA?hNQw&6i?|DY8W3$Q~?neDO&LtXy+qUl8@CejIy6 zri*{;^Z~L(>*wCj7fjk)W1lURMvNBXhTYRUMvZDkG+;xsQAaUF zX^NXT@T8!7%CxO3D3uX7FLxPTINzF5-w&^cWKXLpx~m#~sYR#t78%8Ny~^;H^8_Ye zbedQy;@j1-)Ei@YW1DZh4@1hQj;dT$IBi$zO!SLvEr;TuhU|P%=*s6XapSTE>C*ZxT51Qzv0RA=pm!^a`j%C8`OQz z&q>Y=Wh|aiN=1F*A@w;}l1W&X3L7Yh9v-?RD@bz)L&24Lg zUW_~GLXArkQ~}HP-T=jJuwd+7#Aco+9x7-J_ON(3gn#c4)(Cxyf!u8~fYr<5` z{Afr+V@WPDO43dsmGOmBx2lA@HkB9$=VZ2ny{`AIx15x~?vPQPV`i?q-^y2}0KjkH za|UB(q(eL$4fX}lqq)EE1Y&ZEu+P__f}q#Flk$GM(P1%}{yp9RkPJrgJ|=0-H6`Us zRa@6AdXvo=*-vky68>xmwnS)A2`SA9(YW=cwi$1E-fHDi1$b3sI{c93!C$ zd@&@DZnEDEeML_(3~#y3H6v_H?m{bYb!p@Y74@Yj_+z^nA4c{}LNo1BjVZ2?XN%CS z2?YbK!m(F;Mi_O{h3ezkp>V@fPZp#|$?kI;3yukNUWhiZE!ls50iHp|w5%tepUG9( zoeCC*J)D3_4R;du)>HdHu-{+24cLMXEQdQL?YI>Y6-i_In3oAWE_-9%le#EFKYAxc5R z%Tg!EbYg%UpQh(bsY|5TQ-LPfssgMam)lkc9%V_}Ukn}34ZCw!$asA!pQQXamZDvc z?$|*Dp4;9wZvs#3e}KI7=3)l0%5l@(4r)@-`PA)t5l+A&Xc)RW|hJP@8`7j;mYk10!cF^-Vl;QWFE}d&>+MilP#J~)#-rr_Z}G_R+OW^P zFjy}p^DSA-L&Hq*>=Gid7v%E3i^^~Z3jP2n0AUz65yb>?B37kW#H!hy&(J|(((Zf3 zV=!o9l9mQCNrvh9oFC%Cp~)3AmmSAN8^}RZ@sYTF-Sep|_KtIz#a11?X8IBdZzY0~ zXId-fC~l3)>3fzQq|Nq(Zr#BajY*t!K5SgAa(zT&>p-b4#XJU$%g*x3%kgts6tCU* zc7F?}(`c=`B+eU<%s-jzJSWCMljZGisLeV6PVT`gNg7lKhQfK8$DcSPuC^;fK;>Z< z*bXt7R7j{~^_7f1hG(2r^VI_u9F+PQ-{#d1TrYVe);6(y+6^ik;Xy(&;=f%=1?gJHlGMfupMh)W{1QjJ1k)Ht@eNOy{ z_nZrZm8OMzR72AXCHW!@hHF_oF3#+k;;r>-{`tG)oe(4Jce7%L+51|byaZ^y_eo}Y zF<=+kS|Qv&*mbNRm?mHY6p)hC*c}B;?59lY@s%#NH1F)fy60bHdSw8;`|_d&j50!L zes^E1$#+`urwjc=6f|)8cdW;2!Mg2_-$XrT4> znW7TD`av7Xo!ig(ybv4LdJZY)-N^>h8N%BcoNrL2rW?My110&cTjMNDy;bqFnR$CZ zd>T}aZo!JP0~CHwcYzijn&h8=y<-81uPb$LHEKyVD?S42=}S9TOpMz{>Td?Hn{bpU zL)!o1&xxYn__O@X1Q%0cgqUHSKMWaxTAd$$-Vw2rggtNDktK0qSwR%gn8u$~t{3%b zZvB`GTG<4nZYm)F$IeqYMSlODY3@Kv=lI%Z0FV2x$s%UNgi3PYW{NER&_rK};nnW> zz!{OeTM19;^#a?|7+}9rh2tjL(VHuiCaBxz49)44r>EL_xkk|CfF8q>7x&YEMKb zp6fw%qKQC;Yg6}hX#>?=SP!Nx|NjdWGL#txr?3_Irx!}*cO_N)?8PzX)XVSov-YHg zrbd>4xo7|;NqB$%%L8ENqL%5MFPe2=*nHFUJ?lEiTR<*5*I7d?>_)zSQX$QoQq=G7 zLkABzHwAwV%{yf5_3kZ`8>UEcHg*8dk!LNY*soL2_QK|@=D{h+{fF;fw#Rfn2vrkaNRcBrkoy1LbR zr`ivR4^=~b^t{Bc)Qk89UXA`r>;(jO=nuQ4Arw{jQxP0W0Yd1qqI$4H5i3~p{+AV8 z>WG%9Odb%Gam`$7-fj+rm(iBe-IrartchPg=qJ-T2CSGI%4hsvdzr$Qf*xnuK+c`~ zS2AC;hRimnF0m)KiZ{>7Kw2^OFzIYwXl{7-Ae8kLkSud7PRPfxd-Z~)Q~#npAoRQC za(^{u0~g<;2hmcWT??6fM>s<1G8L2$9#xPf?E0%{vVx=&!&WZXg!pQ+ejrTiD}&jT zF(;zg!mlR!d;E+HN`IrTdn^VqMvJ&S#k5WQhpRc2kiEXE_({G#8cRai=$E-l*-(eu zWAH1t4I%7q3fyh~jURz$4pwvr@|wtDXLy;A8#5PxKNPK^H==WFHvWqJ52RB3?qyDA{mBC~qIK)gK)`6^ZTtFjGKstX+i~3s2 zN;^JCaZfpaT0k$zM^V_fE#1f~(5LwdFITYrLC1u}$d6O3(c|kD@ys# zbN*p04t4&KaJvx)DlrrlFw36)wfK=i=Yt*Cl=d0nc>ge?e~?N4{{orLkkOlcxBD+c zMMA;C{4kmdErHt(StN*>F^lgu0p|fh+92j|_%=aUz&~P<*E#WAa4e@jQD(i!S&ZIu z^nCl%xv=5?H`>&9VsMYKz}iMxfaLxG>0{Wa2w3(VR6YqOs#+KM;r(zgc&&O@UwLS{ z#=`orNcT)xci@R$AN<6_Kp=_ycGvcCW@B3><8d#{RFPk!*c~=zm{hvh4;&%54lysV z-dL;90W822GGTF>p3SQJ+llhGr@bTV+s|noTK^)h3GK;h;hJ}pteo4zeG4lMH<&K! z3koYw`w20v;(4w0RGy6S)wqlabKJRf3PsTJP6&5L>{?+^S{vk zQaAbPePm=@A#6kV|9~F-7^8q7e~)W~K)6a(*h!2yjEYuF43#+?lv$5gg5j5MO(B?L zsa3cK6|g;fM>>Co`EIe`;gCiqFXYi@$y%{is{6zTa5Jt!XjO3Tk;~?cUDWpkPT2MK zPU!XjU=^yiE!$Z!|Az?#J-=ZQZi!#mgyWoj`&=W_z@odz7%QSxsiPmBqjS$+lX=u_ z$ZmiBE4lbO{diolT2tsxBlw`hNYMzS-torC|MW@rRhASH zKD(;jH{E74&5=#xgw3wN_KW``aY#K_fJAJrwRU17t{ogZ-oetJkjuVb#-QD_)srpB zHCMiNjL|&yeot0z<=kPE5-m)A3?II~)*1Zr;|+aIwvS8ykI2E-#)x6UMX2{BG@t4d ze)Vkb{s*MkKiWbIq*J>@Nl9HqZ7x|`DON3eh};a*a)0=~Z3~yC>Y_~u%f~dI8ohaI zGDI{9z~1e3hIA6eU@+$VuWankz}L9e|5;RAXBv{w@{d%uB~*u_48nDm^_*cEOG(VOJyPn`DfSkN;XRrzV?3f`l--JI9gpr9TNz)$!IbI?%{`F zXRY2V>)+g8OR%{Y2y^i^UvcM010PCT!NwF_c@T!Z0(NgimghGz9sTc+$&vmo&S%?% zocIS~L!taRQr>I+3Ax-4`m1ov>C~guPj;@ihagvxyVvv#S$d$tH>ngj(U+9A$f5>`>g8L3maY`#r0G-0E zNBK#el92Wgut{q3qhaLS8DJp9|6D`o6&yWfIQ>}sSJkz|lYMj_aHlt*?NE~_>G3lO`QDz;tl(n@1D$)d$M>-MrU9!#p+_r?&Sr(D(9FL3kus-B*bOfxnb}1FUm>J z&5tRioxWZD`-UILtuo0@zC9SfC85Fom;a`It005wlSmyj$F6iMC}Y9a4PZE?JOm8d zBKxQ8qFOFj!T9YO?yq$8Cu;XtfTRAgObTXahp7lh)bn4S* zM6Z*QwA3d6Qfkro+U9jr5wROGjK4C2X8QRg@Btg?=4blcI+2~i~SGkkC1KRnOjNI*s1pQiJ#3(_zbx-|Fibv);FhJ>_KKJ zn6E8H6DHw!w(2qW!2cxt{MHPrY@odhVsZR9w+Nu7hr}zQzbIs9plUDnb|s=!9zFz| z`FMl+e_;h-Jil*+gpl(xlo(&Xqhs5?fqnnHJ`ymr%Jpo&^j74OU+YAZ$(c0Sphw5U zL)4jn^3XJv zMPessr+YUsLxgF1e=6*fUg5eb8i^HIvD3cdMY?oAN550y zQ(Qd!S|povzB%nvek&p?eEo1n>jT-jmUAYM%*p^;_p)GoA4?HUsiZ0m&Lv!2yBd`ec~9V@mSV| zg0|+R6ACU6%LOnOOp52~D|(XdlLFRWeG7QC1F_$uOj+X0P2<0d;Pq4W`u=Qs?KC!?( zU^%ApX0f97p33kib;kmW=EcM*oa;z3`hkV>u;}kuE0>EWkcO<}JZWG!st|txx)v0z zaXd~%i=*91)IklG9D55RUyJfZju1IVy!FdexunNw~ zP#`2gBjL(VIkE%u^X1i}eG2XOXeWR6);V`0W2v(XT)F zyet5hF0=L-Z#0Y3$G-T|$9nyW&%Kc+xUZ4X9$F*fGdA~+K;+=>X5c|o`#>kgvxCS) zV=8bn#MPfg4JshPBjdZ5lmPwUKkcRt!y=nKx^p#_Rv08%bn)1aHY^O}r&TeFnZL7s ze@MhQ#CZ5Rd_R2y_;eOn{<$j8mPzvkq^pq6xGZsnr)~bSRQ<-NnN5ek(7TaqE{%@S zY#x;Z8x)qI1$lA5G`gkke}Oz79i`Lgb_hqaS}~|_8dKsjJL3w^lwwi*Ex_*6h~;lN z9sn#;VVScwFNYcY@JbM<<xJ&H54ZB^HEihOFGVEQwNFKS-bysj z`2k{$CXGrfmwiA4VTayvH|bxX)_l$XiCP0?=K_f=^xe0?dwaD?*0&&czeTX5cg5j{ zC8+4)NBXisUm`hXbR%=6rnS1M3`Fhp z(~K*$^9SitN3nqxegw#gj6s-a@_%Bii4E*z2x5mJ-le-?CE(k^^Y3bt z{l8P2pqeLa-d}!C{4h4pTEBM;saP+`H>%fzw_ozugnT?vPRvGnr3;tYuMR7{R^WE) z9mw<3ijEL&Y^t@~tqFuLJq`Qp;MF3g90fQS{slfx`XM`vM_r&{$njtFN?-m}uk>|t zz4%>cF_TapmNk1eQJsHiJiZQ49xW9)uuecxrq`S{)d#)WDF9J#z7>x~1DgL3&909o z8ejejpY?*djA|D+$ic%8R65V5P;ziD{Z_;$Iv}^~FQ&ne4D2ev+Xo@vLYfNxcN(e# zZaXyqURENA!KUqty^;EmE-NC?T^(q$2L$nAZ{ic6;$+Q?vQvh0KDRYx%ThI^TQ?0W zagjjn8AS)4fiQha?`$jimb58yjg~$%!#JM%Y9EfZU+P$m(7vRXpKO|Ao}Nod`2GXF z-a+|*uREgJ%*m9)al1aWVMB9eE>h=G6Zjx5gDZMqkUrS9LurY>x2Q8s7!|;oy*PJA zwtCPhf8?eGH{jDgnwb^S0(2048moQ8G`eS&(e%boM1gc}y2%U*e=I~greeZnC-*Pl z2kGsCA@zH)Sw`AbvfZ(53!2_l^+sBO#fmJp8T&oidbJM%5Ww({ zq?LJF8y1bxkqIV;1G0Y%Ee5{h3C-&$pql^8e6&62r}=YJ)$H#Og)_X5FRy}PAHxbr zc)4X=<2$~N#i&I6X)XauABEfv8jv`6(eD zY}m${dqRWR6ou|Kn)`8pFg5E zh5QKP0q}6s7&zKC1^2%wPH{~7VxN$uJHGWN#pPX7The9mm=Lk@t(n2`m9fN~eX670 z3_iOg2-_YPvIad3$R(JaIerl5{*=UUm}6pMf3QAx0n}xp{Thq(R%`z6o-k~b!Tko8 zIP_(oqC)BD>HLMfZb<`m?dc4-goF|huV_mYkD1kHnRKlF8#EkTgcb6SX!v+>G^3cw z2($d>J=+J}{sY3|0*B}|4-3b$f0yR=pm*aL(d&O0)2^z0bR9&+)>Gfb@+|A+J?>_o zllg_}_-MyuV98V@$8y;yIyFSE3vUTf^!_^#lfi(on_^~N_C*|z6-3PmtQKRkzICeJ zn9;*hSo}33W2C~9>>Y6EIi2jmb6iEYoIC9|U|=(W9x(IEL9zHysXc@$p|JJz2l(ls zbxKCb*vlkB|4RVs_ax`aXPZR5oWu3hhQMZb=*&Gs0uW<7X#}6Y>ANK81a23otU17y zE7~S!J$P*r$PEhBq!UfHGSwLCdjLy}=NDwI0Jnv=8(k!M@^CzXLYMZirZ*uwV8TNt z%36SM#|}n^=ggZ}A&v7pm{4dx)SW2&`edshPA|tZx7xi1cx*;PfDIG(OpIejCHgE3 zz#8Am4<{-<{ABH5<{wzQ4e$SswVSW%cV_KuW98JNsS*!5i-zl0L!o0omj_%H(*5AFM~rz->Vil??ME6QBiCcMJO+1r=AY@2eF8{b3|$!Ag@B=}C{5Xw zhMs$)LVXTCioulXr$V#sL0WY|4!|K!CZ!qQHS2y^<|?;YUQD09L3&jK+P^h`Q#ME( zT~*S7i|I@L8=i0L8_+FVKcMNz_3%8`YVJvO6B@}g`wawJ3_mtEp8qW=@OxM;aDnH+ zkXdhVPfi)ss5*vRC&LVisl52BGQ7q0)bTlz7k*a#`8-NMYHZwOI0JfeelV;A>4VDP zp`N|eGk|l$Z0sdXd@AK-=EbQq?W)C5_=ELCyXA8EqnEPBKZB@XZj+D+(>_xHCV-#8 zIcIxJj~vfQkvurFoZ76dvB3IJK$`t4YpdZDmFGKc(U8$Vep zp}sLyNqw?4gK~AUjdDKgL_lv00rZo+0N9hC=}rHl{FHO{(Wno@PXH?Z1OT9TCjLzT z3iDqzRf{c7IDeirZ~D21%jF3+=5fR{6TuIqO@I}k%U{#zzQ_l1vy#RCA5wmOR~!St z0*qf043|nZXt9&o#=DHvZG^^}YskhL$v7w3rx*fK)cRE)f0!emS_tBRw7= zubGeDA^O(j{q}=dmUzx^?WH#QD@b5L(cd8GLJe3H5@N7|R)5UsiBn1J>xQiZA%%w1 ztW!@}Gs;n|@I*!iWreI>)=m?ahX_3e{?M)?id*QsdvY3v;fq<@^ILO8RES&Ji^cy$ zi!zHKh&gbQ{d5`02+qT#h{;M+?;KS!n#AI#nFM`XwGY*X*13>A09B>cKGGv&m*gyZP!4u? zQ%uB(0~9rFT?N>-tAO;EDToV*E?s5!ZH{WK#v7d;$DO#(JM=fAOkVPRyBOcc4ea2c z(x83DdB5xckR)YDR4u3OtaSl<@xOTz!9I1kRw0DQW|~o0L8Bzy&VdK;+s0IDLZ#iy zgT(}}hldPX3-5&SSlyI|lR@Ji6E)>(hYSTwIxSBA;%~ZZ?%y)GW4AG;(CGkW{Zm}2 zp+wttD6(PV{l{w4gijM*(5fpQ^~`Cw-^~?ny}=WIwX!b;uZd zZ>j=zfv@m)lR+$^zI)CC_UfbH6z4+lMA+FS^>pn0_Bc;|8R4UHg2x?)G}Bu3b_Bm2 zBIY|wvNX1b!kDEx#Qv%!&+!4%lP`IVQGZNg9Z(Fw;p{+)*8#=5zbHw=z2Wwl9RCNH zICq}5rmQgmjxO7Oj2-E@nz*st!;{3ho>&io!Y2uF3b_E^nQOj)BN_^{C0SI=sHS1u zRf&NLIu_DE*vf{W*X_CkssKHcsfF5mPg$!Bs_m`jPrLsEr6tY+m?j{NT*6B9_wo76 z0Io9(;B;PaTGQ`zwrQ@BF#0DFneC7YbIkwhiY~~fi2qkz(LZXLu!{b-$Cu3 z$2yW!>*eBAH6Da{IbZyfYN)XI3Ts-*JbmbX4RkM$Bb$m?5Hz8Emr&OZm8$h7LIb5~ zwZEF_!W8H>t}e!Vt#!5ftXG}KPeCG+AXBsP&CD}uhCiPJ$$L$`O=O^AB#no4Vk7|oC`?$2-t=|z zIO%+~H2Hc3%Y5Pv7McXg{;$Xx1c>{Z)r?~gh_2zM?OQaVcc)w9p{94ABa!fC8$ujM z0l~HQi#;&nfwY?5Q9=)>duG^l4dxt|3^7W4(me>A+Q0l3- zUc`s_3oN(+PAW}0ubl5Xk88$W8seyUYV;FkV&3~=J?PE$Y+$Dv(GOUypaH)DzU zcSh-E_;y32;*;oMo(x2jl<#d=pqd)|AgAd zXLyc?N)h^u14*YAMYr#YkbljK`~P%a^wpe}iO~fbB>7wLthZ2A_UD3qs_Tb^m+!sVJKi$NZ>(|uLmOJ*5?$@Xz=e2dKW#UnppP|%>qv&B2Yos|uHGwd=-B|)>lfjhvc;v!QG=7sye91RASvbLlwTQBow12wA3==1v<~#KaqiPFPr%>( zh!k6ouV=qkSVQ%yOx1xS^b6?C-MXtYz!*uD@km62%^}jf`0x)~Y<_~~^_H-GO2vR9 zwgir(UmXdBz!BHCmljYN=D)g5XpU|I5?cPmb5a549Pa${MAZfKhMNlKGL!&Wl7LI1Nd*Yv1 zkBiT$fO^H|UQS8tF1iMV(WCYLwal7BD!Q52Di^R7tsWfY%Nrg8Zc7~?)(L_bm{|Lo zBzP@JkJ{7w!Jvx#55lez26uT-6)$G9q3SzCR_)!UBk)X_^y51!ZqXgc^ z1VK68sF1Lv-uHZVyH1a>E6K)UOS!ApNGdwsrYU~zgULo1M*I-HT$nEEHvbN@Sr*UF z-?VM5UlG;he=O(<)8u-w8O<|1SV)IRXt>()0ffb+YXo;n($lF>%_)3Fr^Gr+t2QGs zhzQ9p5arku$ql%=ik=1nVTw!+?28z5*k{4_tYaf7Tl|qnt9oF)SlgrHjr_VQ_z(E<%t!y(WD3If7HK;OwK4qlZQ6DcxPP z!iQYS{Rx4&v--1$pY&-cI5jzq%g`SRI_G?Rs#G=P1OLaxwt36$ znq52<3BZdyy+K>_T1O8Cl3GrpBvtj{&&e};Q}Cy;7_HR%HHi_)(JRSpfxuA2@e@+C zR-HZayY|d+b>O;Lvb{YGlRzuqr+fW*wceW-t6?5|4xI6d#`ep*C<<8LSx>25Z(G=( zL{}ZHMydNOe@fBT;|Ks7olXU2j+AGgb6!ji!OdN^X%P$MJgmFG?4WU%;IdNMsKx*z zG;-b+oLzNi`hK`V{N_6kn~a)0!CiH{cbz0v8adsPrvVTaoKM)i1HJCD9Gn*DMuu1B zoHW%cY*yR~8}G31uG$xHNe)&hMLeFpYbIs#ov_1KLvR9H^o$24ay?NtbH-&sunyCzUR9*UU$_+?fSr+tw$3D@PJFgz^}6oaY?d5;X})+J)4IO zh+~{UuNXDkUT$_)IUstuQzHI#$)c`W!LIIG;Az*6+Iurv4sXpncbQuc zY#*ZoX^NDPmH3AdSF5o4ra<0ry>S>r%@}~{y_k1r!(c-isCAOZ?DahqN>_&`{q&qL z-^}o^UgJ{G?slAZEcv5*D2vz_QQSzlheJgFB4m5#+_s_3ha;PvFLa3EkSf*8y_EXq zvXkp>lh&=I>tuJ1h41@(;#8$l9k4Ur)$^S5Mr9-aoXdU0%=M4Ln>B6wyYe6pBZv!% zK;LXZzVw)&>xGBdo7~0rU+hAkhGJ#`_Xkp0Yj?K2D>Qn8SsCTYP@`a-?$u$b*QcMP z;Jb4nG&Vr0?{`pB*W^ZDY&?Y4l+0@5`~;8~+=!s~EUYLwg_hlyD-Gpk8!J+*A1j@$r8wT#Ig;(j_+a8az&P+U2u9T1D|d zhB(8+iA|;D4~CxBb2GZ-)x7zQ@}x=I8Th5h4}v`1}jw>8LGE%Vxb(L93nzC&qOsxt#g{=R!oZkqpKh4hVus(5Ol^Kj^Us5!m5im4_5Xd&3pbi+d){#Mivyx zuxtvti$Ur}dI?w(jIITQvcTqj&a2e%q9=yU88aGd(}@VE2I)g~u$x3b=9kBrdwQ1k zpOkFX{2m(v`8s$zo6|l*VQe~YQFvM-LG5Az5e+(YEjx$I*YSXpp!TMe(589 zF_vIFTJdR&u{`Lgx-n*dB+sT(qb=OHFTF2md+CZ`_Jj2t3i#Q?U6zv5Es&)?Cm;^@ zMPB;Pqt281?vVJ;3%sVY&!~CzVF*j1kW6P}QU4A5kx@=kT?zJ%j#4CI$N>aM#Q7pD zf;If$^h_tWVcW`X$=`;S)svJ{M(q=qfh-EQcZd>gStYMOtDfZuPBm;u*r-zQ>&=@a zrVfg&+yJQf#Fb`opGKmlUvf$(CLIIEMveV;KZ@tP>%GK5cc$x@8rP)^K$%K^9qk`2 zJmYvSHXZJx&R3$qdV7bG*mMUg*p^F`*se7uM%;oJ3RBV#i}fnt<;z}E*w7Yo>K#@i z78^y=DD?!|%~5Jpnejrk@_U_l-ER9#(KvMuBm5r|aNmY0!f?yLjzH>%G;r~~Y9zx^`)DjZsA+6e0Q07NiMrXB}+Q+oXvfwt!fuQMD07$lWd*MWh^o^NHZaPyp9|T*;2^GXWke*Ae~a zn5s7;nmi7^$<{71WUh!?cUfOd!j5(Z%_3OwB!<3h)|}y7l&h-RE}4ixYs%t{g7%1% zVDCYW6RFCB32Q)ZYUzHi(YNU@wZPhv(Xd8zR+}vw`_&0EqryaI;b@-L7uWKx3fpdahJkf|2mz>6bQfeY@24B4PSl6}pON7W5$S z{kiK`ko-RS@89R1)Lpb7ct1U73)@;1m+LMCDlO6Y$6%v}m!uW&1RB6)CSlS0Cz$|y z&QAj#&B6Hf2)2$1-VEb==L#y5e*Gec88_NpyunC@a_UE%T_;t0HhKExDoZtpZ5yi| z9{KaO9?9HY*5=yv%vsa>?A=w7J(^y}-NF(`MZG`_zleayX}Yz>gn=NJ!lotH=GN(! zAko75#l=QFu*W_iuF^+dmD8_1C+_EK2Rs5xJ{TSl=Obd~(-z}jcjcoIZE#K8bnX62 zgXs)Jh|^~Iqd7PPN>nYQm9ZeQ%T/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 WFH2+MFc!=J0000}oe(WbFc@7VdX3(D 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 literal 0 HcmV?d00001 diff --git a/vendor/phpoffice/phpspreadsheet/docs/topics/images/04-05-topten-autofilter-2.png b/vendor/phpoffice/phpspreadsheet/docs/topics/images/04-05-topten-autofilter-2.png new file mode 100644 index 0000000000000000000000000000000000000000..4db5125210ea59f29cb75ea60560e96286da0f72 GIT binary patch literal 22842 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 SU%${l{s*e0saOuN2>u_Pw-LPn literal 0 HcmV?d00001 diff --git a/vendor/phpoffice/phpspreadsheet/docs/topics/images/07-simple-example-1.png b/vendor/phpoffice/phpspreadsheet/docs/topics/images/07-simple-example-1.png new file mode 100644 index 0000000000000000000000000000000000000000..30d193683aa96b6839237baf5808043bc6000c08 GIT binary patch literal 12239 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%0Du42g>vFf1>WzBRf|-?z3rz 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 literal 0 HcmV?d00001 diff --git a/vendor/phpoffice/phpspreadsheet/docs/topics/images/07-simple-example-2.png b/vendor/phpoffice/phpspreadsheet/docs/topics/images/07-simple-example-2.png new file mode 100644 index 0000000000000000000000000000000000000000..e2b68506ceb7cd25d4bc3e13f7d8de422472fcc0 GIT binary patch literal 9620 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